hadith_ui/pages/haditha/library/[id]/[slug]/index.vue
2025-04-12 14:42:52 +03:30

590 lines
15 KiB
Vue

<script setup lang="ts">
// 1. Imports
// 2. Metas
// 3. Props
// 2. Reactive State
// 3. Computed Properties
// 4. Functions / Methods
// 5. Lifecycle Hooks
// 6. Watchers
const route = useRoute();
const router = useRouter();
// #region imports
import hadithaApi from "@haditha/apis/hadithaApi";
import type { HadithResponseModel, Hits } from "@haditha/types/hadithType";
// #endregion imports
// #region meta
definePageMeta({
layout: false,
name: "hadithaLibraryShow",
});
useHead({
title: `${import.meta.env.VITE_HADITH_PAGE_TITLE} | ${route.params.slug}`,
meta: [
{ name: "description", content: "کاوش با هوش مصنوعی در احادیث اسلامی" },
],
bodyAttrs: {
class: import.meta.env.VITE_HADITH_SYSTEM,
},
});
// #endregion imports
// #region props
const props = defineProps({
list: {
default() {
return [];
},
},
noDataText: {
default: "نتیجه‌ای یافت نشد!",
},
noDataIcon: {
default: "/img/haditha/no-data.png",
},
});
// #endregion props
// #region refs and reactives
const prevIsDisabled = computed(() => {
console.info("prevIsDisabled");
let isDisabled = true;
if (state.selectedItem[0]._source.address.page_num)
return !(state.selectedItem[0]._source.address.page_num - 1 > 1);
return isDisabled;
});
const nextIsDisabled = computed(() => {
console.info("prevIsDisabled");
let isDisabled = true;
if (state.selectedItem[0]._source.address.page_num)
return !(
state.selectedItem[0]._source.address.page_num + 1 <
+route.query.page_count
);
return isDisabled;
});
// const page_num = computed({
// // getter
// get() {
// return state.selectedItem?.[0]._source.address.page_num - 1 >= 1
// ? state.selectedItem?.[0]._source.address.page_num - 1
// : 1;
// },
// // setter
// set(newValue) {
// console.info(newValue);
// // handlePagination(1, newValue);
// },
// });
const treeItems = [
{
title: "فصل اول",
icon: "lucide:plus",
children: [
{ title: "useAuth.ts", icon: "vscode-icons:file-type-typescript" },
{ title: "useUser.ts", icon: "vscode-icons:file-type-typescript" },
],
},
{
title: "فصل دوم",
icon: "lucide:plus",
children: [
{ title: "useAuth.ts", icon: "vscode-icons:file-type-typescript" },
{ title: "useUser.ts", icon: "vscode-icons:file-type-typescript" },
],
},
{
title: "فصل سوم",
icon: "lucide:plus",
children: [
{
title: "بخش اول",
icon: "lucide:plus",
children: [
{ title: "بخش اول-یک", icon: "vscode-icons:file-type-vue" },
{
title: "بخش اول-دو",
icon: "vscode-icons:file-type-vue",
children: [
{ title: "بخش اول-یک-یک", icon: "vscode-icons:file-type-vue" },
{ title: "بخش اول-یک-دو", icon: "vscode-icons:file-type-vue" },
],
},
],
},
{
title: "بخش دوم",
icon: "lucide:plus",
children: [
{ title: "Card.vue", icon: "vscode-icons:file-type-vue" },
{
title: "Button.vue",
icon: "vscode-icons:file-type-vue",
},
],
},
],
},
{ title: "فصل چهارم", icon: "vscode-icons:file-type-vue" },
{ title: "فصل پنجم", icon: "vscode-icons:file-type-nuxt" },
];
const isModalOpen = ref(false);
const loading = ref(false);
const httpService = useNuxtApp()["$http"];
const state = reactive({
selectedItem: {} as Hits,
});
const page_num = ref(1);
// #endregion refs and reactives
// #region methods
const fetchData = async () => {
if (loading.value) return;
loading.value = true;
let url = repoUrl() + hadithaApi.library.show;
url = url.replace("@appname", "monir");
url = url.replace("@page_start", route.query.page_first);
url = url.replace("@page_end", +route.query.page_first + 1);
url = url.replace("@vol_id", route.params.id.toString());
// fetch search list from backend(ssr)
const { data, status, error, refresh, clear } =
await useHadithaSearchComposable<HadithResponseModel>(url, {
method: "get",
});
if (status.value == "success") {
state.selectedItem = <Hits>data.value.hits.hits;
}
loading.value = false;
};
fetchData();
const goToTheLibrary = (type: string) => {
router.push({
name: "hadithaLibrary",
});
};
const onOpenList = () => {
console.info("onOpenList");
isModalOpen.value = true;
};
const onSearch = () => {
// console.info("onSearch");
router.push({
name:'hadithaSearch'
})
};
const onClose = () => {
console.info("onClose");
router.push({
name: "hadithLibrary",
});
};
const handlePagination = (
prevNextIndicator: number | string,
pageNumber: number | string | undefined = undefined
) => {
if (loading.value) return;
loading.value = true;
if (
!(
state.selectedItem[0]._source.address.page_num + prevNextIndicator > 1 &&
state.selectedItem[0]._source.address.page_num + prevNextIndicator <
+route.query.page_count
)
)
return;
let url = repoUrl() + hadithaApi.library.prevNextHadith;
url = url.replace("@index_key", "dhparag");
url = url.replace("@vol_id", state.selectedItem[0]._source.address.vol_id);
url = url.replace(
"@page_num",
pageNumber ?? state.selectedItem[0]._source.address.page_num
);
url = url.replace("@step", <string>prevNextIndicator);
httpService
.getRequest(url)
.then((res: HadithResponseModel) => {
state.selectedItem = res.hits.hits;
})
.finally(() => (loading.value = false));
};
const handlePageChange = () => {
handlePagination(1, page_num.value);
};
// #endregion methods
// components declaration
const HadithaLayout = defineAsyncComponent(
() => import("@haditha/layouts/HadithaLayout.vue")
);
const UTree = defineAsyncComponent(
() => import("@haditha/components/haditha/library-show/UTree.vue")
);
// const AccordionMenu = defineAsyncComponent(
// () => import("@haditha/components/haditha/library-show/AccordionMenu..vue")
// );
</script>
<template>
<HadithaLayout>
<div class="page-container h-full">
<UContainer
ui="{
base: 'sm:px-6 lg:px-4',
}"
class="page-inner-container sm:px-6 lg:px-4"
>
<div class="page-header py-4 flex justify-between items-center">
<div class="flex items-center">
<UButton
class="menu p-1 me-8"
@click="onOpenList"
icon="i-lucide-menu"
variant=""
/>
<h1 class="m-0 title">
{{ route.params.slug }}
</h1>
</div>
<div class="flex items-center">
<UButton
@click="onSearch"
class="my-trailing-button search p-0"
icon="i-lucide-search"
variant=""
size="xl"
>
</UButton>
<UButton
@click="goToTheLibrary"
class="my-trailing-button close p-0 ms-8"
icon="i-lucide:x"
variant=""
size="xl"
>
</UButton>
</div>
</div>
<div class="separator"></div>
<div class="page-content py-14 p-2">
<!-- <h2></h2> -->
<p
v-for="(parag, index) in state.selectedItem"
:key="index"
v-html="parag?._source?.content"
></p>
</div>
<div class="body-footer">
<div class="mt-5 z-2">
<div class="flex justify-between pagination">
<UButton
@click="handlePagination(-1)"
class="prev-haditha"
label="حدیث قبل"
color=""
variant="soft"
icon="i-haditha-chevron-right"
:disabled="prevIsDisabled"
/>
<div class="flex items-center">
<span class="total-pages">{{ route.query.page_count }}</span>
<span class="mx-2">/</span>
<UInput
:disabled="loading"
color="neutral"
variant="outline"
v-model="page_num"
@change="handlePageChange"
:ui="{
root: 'root ',
base: 'base page-number-input',
leading: 'leading',
leadingIcon: 'leadingIcon',
leadingAvatar: 'leadingAvatar',
leadingAvatarSize: 'leadingAvatarSize',
trailing: 'trailing',
trailingIcon: 'trailingIcon',
}"
/>
</div>
<UButton
@click="handlePagination(1)"
class="next-haditha"
label="حدیث بعد"
color=""
variant="soft"
trailing-icon="i-haditha-chevron-left"
:disabled="nextIsDisabled"
/>
</div>
</div>
</div>
</UContainer>
</div>
<UModal
v-model:open="isModalOpen"
:dismissible="false"
:ui="{
footer: 'modal-footer',
overlay: 'modal-overlay',
content: 'modal-content accordion-menu h-10',
header: 'modal-header',
wrapper: 'modal-wrapper',
body: 'modal-body',
title: 'modal-title',
description: 'modal-description',
close: 'modal-close ring-[white]',
}"
title="فهرست"
:close="{
color: 'primary',
variant: 'outline',
class: 'rounded-full',
}"
>
<!-- <template #header></template> -->
<!-- <template #content></template> -->
<template #body>
<UTree :items="treeItems" />
<!-- <accordion-menu @close="isModalOpen = !isModalOpen"></accordion-menu> -->
</template>
<!-- <template #footer></template> -->
</UModal>
</HadithaLayout>
</template>
<style scoped>
.page-container {
background: #f7fffd;
.page-inner-container {
/* position: relative; */
/* padding-bottom: 4em; */
/* width: 100%; */
/* max-width: 1200px; */
/* margin-right: auto; */
/* margin-left: auto; */
.page-header {
color: var(--ui-color-two);
.menu {
width: 24;
height: 24;
margin-left: 2.2em;
}
.title {
font-family: IRANSansX;
font-weight: 400;
font-size: 16px;
line-height: 20px;
letter-spacing: 0%;
text-align: right;
/* Fallback color */
color: #4d00ff;
/* Gradient background */
background: linear-gradient(268.94deg, #d284ff -0.65%, #4d00ff 104.59%);
/* Clip the background to the text */
background-clip: text;
-webkit-background-clip: text; /* For Safari */
/* Make the text transparent */
color: transparent;
-webkit-text-fill-color: transparent; /* For Safari */
}
}
.separator {
/* border: 0.5px solid #eee; */
height: 1px;
background-position: center center;
background-size: contain;
background-image: linear-gradient(
90deg,
#ffffff 0%,
#a0f5ff 30.4%,
#3fc9fa 71.47%,
#a7ffe7 100%
);
}
.page-content {
/* margin: 1.5em; */
font-family: Takrim;
font-weight: 400;
font-size: 20px;
line-height: 40px;
letter-spacing: 0%;
text-align: right;
color: var(--ui-color-two);
height: calc(100dvh - 8em);
overflow-y: auto;
}
.body-footer {
.actions {
margin-bottom: 1em;
.similar-btn {
width: 114;
height: 56;
gap: 8px;
border-radius: 12px;
border-width: 0.5px;
padding-top: 8px;
padding-right: 20px;
padding-bottom: 8px;
padding-left: 24px;
background: #ffffff;
border: 0.5px solid;
border-image-source: linear-gradient(
102.02deg,
#4be8ae 7.38%,
#00a762 91.78%
);
box-shadow: 0px 8px 20px 0px #0000001a;
font-family: IRANSansX;
font-weight: 400;
font-size: 15px;
line-height: 22.5px;
letter-spacing: 0%;
text-align: right;
color: var(--ui-color-two);
}
.explore-btn {
width: 118;
height: 56;
gap: 4px;
border-radius: 12px;
padding-top: 8px;
padding-right: 24px;
padding-bottom: 8px;
padding-left: 20px;
background: linear-gradient(
268.94deg,
#d284ff -0.65%,
#4d00ff 104.59%
);
box-shadow: 0px 8px 20px 0px #0000001a;
font-family: IRANSansX;
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0%;
text-align: right;
color: #fff;
}
}
.pagination {
padding: 0.7em 0px;
/* width: 672; */
/* height: 56; */
justify-content: space-between;
border-radius: 16px;
border-width: 0.3px;
padding-right: 32px;
padding-left: 32px;
background: #ffffff;
border: 0.3px solid #e0e0e0;
box-shadow: 0px 8px 20px 0px #0000001a;
.total-pages {
font-family: IRANSansX;
font-weight: 400;
font-size: 12px;
line-height: 100%;
letter-spacing: 0%;
text-align: right;
color: #8a92a8;
}
.prev-haditha {
font-family: IRANSansX;
font-weight: 300;
font-size: 12px;
line-height: 20px;
letter-spacing: 0%;
text-align: right;
color: var(--ui-color-two);
}
.next-haditha {
font-family: IRANSansX;
font-weight: 300;
font-size: 12px;
line-height: 20px;
letter-spacing: 0%;
text-align: right;
color: var(--ui-color-two);
}
}
}
}
}
</style>
<style>
.page-container {
.page-inner-container {
.body-footer {
.pagination {
.page-number-input {
display: flex;
justify-content: center;
align-items: center;
width: 3em /*36px*/;
height: 3em /*36px*/;
gap: 4px;
border-radius: 12px;
border-width: 0.5px;
border: 0.3px solid #e0e0e0;
box-shadow: 0px 8px 20px 0px #0000001a;
font-family: IRANSansX;
font-weight: 400;
font-size: 0.75rem;
line-height: 100%;
letter-spacing: 0%;
text-align: center;
color: #8a92a8;
}
}
}
}
}
</style>