Fix page refresh bug in library and search

This commit is contained in:
mustafa-rezae 2025-05-08 14:35:01 +03:30
parent dc2d5cc460
commit 917824f098
4 changed files with 298 additions and 174 deletions

View File

@ -31,7 +31,6 @@ const goToLibraryShow = (item) => {
<template> <template>
<UCard <UCard
v-if="props.list?.length"
v-for="(item, index) in props.list" v-for="(item, index) in props.list"
class="mx-auto" class="mx-auto"
:key="index" :key="index"
@ -46,7 +45,6 @@ const goToLibraryShow = (item) => {
<!-- <template #header></template> --> <!-- <template #header></template> -->
<ULink <ULink
v-if="item?._source?.id"
:to="{ :to="{
name: 'hadithaLibraryShow', name: 'hadithaLibraryShow',
params: { params: {
@ -65,8 +63,8 @@ const goToLibraryShow = (item) => {
quality="80" quality="80"
placeholder placeholder
src="/img/haditha/library/totally.webp" src="/img/haditha/library/totally.webp"
/> />
<!-- src="/img/haditha/sample-bgi.svg" --> <!-- src="/img/haditha/sample-bgi.svg" -->
<p class="title">{{ item?._source?.title }}</p> <p class="title">{{ item?._source?.title }}</p>
<p class="version"> <p class="version">
جلد جلد
@ -76,14 +74,6 @@ const goToLibraryShow = (item) => {
<!-- <template #footer> </template> --> <!-- <template #footer> </template> -->
</UCard> </UCard>
<no-data
class="h-full w-full flex flex-col justify-center items-center"
v-else
>
<img fit="auto" quality="80" placeholder :src="props.noDataIcon" />
<p class="no-data-text">{{ props.noDataText }}</p>
</no-data>
</template> </template>
<style scoped> <style scoped>

View File

@ -7,6 +7,9 @@ const props = defineProps({
return []; return [];
}, },
}, },
requestStatus: {
default: "pending",
},
total: { total: {
default: 0, default: 0,
}, },
@ -84,6 +87,17 @@ const removeFromFavorites = async (item = {}, index = 0) => {
</script> </script>
<template> <template>
<!-- <template v-if="props.requestStatus == 'pending'"> -->
<!-- <div class="flex items-center gap-4 mb-4" v-for="item in props?.list?.length">
<USkeleton class="h-12 w-12 rounded-full" />
<div class="grid gap-2 flex-grow-1">
<USkeleton class="h-4 " />
<USkeleton class="h-4 " />
</div>
</div> -->
<!-- </template> -->
<!-- <template v-else-if="props.requestStatus == 'success' || props.requestStatus == 'idle'"> -->
<div <div
v-if="props?.list?.length" v-if="props?.list?.length"
class="search-list-item" class="search-list-item"
@ -155,6 +169,7 @@ const removeFromFavorites = async (item = {}, index = 0) => {
</p> --> </p> -->
</div> </div>
</div> </div>
<!-- </template> -->
<!-- <UModal <!-- <UModal
v-model:open="isModalOpen" v-model:open="isModalOpen"

View File

@ -2,14 +2,22 @@
import hadithaApi from "@haditha/apis/hadithaApi"; import hadithaApi from "@haditha/apis/hadithaApi";
import headLinks from "@haditha/json/haditha/headLinks"; import headLinks from "@haditha/json/haditha/headLinks";
import headMetas from "@haditha/json/haditha/headMetas"; import headMetas from "@haditha/json/haditha/headMetas";
import { useInfiniteScroll } from "@vueuse/core"; // import { useInfiniteScroll } from "@vueuse/core";
const id_token = useCookie("id_token");
const token = id_token.value ?? "GuestAccess";
const config = useRuntimeConfig();
const baseUrl =
config.public.NUXT_PUBLIC_BASE_URL + config.public.NUXT_PUBLIC_API_NAME;
// this enable us to send cookies.
const requestFetch = useRequestFetch();
definePageMeta({ definePageMeta({
layout: false, layout: false,
name: "hadithaLibrary", name: "hadithaLibrary",
}); });
useHead({ useHead({
name: "hadithaLibrary",
title: `${import.meta.env.VITE_HADITH_PAGE_TITLE} | کتابخانه`, title: `${import.meta.env.VITE_HADITH_PAGE_TITLE} | کتابخانه`,
meta: [ meta: [
{ name: "description", content: "کاوش با هوش مصنوعی در احادیث اسلامی" }, { name: "description", content: "کاوش با هوش مصنوعی در احادیث اسلامی" },
@ -22,19 +30,21 @@ useHead({
}); });
// #region refs // #region refs
const httpService = useNuxtApp()["$http"]; // const httpService = useNuxtApp()["$http"];
// const { $api } = useNuxtApp()
const offset = useState("offset", () => 0); const offset = useState("offset", () => 0);
const total = useState("total", () => 0); const total = useState("total", () => 0);
const loading = useState("loading", () => false); // const libraryList = useState("libraryList", () => []);
const hasMore = useState("hasMore", () => true); // const loading = useState("loading", () => false);
// const hasMore = useState("hasMore", () => true);
const el = ref(null); // const el = ref(null);
// #endregion refs // #endregion refs
// #region reactive // #region reactive
const state = reactive({ const state = reactive({
pagination: { pagination: {
limit: 10, limit: 15,
page: 1, page: 1,
pages: 1, pages: 1,
}, },
@ -42,59 +52,65 @@ const state = reactive({
// #region methods // #region methods
const getLibraryList = async () => { const getLibraryList = () => {
let url = repoUrl() + hadithaApi.library.list; let url = baseUrl + repoUrl() + hadithaApi.library.list;
url = url.replace("@field_collapsed", "normal"); url = url.replace("@field_collapsed", "normal");
url = url.replace("@offset", offset.value); url = url.replace("@offset", offset.value);
url = url.replace("@limit", state.pagination.limit); url = url.replace("@limit", state.pagination.limit);
url = url.replace("@q", "none"); url = url.replace("@q", "none");
return await httpService.postRequest(url).then((res) => { return requestFetch(url, {
total.value = res.hits?.total?.value ?? 0; method: "POST",
headers: {
Authorization: token,
},
}).then((data) => {
total.value = data.hits?.total?.value ?? 0;
offset.value += state.pagination.limit; offset.value += state.pagination.limit;
return data.hits?.hits;
return res;
}); });
}; };
// Server-side initial load // Server-side initial load
const { data: loadedItems } = await useAsyncData( // Wrapping with useAsyncDataavoid double data fetching when
"libraryList", // doing server-side rendering (server & client on hydration).
() => getLibraryList(), const { data: libraryList } = await useAsyncData("libraryList", () =>
{ getLibraryList()
transform: (data) => data.hits.hits,
}
); );
// Client-side infinite scroll // Client-side infinite scroll
useInfiniteScroll( const loadMore = async () => {
el, // const listElm = $event.target;
async () => {
if (!hasMore.value || loading.value) return;
loading.value = true; // if (status.value == "pending") return;
try { // // window.innerHeight + window.scrollY >= document.body.offsetHeight - 100
await getLibraryList().then((res) => { // if (listElm.scrollTop + listElm.clientHeight >= listElm.scrollHeight) {
const hits = res?.hits?.hits ?? []; // status.value = "pending";
// mainState.pagination.offset =
// mainState.pagination.offset + mainState.pagination.limit;
if (hits.length > 0) { // if (total.value > mainState.pagination.offset) {
// Use spread operator to create new array reference // window.clearTimeout(isScrolling.value);
loadedItems.value = [...loadedItems.value, ...hits]; // isScrolling.value = setTimeout(() => {
} else { return await getLibraryList().then((res) => {
hasMore.value = false; const hits = res ?? [];
} // Use spread operator to create new array reference
}); libraryList.value = [...libraryList.value, ...hits];
} catch (error) { // status.value = "success";
hasMore.value = false; return res;
});
// console.error("Error loading more items:", error); // }, 300);
// Consider setting hasMore.value = false if you want to stop on error // } else {
} finally { // toast.add({
loading.value = false; // title: "کاربر محترم",
} // description: "دیگر رکوردی جهت بارگزاری وجود ندارد.",
}, // color: "success",
{ distance: 100 } // });
); // status.value = "idle";
// }
// } else status.value = "idle";
};
const { isFetching } = useInfiniteScroll(loadMore, "libraryInfiniteScroll");
// #endregion methods // #endregion methods
// components declaration // components declaration
@ -121,15 +137,24 @@ const CardList = defineAsyncComponent(
</div> </div>
<div <div
ref="el" ref="libraryInfiniteScroll"
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" id="libraryInfiniteScroll"
class="library-list pl-4 firefox-scrollbar 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"
:class="{ 'grid grid-cols-2': libraryList?.length }"
> >
<!-- Client-side loaded content --> <!-- Client-side loaded content -->
<card-list <card-list
no-data-text="هنوز چیزی ذخیره نکرده‌اید!" :list="libraryList"
no-data-icon="/img/haditha/no-data.png" no-data-text="به زودی لیست کتاب ها بروزرسانی خواهد شد."
:list="loadedItems" no-data-icon=""
></card-list> ></card-list>
<no-data
class="h-full w-full flex flex-col justify-center items-center"
v-if="libraryList?.length == 0"
>
<p class="no-data-text">به زودی لیست کتاب ها بروزرسانی خواهد شد.</p>
</no-data>
</div> </div>
</div> </div>
</div> </div>
@ -169,6 +194,7 @@ const CardList = defineAsyncComponent(
/* padding: 1em 1.3em; */ /* padding: 1em 1.3em; */
height: calc(100dvh - 13.5em); height: calc(100dvh - 13.5em);
overflow-y: auto; overflow-y: auto;
scroll-behavior: smooth;
} }
.no-data-text { .no-data-text {
font-family: var(--font); font-family: var(--font);

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useStorage } from "@vueuse/core"; import { useStorage } from "@vueuse/core";
import { useInfiniteScroll } from "@vueuse/core"; // import { useInfiniteScroll } from "@vueuse/core";
import * as z from "zod"; import * as z from "zod";
// const myCookie = useCookie("searchPhrase"); // const myCookie = useCookie("searchPhrase");
@ -10,6 +10,7 @@ import headLinks from "@haditha/json/haditha/headLinks";
import headMetas from "@haditha/json/haditha/headMetas"; import headMetas from "@haditha/json/haditha/headMetas";
import hadithaApi from "@haditha/apis/hadithaApi"; import hadithaApi from "@haditha/apis/hadithaApi";
import type { Synonym } from "@haditha/types/hadithType"; import type { Synonym } from "@haditha/types/hadithType";
const toast = useToast();
// const searchTerm = useState("searchTerm", () => ''); // Tracks the searchTerm // const searchTerm = useState("searchTerm", () => ''); // Tracks the searchTerm
const offset = useState("offset", () => 0); // Tracks the current offset const offset = useState("offset", () => 0); // Tracks the current offset
@ -34,15 +35,11 @@ useHead({
// #region refs // #region refs
// وقتی از صفحه حدیث با کلیک بر روی دکمه مشابه، وارد صفحه جستجو میشویم // وقتی از صفحه حدیث با کلیک بر روی دکمه مشابه، وارد صفحه جستجو میشویم
const showPrevSearch = ref(false); // const showPrevSearch = ref(false);
// لیست جستجو در حالت اسکرول
// const loadedItems = ref([]);
// لودینگ
const loading = ref(false);
// هنگام اسکرول، چک میشود که ایا صفحه بعدی هم وجود دارد یا نه. // هنگام اسکرول، چک میشود که ایا صفحه بعدی هم وجود دارد یا نه.
const hasMore = ref(true); const hasMore = ref(true);
// عنصری که برای اسکرول استفاده میشه. // عنصری که برای اسکرول استفاده میشه.
const el = ref(null); // const el = ref(null);
// پلاگین ارسال درخواست // پلاگین ارسال درخواست
const httpService = useNuxtApp()["$http"]; const httpService = useNuxtApp()["$http"];
// استفاده از روت // استفاده از روت
@ -142,9 +139,10 @@ const AutoComplationState = reactive({
}); });
const mainState = reactive({ const mainState = reactive({
pagination: { pagination: {
limit: 10, limit: 15,
page: 1, page: 1,
pages: 1, pages: 1,
offset: 0,
}, },
}); });
@ -167,7 +165,7 @@ const backgroundImageStyle = computed(() => {
// }); // });
let optimizedImageUrl = "/img/haditha/background.webp"; let optimizedImageUrl = "/img/haditha/background.webp";
if (!showNoData.value) { if (loadedItems.value) {
// optimizedImageUrl = img("/img/haditha/sub-header-bgi.webp", { // optimizedImageUrl = img("/img/haditha/sub-header-bgi.webp", {
// quality: 80, // quality: 80,
// }); // });
@ -205,117 +203,150 @@ const exitSimilarMode = () => {
}; };
// ارسال درخواست // ارسال درخواست
const sendQuery = async (payload = {}) => { const sendQuery = async (payload = {}) => {
let url = hadithaApi.search.list; try {
url = url.replace("@index_key", "dhparag"); let url = hadithaApi.search.list;
url = url.replace("@search_type", search_type.value); // normal, and , phrase, vector, synonym url = url.replace("@index_key", "dhparag");
url = url.replace("@type_key", type_key.value); // hadith, hadith_fa, hadith_ar, hadith_shr url = url.replace("@search_type", search_type.value); // normal, and , phrase, vector, synonym
url = url.replace("@offset", offset.value); url = url.replace("@type_key", type_key.value); // hadith, hadith_fa, hadith_ar, hadith_shr
url = url.replace("@limit", mainState.pagination.limit); url = url.replace("@offset", offset.value);
url = url.replace("@listkey", "normal"); url = url.replace("@limit", mainState.pagination.limit);
url = url.replace("@field_collapsed", "normal"); url = url.replace("@listkey", "normal");
url = url.replace("@field_collapsed", "normal");
// اگر نوع انتخاب شود. // اگر نوع انتخاب شود.
const isTypeSelected = const isTypeSelected =
typeModelValue.value == "arabic" || typeModelValue.value == "arabic" ||
typeModelValue.value == "translations" || typeModelValue.value == "translations" ||
typeModelValue.value == "descriptions"; typeModelValue.value == "descriptions";
if (searchTerm.value.length) { if (searchTerm.value.length) {
url = url.replace( url = url.replace(
"@q=none", "@q=none",
`q=${isTypeSelected ? "#" + typeModelValueFa.value + " " : ""}${ `q=${isTypeSelected ? "#" + typeModelValueFa.value + " " : ""}${
searchTerm.value searchTerm.value
}` }`
); );
// if (route.query.f_aik?.length) url += `&f_aik=${route.query.f_aik}`; // if (route.query.f_aik?.length) url += `&f_aik=${route.query.f_aik}`;
}
return await httpService
.postRequest(url, payload)
.then((res) => {
total.value = res.hits.total.value ?? 0;
offset.value += mainState.pagination.limit;
// 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;
return res;
})
.catch((err) => {});
} catch (err) {
console.error("API Error:", err.message);
throw err; // Re-throw the error to be handled by useAsyncData
} }
return await httpService.postRequest(url, payload).then((res) => {
total.value = res.hits.total.value ?? 0;
offset.value += mainState.pagination.limit;
// 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;
return res;
});
}; };
// Server-side initial load // Server-side initial load
const { data: loadedItems } = await useAsyncData("search", () => sendQuery(), {
transform: (data) => data.hits.hits,
});
showNoData.value = loadedItems.value?.length == 0;
const onBeforeSendQuery = (from) => { const {
if (loading.value) return; data: loadedItems,
loading.value = true; status,
refresh,
history?.pushState( clear,
{}, error,
document?.title, execute,
route.path + `?q=${searchTerm.value}` } = await useAsyncData(
); "search",
async () => {
if (searchTerm.value.length) {
return await sendQuery();
} else {
return {
hits: {
hits: undefined,
},
};
}
},
{
transform: (data) => data.hits.hits,
}
);
const onBeforeSendQuery = () => {
if (status.value == "pending") return;
status.value = "pending";
offset.value = 0; offset.value = 0;
if (searchTerm.value?.length) { if (searchTerm.value?.length) {
sendQuery().then((res) => { sendQuery().then((res) => {
loadedItems.value = res.hits.hits; loadedItems.value = res?.hits?.hits ?? [];
showNoData.value = loadedItems.value?.length == 0; showNoData.value = loadedItems.value?.length == 0;
loading.value = false; status.value = "success";
history?.pushState(
{},
document?.title,
route.path + `?q=${searchTerm.value}`
);
route.query.q = searchTerm.value;
}); });
} else { } else {
searchTerm.value = ""; resetForm();
loading.value = false;
loadedItems.value = []; // searchTerm.value = "";
showNoData.value = false; // loading.value = false;
loading.value = false; // loadedItems.value = [];
// showNoData.value = false;
// loading.value = false;
} }
}; };
// Client-side infinite scroll // Client-side infinite scroll
useInfiniteScroll( // useInfiniteScroll(
el, // el,
async () => { // async () => {
if (!hasMore.value || loading.value) return; // if (!hasMore.value || loading.value) return;
loading.value = true; // loading.value = true;
try { // try {
// const nextPage = page.value + 1; // // const nextPage = page.value + 1;
await sendQuery().then((res) => { // await sendQuery().then((res) => {
const hits = res.hits.hits; // const hits = res.hits.hits;
if (hits.length) { // if (hits.length) {
loadedItems.value.push(...hits); // loadedItems.value.push(...hits);
} else { // } else {
hasMore.value = false; // hasMore.value = false;
} // }
}); // });
} finally { // } finally {
loading.value = false; // loading.value = false;
} // }
}, // },
{ distance: 100 } // { distance: 100 }
); // );
// دکمه جستجو کردن // دکمه جستجو کردن
const onSearchButtonClick = () => { const onSearchButtonClick = () => {
if (loading.value) return; if (status.value == "pending") return;
loading.value = true; status.value = "pending";
sendQuery().then((res) => { sendQuery().then((res) => {
loadedItems.value = res.hits.hits; loadedItems.value = res.hits.hits;
showNoData.value = loadedItems.value?.length == 0; showNoData.value = loadedItems.value?.length == 0;
loading.value = false; status.value = "idle";
}); });
}; };
const resetForm = () => { const resetForm = () => {
clear();
searchTerm.value = ""; searchTerm.value = "";
loadedItems.value = []; route.query.q = null;
// loadedItems.value = [];
status.value = "idle";
showNoData.value = false; showNoData.value = false;
loading.value = false; history?.pushState({}, document?.title, route.path);
}; };
// وقتی کاربر کلیدی فشار میدهد // وقتی کاربر کلیدی فشار میدهد
@ -333,10 +364,10 @@ const resetForm = () => {
// تنظیم نوع جستجو // تنظیم نوع جستجو
const setType = (type: string) => { const setType = (type: string) => {
search_type.value = type; search_type.value = type;
loadedItems.value = []; // loadedItems.value = [];
offset.value = 0; offset.value = 0;
sendQuery().then((res) => { sendQuery().then((res) => {
loadedItems.value = res.hits.hits; loadedItems.value = res?.hits?.hits ?? [];
showNoData.value = loadedItems.value?.length == 0; showNoData.value = loadedItems.value?.length == 0;
}); });
}; };
@ -455,6 +486,60 @@ const onAddNewTitle = (subTitles) => {
showNoData.value = loadedItems.value?.length == 0; showNoData.value = loadedItems.value?.length == 0;
}); });
}; };
// Using the Intersection Observer version
const loadMore = async () => {
// const listElm = $event.target;
if (!hasMore.value) return;
// // window.innerHeight + window.scrollY >= document.body.offsetHeight - 100
// if (listElm.scrollTop + listElm.clientHeight >= listElm.scrollHeight) {
// status.value = "pending";
// mainState.pagination.offset =
// mainState.pagination.offset + mainState.pagination.limit;
// if (total.value > mainState.pagination.offset) {
// window.clearTimeout(isScrolling.value);
// isScrolling.value = setTimeout(() => {
return await sendQuery().then((res) => {
const hits = res?.hits?.hits ?? [];
if (hits.length == 0) hasMore.value = false;
else loadedItems.value.push(...hits);
// status.value = "success";
return res;
});
// }, 300);
// } else {
// toast.add({
// title: "کاربر محترم",
// description: "دیگر رکوردی جهت بارگزاری وجود ندارد.",
// color: "success",
// });
// status.value = "idle";
// }
// } else status.value = "idle";
};
const { isFetching } = useInfiniteScroll(loadMore, "searchInfiniteScroll");
// Add the scroll event listener when the component is mounted
// onMounted(() => {
// const targetElement = document.getElementById("search-list");
// if (targetElement) {
// targetElement.addEventListener("scroll", loadMore);
// }
// });
// // Remove the scroll event listener when the component is unmounted
// onUnmounted(() => {
// const targetElement = document.getElementById("search-list");
// if (targetElement) {
// targetElement.removeEventListener("scroll", loadMore);
// }
// });
// #endregion methods // #endregion methods
// #region components // #region components
@ -508,7 +593,7 @@ const SearchList = defineAsyncComponent(
<UInput <UInput
class="w-full focus:placeholder-gray-800" class="w-full focus:placeholder-gray-800"
v-model="searchTerm" v-model.trim="searchTerm"
v-model:open="open" v-model:open="open"
v-model:search-term="searchTerm" v-model:search-term="searchTerm"
placeholder="هوشمند جستجو کنید..." placeholder="هوشمند جستجو کنید..."
@ -520,7 +605,7 @@ const SearchList = defineAsyncComponent(
side: 'bottom', side: 'bottom',
sideOffset: 4, sideOffset: 4,
}" }"
:loading="loading" :loading="status == 'pending'"
highlight highlight
highlightOnHover highlightOnHover
@focus="open = true" @focus="open = true"
@ -554,7 +639,7 @@ const SearchList = defineAsyncComponent(
</div> </div>
<div <div
class="search-filter flex items-center my-3 justify-between" class="search-filter flex items-center my-3 justify-between"
v-if="!showNoData" v-if="loadedItems"
> >
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<!-- #region معنایی --> <!-- #region معنایی -->
@ -782,15 +867,10 @@ const SearchList = defineAsyncComponent(
</div> </div>
</div> </div>
<div <div
v-if="showNoData" v-if="!loadedItems"
class="flex justify-center flex-col items-center mt-10" class="flex justify-center flex-col items-center mt-10"
> >
<img <img fit="auto" quality="80" src="/img/haditha/logo.webp" />
fit="auto"
quality="80"
placeholder
src="/img/haditha/logo.webp"
/>
<div class="title"> <div class="title">
کاوش با کاوش با
<span class="badge-style mx-1">هوش مصنوعی</span> <span class="badge-style mx-1">هوش مصنوعی</span>
@ -798,33 +878,40 @@ const SearchList = defineAsyncComponent(
</div> </div>
</div> </div>
<!-- v-show="!showNoData" -->
<div <div
v-show="!showNoData"
class="search-box-container pb-0 bg-white flex justify-center" class="search-box-container pb-0 bg-white flex justify-center"
:class="{ 'pt-0': loadedItems == undefined }"
> >
<div class="search-list-contianer"> <div class="search-list-contianer">
<div class="total"> <div v-if="loadedItems" class="total">
<span>{{ total }}</span> <span>{{ total }}</span>
نتیجه نتیجه
</div> </div>
<!-- v-show="!loading" --> <!-- ref="el" -->
<div ref="el" class="search-list firefox-scrollbar"> <div
ref="searchInfiniteScroll"
id="searchInfiniteScroll"
class="search-list firefox-scrollbar"
:class="{ 'enable-scroll': loadedItems?.length }"
>
<search-list <search-list
no-data-text="نتیجه‌ای یافت نشد!" no-data-text="نتیجه‌ای یافت نشد!"
no-data-icon="/img/haditha/no-data.png" no-data-icon="/img/haditha/no-data.png"
:total="total" :total="total"
:list="loadedItems" :list="loadedItems"
:searchTerm="searchTerm" :searchTerm="searchTerm"
:requestStatus="status"
></search-list> ></search-list>
<!-- <no-data <no-data
class="h-full w-full flex flex-col justify-center items-center" class="h-full w-full flex flex-col justify-center items-center"
v-if="showNoData" v-if="loadedItems?.length == 0"
> >
<img fit="auto" quality="80" src="/img/haditha/no-data.png" /> <img fit="auto" quality="80" src="/img/haditha/no-data.png" />
<p class="no-data-text">"نتیجهای یافت نشد!</p> <p class="no-data-text">"نتیجهای یافت نشد!</p>
</no-data> --> </no-data>
</div> </div>
</div> </div>
</div> </div>
@ -876,6 +963,9 @@ const SearchList = defineAsyncComponent(
&.pb-0 { &.pb-0 {
padding-bottom: 0 !important; padding-bottom: 0 !important;
} }
&.pt-0 {
padding-top: 0 !important;
}
} }
.search-list-contianer { .search-list-contianer {
@ -898,9 +988,12 @@ const SearchList = defineAsyncComponent(
color: #b4c2cf; color: #b4c2cf;
} }
.search-list { .search-list {
padding: 1em 1.3em; &.enable-scroll {
height: calc(100dvh - 16em); padding: 1em 1.3em;
overflow-y: auto; height: calc(100dvh - 16em);
overflow-y: auto;
scroll-behavior: smooth;
}
&.hadithaFavorites { &.hadithaFavorites {
height: calc(100dvh - 8em); height: calc(100dvh - 8em);