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

View File

@ -1,4 +1,6 @@
export default { export default {
search: search: {
"repo/monir/search/@index_key/@search_type/@type_key/@listkey/@field_collapsed/@offset/@limit/@q=none", 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

Width:  |  Height:  |  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

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -3,7 +3,8 @@ import type { InputMenuItem } from "@nuxt/ui";
import hadithaApi from "../../apis/hadithaApi"; import hadithaApi from "../../apis/hadithaApi";
import type { HadithResponseModel } from "../../types/hadithType"; import type { HadithResponseModel } from "../../types/hadithType";
import { useStorage } from "@vueuse/core"; 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 // #region props
const props = defineProps({ const props = defineProps({
@ -25,10 +26,15 @@ const userSearchHistory = useStorage(
"userSearchHistory", "userSearchHistory",
new Set() // Initial value new Set() // Initial value
); );
const searchTerm = useStorage<string>("searchPhrase", "");
const searchTerm = ref("");
const open = ref(false); const open = ref(false);
const loading = 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, // If you want to share state across multiple components,
// you can use the same key in useState. Nuxt will ensure // you can use the same key in useState. Nuxt will ensure
@ -42,82 +48,97 @@ const doneTypingInterval = ref<number>(1000);
// #region reactive // #region reactive
const state = reactive({ const state = reactive({
list: [], list: [],
filters: [ vector: {
{ label: "معنایی",
label: "معنایی", value: "vector",
items: [], icon: "i-haditha-robot-indicator",
}, },
{ type: {
label: "ترجمه", icon: "",
items: [], value: "normal",
}, label: "نوع",
{ items: [
label: "مترادف", {
items: [ label: "جستجو در همه",
{ onSelect(e: Event) {
label: "جستجو در همه", 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: "نوع", synonym: {
items: [ value: "synonym",
{ label: "مترادف",
label: "جستجو در همه", icon: "i-haditha-chevron-down",
}, items: [
{ {
label: "فقط در متن عربی حدیث", label: "جستجو در همه",
}, slot: "arabic",
{ },
label: "فقط در ترجمه ها", {
}, label: "فقط در متن عربی حدیث",
{ slot: "arabic",
label: "فقط در شروح", },
}, {
], label: "فقط در ترجمه ها",
}, slot: "arabic",
{ },
label: "عین عبارت", {
items: [ label: "فقط در شروح",
{ slot: "arabic",
label: "جستجو در همه", },
}, ],
{ },
label: "فقط در متن عربی حدیث", type_key: {
}, label: "ترجمه",
{ value: "hadith",
label: "فقط در ترجمه ها", },
}, phrase: {
{ label: "عین عبارت",
label: "فقط در شروح", value: "normal",
}, },
],
},
],
}); });
// #endregion reactive // #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 // #region methods
const clearSimilar = () => { const clearSimilar = () => {
console.info("clearSimilar"); console.info("clearSimilar");
}; };
const onBlur = () => {
console.info("onBlur");
};
const onChange = () => {
console.info("onChange");
sendQuery();
};
const onUpdateModel = (newVal: boolean | InputMenuItem | any) => { const onUpdateModel = (newVal: boolean | InputMenuItem | any) => {
console.info("onUpdateModel", newVal); console.info("onUpdateModel", newVal);
}; };
@ -132,15 +153,24 @@ const onKeyUp = () => {
}, doneTypingInterval.value); }, doneTypingInterval.value);
}; };
const setType = (type: string) => {
search_type.value = type;
sendQuery();
};
const setKey = (type: string) => {
type_key.value = type;
sendQuery();
};
const sendQuery = async () => { const sendQuery = async () => {
if (loading.value) return; if (loading.value) return;
loading.value = true; loading.value = true;
let url = hadithaApi.search; let url = hadithaApi.search.list;
url = url.replace("@index_key", "dhparag"); url = url.replace("@index_key", "dhparag");
url = url.replace("@search_type", "normal"); url = url.replace("@search_type", search_type.value); // normal, and , phrase, vector, synonym
url = url.replace("@type_key", "hadith"); url = url.replace("@type_key", type_key.value); // hadith, hadith_fa, hadith_ar, hadith_shr
url = url.replace("@offset", "0"); url = url.replace("@offset", "0");
url = url.replace("@limit", "10"); url = url.replace("@limit", "10");
url = url.replace("@listkey", "normal"); url = url.replace("@listkey", "normal");
@ -150,34 +180,69 @@ const sendQuery = async () => {
searchTerm.value.length ? `q=${searchTerm.value}` : "q=none" 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) // fetch search list from backend(ssr)
const { data, status, error, refresh, clear } = return await httpService
await useHadithaSearchComposable<HadithResponseModel>(url, { .postRequest(url)
method: "post", .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 // #endregion methods
</script> </script>
<template> <template>
<div class="haditha-search-root-wrapper"> <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"> <div v-if="showPrevSearch" class="prev-search-item flex items-center">
<span class="total">۴۷ مشابه </span> <span class="total">۴۷ مشابه </span>
<span class="text me-auto"> <span class="text me-auto">
@ -191,6 +256,8 @@ const sendQuery = async () => {
@click="clearSimilar" @click="clearSimilar"
/> />
</div> </div>
<!-- <client-only> -->
<div class="search-input"> <div class="search-input">
<UInputMenu <UInputMenu
class="w-full focus:placeholder-gray-800" class="w-full focus:placeholder-gray-800"
@ -212,7 +279,7 @@ const sendQuery = async () => {
highlightOnHover highlightOnHover
@focus="open = true" @focus="open = true"
@blur="open = false" @blur="open = false"
@change="onChange" @change="sendQuery"
@update:modelValue="onUpdateModel" @update:modelValue="onUpdateModel"
@update:searchTerm="onUpdateModel" @update:searchTerm="onUpdateModel"
@keydown="onKeyDown" @keydown="onKeyDown"
@ -228,26 +295,209 @@ const sendQuery = async () => {
> >
<!-- <UIcon name="i-lucide-search" /> --> <!-- <UIcon name="i-lucide-search" /> -->
</UButton> </UButton>
<!-- </client-only> -->
</div> </div>
<div class="search-filter my-3 space-x-2" v-if="props.showFilter"> <div
<UDropdownMenu class="search-filter flex items-center my-3 justify-between"
v-for="(filter, index) in state.filters" v-if="props.showFilter && searchTerm.length"
:items="filter.items" >
:content="{ <div class="flex items-center space-x-2">
align: 'start', <!-- معنایی -->
side: 'bottom', <!-- @click.self="search_type = 'vector'" -->
sideOffset: 8,
}"
:ui="{
content: 'w-48',
}"
>
<UButton <UButton
@click.self="setType('vector')"
:class="{ active: search_type == 'vector' }"
type="button"
class="filter-item" class="filter-item"
:label="filter.label" :icon="state.vector.icon"
:trailingIcon="filter.items?.length ? 'i-haditha-chevron-down' : ''" >
/> {{ state.vector.label }}
</UDropdownMenu>
<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>
</div> </div>
</template> </template>
@ -339,50 +589,6 @@ const sendQuery = async () => {
.search-input { .search-input {
position: relative; 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> </style>
@ -429,7 +635,7 @@ const sendQuery = async () => {
height: 72px; height: 72px;
justify-content: space-between; justify-content: space-between;
padding-top: 12px; padding-top: 12px;
padding-right: 12px; /* padding-right: 12px; */
padding-bottom: 12px; padding-bottom: 12px;
padding-left: 12px; padding-left: 12px;
border-radius: 12px; border-radius: 12px;
@ -447,5 +653,129 @@ const sendQuery = async () => {
text-align: right; text-align: right;
color: #a7acbe; 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> </style>

View File

@ -15,29 +15,36 @@ const props = defineProps({
default: "/img/haditha/no-data.png", default: "/img/haditha/no-data.png",
}, },
}); });
const route = useRoute(); const router = useRouter();
const modal = useModal(); // const modal = useModal();
const isModalOpen = ref(false); // const isModalOpen = ref(false);
function openModal() { function openModal(selectedItem) {
// modal.open(SearchShow, { title: "Welcome" }); // modal.open(SearchShow, { title: "Welcome" });
isModalOpen.value = true; // isModalOpen.value = true;
} router.push({
async function closeModal() { name: "hadithaSearchShow",
await modal.close(); params: {
} id: selectedItem._id,
function resetModal() { slug: "no-slug",
modal.reset(); },
} });
function updateModalTitle() {
modal.patch({ title: "Updated Title" });
} }
// async function closeModal() {
// await modal.close();
// }
// function resetModal() {
// modal.reset();
// }
// function updateModalTitle() {
// modal.patch({ title: "Updated Title" });
// }
// components declaration // components declaration
const SearchShow = defineAsyncComponent(() => // const SearchShow = defineAsyncComponent(() =>
import("@haditha/components/haditha/search-page/SearchShow.vue") // import("@haditha/components/haditha/search-page/SearchShow.vue")
); // );
</script> </script>
<template> <template>
@ -64,7 +71,8 @@ const SearchShow = defineAsyncComponent(() =>
></p> ></p>
<div class="flex justify-end"> <div class="flex justify-end">
<p class="reference"> <p class="reference">
{{ item._source.address.vol_title }}، صفحه {{ item._source.address.page_num }} {{ item._source.address.vol_title }}، صفحه
{{ item._source.address.page_num }}
</p> </p>
</div> </div>
</div> </div>
@ -78,7 +86,7 @@ const SearchShow = defineAsyncComponent(() =>
</no-data> </no-data>
</div> </div>
<UModal <!-- <UModal
v-model:open="isModalOpen" v-model:open="isModalOpen"
:dismissible="false" :dismissible="false"
:ui="{ :ui="{
@ -93,13 +101,13 @@ const SearchShow = defineAsyncComponent(() =>
close: 'modal-close', close: 'modal-close',
}" }"
> >
<!-- <template #header><div class="hidden"></div></template> --> <template #header><div class="hidden"></div></template>
<!-- <template #content></template> --> <template #content></template>
<template #body> <template #body>
<search-show @close="isModalOpen = !isModalOpen"></search-show> <search-show @close="isModalOpen = !isModalOpen"></search-show>
</template> </template>
<!-- <template #footer></template> --> <<template #footer></template>
</UModal> </UModal> -->
</div> </div>
</template> </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({ definePageMeta({
layout: false, layout: false,
name: "hadithaSearchShow", name: "hadithaSearchShow",
}); });
useHead({ useHead({
name: "hadithaSearchShow", title: `${import.meta.env.VITE_HADITH_PAGE_TITLE}`,
title: `${import.meta.env.VITE_HADITH_PAGE_TITLE} | ${
props.selectedItem._source.meta.hadith_masoum ?? "بدون عنوان"
}`,
meta: [ meta: [
{ name: "description", content: "کاوش با هوش مصنوعی در احادیث اسلامی" }, { name: "description", content: "کاوش با هوش مصنوعی در احادیث اسلامی" },
], ],
@ -15,23 +29,52 @@ useHead({
class: import.meta.env.VITE_HADITH_SYSTEM, class: import.meta.env.VITE_HADITH_SYSTEM,
}, },
}); });
// #endregion imports
const props = defineProps({ // #region props
selectedItem: { // const props = defineProps({
type: Object, // selectedItem: {
default() { // type: Object,
return {}; // default() {
}, // return {};
}, // },
}); // },
// });
// #endregion imports
// #region refs and reactives
const emit = defineEmits(["close"]); const emit = defineEmits(["close"]);
const route = useRoute();
const router = useRouter();
const loading = ref(false);
// const open = ref(false); const state = reactive({
const closeModal = () => { selectedItem: {} as HadithResponseShowModel,
emit("close"); });
// #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({ router.push({
name: "hadithaSearch", name: "hadithaSearch",
}); });
@ -41,136 +84,157 @@ const goToTheChatbot = () => {
name: "hadithaChatbot", name: "hadithaChatbot",
}); });
}; };
// #endregion methods
fetchData();
</script> </script>
<template> <template>
<div class="search-show-modal"> <UContainer
<div class="body-header"> ui="{
<span class="top-left-bgi z-0"></span> base: 'sm:px-6 lg:px-4',
<div class="modal-title flex justify-between"> }"
<NuxtImg class="page-inner-container sm:px-6 lg:px-4 py-8"
fit="auto" >
quality="80" <div class="search-show-page">
placeholder <div class="body-header">
src="/img/haditha/haditha-title.svg" <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 <UButton
class="similar-btn" icon="i-haditha-close"
icon="i-haditha-search-3"
label="مشابه"
color="neutral" color="neutral"
variant="outline" variant="ghost"
@click.prevent="goToTheSearch" class="close-btn"
/> @click="goToTheSearch('normal')"
<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 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>
</div> </UContainer>
</template> </template>
<!-- because of the buttons, using without scoped. --> <!-- because of the buttons, using without scoped. -->
<style> <style>
.search-show-modal { .search-show-page {
.body-header { .body-header {
.modal-title { .modal-title {
padding: 0 0.5em 1.5em; padding: 0 0.5em 1.5em;
@ -248,8 +312,8 @@ const goToTheChatbot = () => {
} }
} }
.content { .content {
height: calc(100dvh - 29em); /* height: calc(100dvh - 29em); */
overflow-y: auto; /* overflow-y: auto; */
.search-item { .search-item {
padding: 1em 0 1em 0.1em; padding: 1em 0 1em 0.1em;

View File

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

View File

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