<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"; import type { Hit } from "~/systems/hadith_ui/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: "کاوش با هوش مصنوعی در احادیث اسلامی" }, { name: "msapplication-TileImage", content: "/img/haditha/fav-icons/ms-icon-144x144.png", }, { name: "theme-color", content: "#ffffff" }, ], bodyAttrs: { class: import.meta.env.VITE_HADITH_SYSTEM, }, link: [ { rel: "icon", type: "image/x-icon", href: "/img/haditha/fav-icons/favicon.ico", }, { rel: "manifest", href: "/img/haditha/fav-icons/manifest.json" }, // rel: icon { rel: "icon", type: "image/png", sizes: "16x16", href: "/img/haditha/fav-icons/favicon-16x16.png", }, { rel: "icon", type: "image/png", sizes: "32x32", href: "/img/haditha/fav-icons/favicon-32x32.png", }, { rel: "icon", type: "image/png", sizes: "96x96", href: "/img/haditha/fav-icons/favicon-96x96.png", }, { rel: "icon", sizes: "192x192", type: "image/png", href: "/img/haditha/fav-icons/android-icon-192x192.png", }, // rel: apple { rel: "apple-touch-icon", sizes: "57x57", href: "/img/haditha/fav-icons/apple-icon-57x57.png", }, { rel: "apple-touch-icon", sizes: "60x60", href: "/img/haditha/fav-icons/android-icon-60x60.png", }, { rel: "apple-touch-icon", sizes: "72x72", href: "/img/haditha/fav-icons/android-icon-72x72.png", }, { rel: "apple-touch-icon", sizes: "76x76", href: "/img/haditha/fav-icons/android-icon-76x76.png", }, { rel: "apple-touch-icon", sizes: "114x114", href: "/img/haditha/fav-icons/android-icon-114x114.png", }, { rel: "apple-touch-icon", sizes: "120x120", href: "/img/haditha/fav-icons/android-icon-120x120.png", }, { rel: "apple-touch-icon", sizes: "144x144", href: "/img/haditha/fav-icons/android-icon-144x144.png", }, { rel: "apple-touch-icon", sizes: "152x152", href: "/img/haditha/fav-icons/android-icon-152x152.png", }, { rel: "apple-touch-icon", sizes: "180x180", href: "/img/haditha/fav-icons/android-icon-180x180.png", }, ], }); // #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 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 isModalOpen = ref(false); const loading = ref(false); const httpService = useNuxtApp()["$http"]; const state = reactive({ selectedItem: [] as Hit[], 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 pageIsLessThanOne = computed(() => { console.info("pageIsLessThanOne"); let isDisabled = true; if (state.selectedItem.length) return state.selectedItem[0]._source.address.page_num <= 1; return isDisabled; }); const pageIsBiggerThanTotal = computed(() => { console.info("pageIsBiggerThanTotal"); let isDisabled = true; if ( state.selectedItem.length && 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 = 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 = <Hit[]>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, pageNumber: number | undefined = undefined ) => { if (loading.value) return; loading.value = true; if (state.selectedItem?.length) { const isPageBiggerThanOne = state.selectedItem[0]._source.address.page_num + prevNextIndicator > 0; const isPageLessThanTotal = state.selectedItem[0]._source.address.page_num + prevNextIndicator < +route.query.page_count; if (!(isPageBiggerThanOne && isPageLessThanTotal)) 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).toString() ); url = url.replace("@step", prevNextIndicator.toString()); httpService .getRequest(url) .then((res: HadithResponseModel) => { state.selectedItem = res.hits.hits; page_num.value = res.hits.hits[0]._source.address.page_num ?? 1; }) .finally(() => (loading.value = false)); }; const handlePageChange = () => { handlePagination(1, page_num.value); }; const getDataTree = () => { let id = route.params.id; let url = repoUrl() + hadithaApi.search.getDataTree; url = url.replace("@appname", "monir"); url = url.replace("@offset", 0); url = url.replace("@limit", 10000); url = url.replace("@vol_id", id); url = url.replace("@q", "none"); httpService.getRequest(url).then((res) => { state.treeItems = prepareTreeData(res.hits.hits); }); }; const prepareTreeData = (data) => { return data.map((item) => { return { ...item, ...item._source, title: item._source.content, }; }); }; getDataTree(); // #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-if="state.selectedItem?.length" 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="pageIsLessThanOne" /> <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="pageIsBiggerThanTotal" /> </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', 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="state.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>