Debug and refactor

This commit is contained in:
mustafa-rezae 2025-05-03 17:12:25 +03:30
parent 612fc1cc25
commit dc2d5cc460
7 changed files with 187 additions and 290 deletions

View File

@ -69,6 +69,7 @@ const goToLibraryShow = (item) => {
<!-- src="/img/haditha/sample-bgi.svg" -->
<p class="title">{{ item?._source?.title }}</p>
<p class="version">
جلد
{{ item?._source?.vol_title + item?._source?.vol_num }}
</p>
</ULink>

View File

@ -20,6 +20,7 @@ const props = defineProps({
default: "",
},
});
const emit = defineEmits(["on-bookmard-removed"]);
const router = useRouter();
const route = useRoute();
@ -76,6 +77,7 @@ const removeFromFavorites = async (item = {}, index = 0) => {
title: item?._source?.title,
};
httpService.postRequest(url, formData).then((res) => {
emit("on-bookmard-removed", index);
// this.updateListAnswer(index, "tbookmark", 0);
});
};
@ -104,7 +106,7 @@ const removeFromFavorites = async (item = {}, index = 0) => {
</a>
<UButton
v-if="route.name == 'hadithaFavorites'"
@click="removeFromFavorites(item)"
@click="removeFromFavorites(item, index)"
variant="ghost"
color="error"
class="copy-btn"
@ -133,6 +135,9 @@ const removeFromFavorites = async (item = {}, index = 0) => {
id: item?._source?.address.vol_id,
slug: hadithAddress(item),
},
query: {
page_num: item?._source?.address?.page_num,
},
}"
color="neutral"
variant="outline"

View File

@ -1,5 +1,8 @@
<script setup>
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,
@ -10,171 +13,98 @@ useHead({
title: `${import.meta.env.VITE_HADITH_PAGE_TITLE} | ذخیره ها`,
meta: [
{ name: "description", content: "کاوش با هوش مصنوعی در احادیث اسلامی" },
{
name: "msapplication-TileImage",
content: "/img/haditha/fav-icons/ms-icon-144x144.png",
},
{ name: "theme-color", content: "#ffffff" },
...headMetas,
],
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",
},
],
link: headLinks,
});
// #region refs
const loading = ref(false);
const el = ref(null);
const httpService = useNuxtApp()["$http"];
const offset = useState("offset", () => 0);
const total = useState("total", () => 0);
const loading = useState("loading", () => false);
const hasMore = useState("hasMore", () => true);
// #endregion refs
// #region reactive
const state = reactive({
// list: new Array(5).fill(0),
list: [],
counts: [],
totalCounts: [],
pagination: {
page: 1,
pages: 1,
offset: 0,
limit: 10,
// offset: 0,
limit: 500,
},
});
// #endregion reactive
// #region methods
const getCategories = async (dataType = "bookmark") => {
if (loading.value) return;
loading.value = true;
const getFavorites = async (dataType = "bookmark") => {
let url = repoUrl() + hadithaApi.favorite.getList;
url = url.replace("@data_type", dataType);
url = url.replace("@time_key", "all");
url = url.replace("@source", "main");
url = url.replace("@offset", state.pagination.offset);
url = url.replace("@offset", offset.value);
url = url.replace("@limit", state.pagination.limit);
url = url.replace("@q", "none");
return await httpService
.getRequest(url)
.then((res) => {
state.list = res.hits.hits;
state.totalCounts = res.hits.total.value;
loading.value = false;
// getCounts();
})
.catch((err) => {
console.info(err);
loading.value = false;
});
return await httpService.getRequest(url).then((res) => {
console.info(res);
total.value = res.hits?.total?.value ?? 0;
offset.value += state.pagination.limit;
return res;
});
};
// const getCounts = async () => {
// let url = repoUrl() + hadithaApi.favorite.getCounts;
// url = url.replace("@data_type", "bookmark");
const { data: favoriteList } = await useAsyncData(
"favorites",
() => getFavorites(),
{
transform: (data) => data.hits.hits,
}
);
// await httpService
// .getRequest(url)
// .then((res) => {
// state.totalCounts = res.hits.total.value;
// state.counts = res.aggregations.result.buckets;
// loading.value = false;
// })
// .catch((err) => {
// console.info(err);
// })
// .finally(() => {
// loading.value = false;
// });
// };
// #endregion methods
// Client-side infinite scroll
useInfiniteScroll(
el,
async () => {
if (!hasMore.value || loading.value) return;
// #region hooks
onMounted(() => {
getCategories();
});
// #endregion methods
loading.value = true;
try {
await getFavorites().then((res) => {
const hits = res?.hits?.hits ?? [];
if (hits.length) {
// Use spread operator to create new array reference
favoriteList.value = [...favoriteList.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,
}
);
const updateList = (index) => {
favoriteList.value.splice(index, 1);
};
const HadithaLayout = defineAsyncComponent(() =>
import("@haditha/layouts/HadithaLayout.vue")
@ -196,7 +126,7 @@ const SearchList = defineAsyncComponent(() =>
<div class="text-logo">
<div
v-if="state.list?.length"
v-show="favoriteList?.length"
class="search-box-container pb-0 flex justify-center"
>
<div class="search-list-contianer">
@ -205,18 +135,21 @@ const SearchList = defineAsyncComponent(() =>
نتیجه
</div>
<div ref="el" class="search-list firefox-scrollbar hadithaFavorites">
<div
ref="el"
class="search-list firefox-scrollbar hadithaFavorites"
>
<search-list
no-data-text="هنوز چیزی ذخیره نکرده‌اید!"
no-data-icon="/img/haditha/save.png"
:list="state.list"
:total="state.totalCounts"
:list="favoriteList"
@on-bookmard-removed="updateList"
></search-list>
</div>
</div>
</div>
<no-data
v-else
v-show="favoriteList?.length == 0"
class="h-full w-full flex flex-col justify-center items-center"
>
<img fit="auto" quality="80" src="/img/haditha/save.png" />
@ -238,6 +171,7 @@ const SearchList = defineAsyncComponent(() =>
}
.text-logo {
height: 100%;
padding-top: 4.5em;
position: relative;
}

View File

@ -61,25 +61,23 @@ useHead({
// const page_num = computed({
// // getter
// get() {
// return state.selectedItem?.[0]._source.address.page_num - 1 >= 1
// ? state.selectedItem?.[0]._source.address.page_num - 1
// return selectedItem.value?.[0]._source.address.page_num - 1 >= 1
// ? selectedItem.value?.[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 page_num = ref(1);
const loading = useState("loading", () => false);
const page_num = useState("page_num", () => 1);
const volumeInfo = useState("volumeInfo", () => {});
const state = reactive({
selectedItem: [] as Hit[],
treeItems: [
{
title: "فصل اول",
@ -132,72 +130,51 @@ const state = reactive({
{ title: "فصل چهارم", icon: "vscode-icons:file-type-vue" },
{ title: "فصل پنجم", icon: "vscode-icons:file-type-nuxt" },
],
volumeInfo: {},
});
const pageIsLessThanOne = computed(() => {
return page_num.value <= 1;
});
const pageIsBiggerThanTotal = computed(() => {
const page_count = state.volumeInfo._source?.page_count;
return page_num.value + 1 >= page_count;
});
// #endregion refs and reactives
// #region methods
const fetchData = async () => {
const fetchData = async (page_first = 0) => {
const pageNum = route.query.page_num;
const volId = route.params.id;
page_num.value = pageNum ?? page_first;
let url = repoUrl() + hadithaApi.library.show;
url = url.replace("@appname", "monir");
url = url.replace("@page_start", state.volumeInfo._source?.page_first);
url = url.replace("@page_len", 1);
url = url.replace("@vol_id", state.volumeInfo?._source.id);
// 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;
// }
httpService
.getRequest(url)
.then((res: HadithResponseModel) => {
state.selectedItem = <Hit[]>res.hits.hits;
})
.finally(() => (loading.value = false));
};
const fetchVolume = async () => {
if (loading.value) return;
loading.value = true;
const volId = route.params.id;
let url = repoUrl() + hadithaApi.library.get;
url = url.replace("@vol_id", volId);
url = url.replace("@page_start", page_num.value);
// fetch search list from backend(ssr)
// const { data, status, error, refresh, clear } =
// await useHadithaSearchComposable<HadithResponseModel>(url, {
// method: "get",
// });
return await httpService.getRequest(url).then((res) => {
volumeInfo.value = res.meta;
// if (status.value == "success") {
// state.volumeInfo = data.value;
// page_num.value = state.volumeInfo._source.page_first;
// }
// for first time and navigating from book list.
if (page_num.value == 0) page_num.value = volumeInfo.value.page_first;
httpService.getRequest(url).then((res: HadithResponseModel) => {
state.volumeInfo = res;
page_num.value = state.volumeInfo._source?.page_first;
fetchData();
return res;
});
};
fetchVolume();
// Server-side initial load
const { data: selectedItem } = await useAsyncData(
"libraryItem",
() => fetchData(),
{
transform: (data) => data.hits.hits,
// getCachedData: (key) => {
// return useNuxtApp().payload.data[key] || useNuxtApp().static.data[key];
// },
}
);
const pageIsLessThanOne = computed(() => {
return +page_num.value <= 1;
});
const pageIsBiggerThanTotal = computed(() => {
const page_count = volumeInfo.value?.page_count;
return +page_num.value + 1 >= page_count;
});
const goToTheLibrary = (type: string) => {
router.push({
@ -226,7 +203,7 @@ const handlePagination = (
loading.value = true;
const volId = route.params.id;
const page_count = state.volumeInfo._source?.page_count;
const page_count = volumeInfo.value?.page_count;
const isPageBiggerThanOne = +page_num.value + prevNextIndicator > 0;
const isPageLessThanTotal = +page_num.value + prevNextIndicator < page_count;
@ -248,9 +225,11 @@ const handlePagination = (
httpService
.getRequest(url)
.then((res: HadithResponseModel) => {
state.selectedItem = res.hits.hits;
selectedItem.value = res.hits.hits;
})
.finally(() => (loading.value = false));
.finally(() => {
loading.value = false;
});
};
const handlePageChange = () => {
handlePagination(1, +page_num.value);
@ -283,7 +262,7 @@ const prepareTreeData = (data) => {
});
};
getDataTree();
// getDataTree();
// #endregion methods
@ -330,7 +309,7 @@ const NavigationMenu = defineAsyncComponent(
<div class="flex items-center">
<UButton
@click="onSearch"
class="my-trailing-button search p-0"
class="my-trailing-button search p-0 close-btn text-[var(--ui-color-two)] hover:bg-gray-300"
icon="i-lucide-search"
variant=""
size="xl"
@ -339,7 +318,7 @@ const NavigationMenu = defineAsyncComponent(
<UButton
@click="goToTheLibrary"
class="my-trailing-button close p-0 ms-8"
class="my-trailing-button close p-0 ms-8 close-btn text-[var(--ui-color-two)] hover:bg-gray-300"
icon="i-lucide:x"
variant=""
size="xl"
@ -350,14 +329,11 @@ const NavigationMenu = defineAsyncComponent(
<div class="separator"></div>
<div class="page-content firefox-scrollbar py-14 p-2">
<template v-if="state.selectedItem?.length">
<p
v-for="(parag, index) in state.selectedItem"
:key="index"
v-html="parag?._source?.content"
></p>
</template>
<no-data v-else></no-data>
<p
v-for="(parag, index) in selectedItem"
:key="index"
v-html="parag?._source?.content"
></p>
</div>
<div class="body-footer">
@ -373,9 +349,7 @@ const NavigationMenu = defineAsyncComponent(
:disabled="pageIsLessThanOne"
/>
<div class="flex items-center">
<span class="total-pages">{{
state.volumeInfo._source?.page_count
}}</span>
<span class="total-pages">{{ volumeInfo?.page_count }}</span>
<span class="mx-2">/</span>
<UInput
:disabled="loading"
@ -634,6 +608,10 @@ const NavigationMenu = defineAsyncComponent(
cursor: pointer;
background-color: #eee;
}
&:disabled {
cursor: not-allowed;
}
}
}
}

View File

@ -22,13 +22,13 @@ useHead({
});
// #region refs
const el = useTemplateRef<HTMLElement>("el");
const httpService = useNuxtApp()["$http"];
const total = ref(0);
const currentPage = useState("currentPage", () => 0);
// Client-side state
const loading = ref(false);
const hasMore = ref(true);
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
@ -42,16 +42,16 @@ const state = reactive({
// #region methods
const getLibraryList = async (dataType = "bookmark") => {
const getLibraryList = async () => {
let url = repoUrl() + hadithaApi.library.list;
url = url.replace("@field_collapsed", "normal");
url = url.replace("@offset", currentPage.value);
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;
currentPage.value += state.pagination.limit;
total.value = res.hits?.total?.value ?? 0;
offset.value += state.pagination.limit;
return res;
});
@ -60,32 +60,9 @@ const getLibraryList = async (dataType = "bookmark") => {
// Server-side initial load
const { data: loadedItems } = await useAsyncData(
"libraryList",
async () => {
if (loading.value) return;
loading.value = true;
let url = repoUrl() + hadithaApi.library.list;
url = url.replace("@field_collapsed", "normal");
url = url.replace("@offset", currentPage.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;
currentPage.value += state.pagination.limit;
return res;
})
.finally(() => {
loading.value = false;
});
},
() => getLibraryList(),
{
transform: (data) => data.hits.hits,
getCachedData: (key) => {
return useNuxtApp().payload.data[key] || useNuxtApp().static.data[key];
},
}
);
@ -97,16 +74,21 @@ useInfiniteScroll(
loading.value = true;
try {
// const nextPage = page.value + 1;
await getLibraryList().then((res) => {
const hits = res.hits.hits;
const hits = res?.hits?.hits ?? [];
if (hits.length) {
loadedItems.value.push(...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;
}
@ -144,7 +126,6 @@ const CardList = defineAsyncComponent(
>
<!-- Client-side loaded content -->
<card-list
v-if="loadedItems?.length"
no-data-text="هنوز چیزی ذخیره نکرده‌اید!"
no-data-icon="/img/haditha/no-data.png"
:list="loadedItems"

View File

@ -269,6 +269,9 @@ const NavigationMenu = defineAsyncComponent(
id: selectedItem?._source?.address.vol_id,
slug: hadithAddress,
},
query: {
page_num: selectedItem?._source?.address?.page_num,
},
}"
color="neutral"
variant="ghost"

View File

@ -167,7 +167,7 @@ const backgroundImageStyle = computed(() => {
// });
let optimizedImageUrl = "/img/haditha/background.webp";
if (showNoData.value) {
if (!showNoData.value) {
// optimizedImageUrl = img("/img/haditha/sub-header-bgi.webp", {
// quality: 80,
// });
@ -231,8 +231,6 @@ const sendQuery = async (payload = {}) => {
// if (route.query.f_aik?.length) url += `&f_aik=${route.query.f_aik}`;
}
console.info(url);
return await httpService.postRequest(url, payload).then((res) => {
total.value = res.hits.total.value ?? 0;
offset.value += mainState.pagination.limit;
@ -249,11 +247,8 @@ const sendQuery = async (payload = {}) => {
// Server-side initial load
const { data: loadedItems } = await useAsyncData("search", () => sendQuery(), {
transform: (data) => data.hits.hits,
getCachedData: (key) => {
return useNuxtApp().payload.data[key] || useNuxtApp().static.data[key];
},
});
showNoData.value = Boolean(loadedItems.value?.length);
showNoData.value = loadedItems.value?.length == 0;
const onBeforeSendQuery = (from) => {
if (loading.value) return;
@ -270,11 +265,12 @@ const onBeforeSendQuery = (from) => {
if (searchTerm.value?.length) {
sendQuery().then((res) => {
loadedItems.value = res.hits.hits;
showNoData.value = Boolean(loadedItems.value?.length);
showNoData.value = loadedItems.value?.length == 0;
loading.value = false;
});
} else {
searchTerm.value = "";
loading.value = false;
loadedItems.value = [];
showNoData.value = false;
loading.value = false;
@ -309,19 +305,17 @@ const onSearchButtonClick = () => {
if (loading.value) return;
loading.value = true;
// showclearButton.value = true;
if (searchTerm.value?.length) {
searchTerm.value = "";
loadedItems.value = [];
showNoData.value = false;
sendQuery().then((res) => {
loadedItems.value = res.hits.hits;
showNoData.value = loadedItems.value?.length == 0;
loading.value = false;
} else {
sendQuery().then((res) => {
loadedItems.value = res.hits.hits;
showNoData.value = Boolean(loadedItems.value?.length);
loading.value = false;
});
}
});
};
const resetForm = () => {
searchTerm.value = "";
loadedItems.value = [];
showNoData.value = false;
loading.value = false;
};
// وقتی کاربر کلیدی فشار میدهد
@ -343,7 +337,7 @@ const setType = (type: string) => {
offset.value = 0;
sendQuery().then((res) => {
loadedItems.value = res.hits.hits;
showNoData.value = Boolean(loadedItems.value?.length);
showNoData.value = loadedItems.value?.length == 0;
});
};
// const setKey = (type: string) => {
@ -384,7 +378,7 @@ const onTypeSelectChanged = (value: string) => {
sendQuery().then((res) => {
loadedItems.value = res.hits.hits;
showNoData.value = Boolean(loadedItems.value?.length);
showNoData.value = loadedItems.value?.length == 0;
});
};
@ -438,7 +432,7 @@ const prepareSynonym = () => {
const onUpdateSwitch = () => {
sendQuery(prepareSynonym()).then((res) => {
loadedItems.value = res.hits.hits;
showNoData.value = Boolean(loadedItems.value?.length);
showNoData.value = loadedItems.value?.length == 0;
});
};
const onUpdateSubTitle = (subTitle) => {
@ -446,7 +440,7 @@ const onUpdateSubTitle = (subTitle) => {
sendQuery(prepareSynonym()).then((res) => {
loadedItems.value = res.hits.hits;
showNoData.value = Boolean(loadedItems.value?.length);
showNoData.value = loadedItems.value?.length == 0;
});
};
const onAddNewTitle = (subTitles) => {
@ -458,7 +452,7 @@ const onAddNewTitle = (subTitles) => {
sendQuery(prepareSynonym()).then((res) => {
loadedItems.value = res.hits.hits;
showNoData.value = Boolean(loadedItems.value?.length);
showNoData.value = loadedItems.value?.length == 0;
});
};
// #endregion methods
@ -541,20 +535,26 @@ const SearchList = defineAsyncComponent(
</div>
<!-- 'similar-mode': route.query.f_aik?.length, -->
<UButton
class="my-trailing-button"
:class="{
'close-mode': searchTerm.length,
}"
@click.prevent="onSearchButtonClick"
:icon="searchButtonIcon"
v-if="searchTerm.length"
class="my-trailing-button close-mode"
@click.prevent="resetForm"
icon="i-lucide-x"
>
<!-- <UIcon name="i-lucide-search" /> -->
</UButton>
<UButton
v-else
class="my-trailing-button"
icon="i-haditha-search"
>
<!-- @click.prevent="onSearchButtonClick" -->
<!-- <UIcon name="i-lucide-search" /> -->
</UButton>
<!-- </client-only> -->
</div>
<div
class="search-filter flex items-center my-3 justify-between"
v-if="showNoData"
v-if="!showNoData"
>
<div class="flex items-center space-x-2">
<!-- #region معنایی -->
@ -782,7 +782,7 @@ const SearchList = defineAsyncComponent(
</div>
</div>
<div
v-if="!showNoData"
v-if="showNoData"
class="flex justify-center flex-col items-center mt-10"
>
<img
@ -799,7 +799,7 @@ const SearchList = defineAsyncComponent(
</div>
<div
v-show="showNoData"
v-show="!showNoData"
class="search-box-container pb-0 bg-white flex justify-center"
>
<div class="search-list-contianer">
@ -826,11 +826,6 @@ const SearchList = defineAsyncComponent(
<p class="no-data-text">"نتیجهای یافت نشد!</p>
</no-data> -->
</div>
<!-- <the-content-loading
v-show="loading"
class="absolute-positioning"
></the-content-loading> -->
</div>
</div>
</div>