<script setup lang="ts"> import hadithaApi from "@haditha/apis/hadithaApi"; import headLinks from "@haditha/json/haditha/headLinks"; import headMetas from "@haditha/json/haditha/headMetas"; import { useInfiniteScroll } from "@vueuse/core"; definePageMeta({ layout: false, name: "hadithaLibrary", }); useHead({ name: "hadithaLibrary", title: `${import.meta.env.VITE_HADITH_PAGE_TITLE} | کتابخانه`, meta: [ { name: "description", content: "کاوش با هوش مصنوعی در احادیث اسلامی" }, ...headMetas, ], bodyAttrs: { class: import.meta.env.VITE_HADITH_SYSTEM, }, link: headLinks, }); // #region refs const httpService = useNuxtApp()["$http"]; const offset = useState("offset", () => 0); const total = useState("total", () => 0); const loading = useState("loading", () => false); const hasMore = useState("hasMore", () => true); const el = ref(null); // #endregion refs // #region reactive const state = reactive({ pagination: { limit: 10, page: 1, pages: 1, }, }); // #region methods const getLibraryList = async () => { let url = repoUrl() + hadithaApi.library.list; url = url.replace("@field_collapsed", "normal"); url = url.replace("@offset", offset.value); url = url.replace("@limit", state.pagination.limit); url = url.replace("@q", "none"); return await httpService.postRequest(url).then((res) => { total.value = res.hits?.total?.value ?? 0; offset.value += state.pagination.limit; return res; }); }; // Server-side initial load const { data: loadedItems } = await useAsyncData( "libraryList", () => getLibraryList(), { transform: (data) => data.hits.hits, } ); // Client-side infinite scroll useInfiniteScroll( el, async () => { if (!hasMore.value || loading.value) return; loading.value = true; try { await getLibraryList().then((res) => { const hits = res?.hits?.hits ?? []; if (hits.length > 0) { // Use spread operator to create new array reference loadedItems.value = [...loadedItems.value, ...hits]; } else { hasMore.value = false; } }); } catch (error) { hasMore.value = false; // console.error("Error loading more items:", error); // Consider setting hasMore.value = false if you want to stop on error } finally { loading.value = false; } }, { distance: 100 } ); // #endregion methods // components declaration const HadithaLayout = defineAsyncComponent( () => import("@haditha/layouts/HadithaLayout.vue") ); const NavigationMenu = defineAsyncComponent( () => import("@haditha/components/haditha/NavigationMenu.vue") ); const CardList = defineAsyncComponent( () => import("@haditha/components/haditha/CardList.vue") ); </script> <template> <HadithaLayout> <div class="search-box-container h-full flex flex-col justify-center"> <navigation-menu></navigation-menu> <div class="library-list-contianer"> <div class="page-header flex items-center"> <span class="title">کتابخانه</span> <img fit="auto" quality="80" src="/img/haditha/haditha-title.svg" /> </div> <div ref="el" class="library-list pl-4 firefox-scrollbar grid grid-cols-2 gap-x-15 gap-y-12 md:grid-cols-3 md:gap-x-28 md:gap-y-12 lg:grid-cols-5 lg:gap-x-28 lg:gap-y-12 mx-6" > <!-- Client-side loaded content --> <card-list no-data-text="هنوز چیزی ذخیره نکردهاید!" no-data-icon="/img/haditha/no-data.png" :list="loadedItems" ></card-list> </div> </div> </div> </HadithaLayout> </template> <style scoped> .search-box-container { padding-top: 8.3em; background: #f7fffd; .library-list-contianer { height: 100%; margin-top: 10em; max-width: 1200px; width: 100%; margin: 0 1em; margin-right: auto; margin-left: auto; .page-header { margin-bottom: 2em; .title { margin-left: 0.4em; font-family: var(--font); font-weight: 300; font-size: 24px; line-height: 36px; letter-spacing: 0%; text-align: center; color: var(--ui-color-two); } } .library-list { /* padding: 1em 1.3em; */ height: calc(100dvh - 13.5em); overflow-y: auto; } .no-data-text { font-family: var(--font); font-weight: 300; font-size: 16px; line-height: 24px; letter-spacing: 0%; text-align: center; } } } @media screen and (max-width: 991.99px) { .search-box-container { padding-top: 0em; } .library-list { height: calc(100dvh - 13em); } .page-header { margin-top: 4em; margin-right: 2em; margin-bottom: 1em; } } </style>