diff --git a/apis/hadithaApi.js b/apis/hadithaApi.js index c8c8d44..486f782 100644 --- a/apis/hadithaApi.js +++ b/apis/hadithaApi.js @@ -1,7 +1,7 @@ export default { search: { list: "repo/monir/search/@index_key/@search_type/@type_key/@listkey/@field_collapsed/@offset/@limit/@q=none", - show: "repo/public/get/byid/@index_key/@id", + show: "repo/public/get/byid/@index_key/@id/tbookmark", synonym: "synonym/get/words", prevNextHadith: "monir/next/@index_key/@vol_id/@parag_order/@step", getDataTree: "@appname/book/tree/@offset/@limit/@vol_id/@q" diff --git a/assets/haditha/font-icons/close-circle.svg b/assets/haditha/font-icons/close-circle.svg new file mode 100644 index 0000000..a92e4d6 --- /dev/null +++ b/assets/haditha/font-icons/close-circle.svg @@ -0,0 +1,5 @@ +<svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M0 16.7158C0 7.87926 7.16344 0.71582 16 0.71582C24.8366 0.71582 32 7.87926 32 16.7158C32 25.5524 24.8366 32.7158 16 32.7158C7.16344 32.7158 0 25.5524 0 16.7158Z" fill="#F0F1F4"/> +<path d="M11 21.7158L21 11.7158" stroke="#1B2132" stroke-linecap="round"/> +<path d="M11 11.7158L21 21.7158" stroke="#1B2132" stroke-linecap="round"/> +</svg> diff --git a/components/haditha/AutoComplation.vue b/components/haditha/AutoComplation.vue index c184877..3089975 100644 --- a/components/haditha/AutoComplation.vue +++ b/components/haditha/AutoComplation.vue @@ -57,6 +57,7 @@ const search_type = ref("normal"); const type_key = ref("hadith"); const typeModelValue = ref("normal"); const typeModelValueFa = ref(""); +// const showclearButton = ref(false); // If you want to share state across multiple components, // you can use the same key in useState. Nuxt will ensure @@ -168,17 +169,27 @@ const clearSimilar = () => { // const onUpdateModel = (newVal: boolean | InputMenuItem | any) => { // console.info("onUpdateModel", newVal); // }; +// const searchButtonIcon = computed(() => { +// return showclearButton.value ? "i-lucide-x" : "i-haditha-search"; +// }); + +// const handleSearchClearButton = ()=>{ +// showclearButton.value = true; +// sendQuery(); +// } const onKeyDown = () => { - clearTimeout(typingTimer.value); -}; -const onKeyUp = () => { - clearTimeout(typingTimer.value); - typingTimer.value = setTimeout(() => { - if (props.autoRedirection) sendQuery(); - }, doneTypingInterval.value); + // showclearButton.value = false; + // clearTimeout(typingTimer.value); }; +// const onKeyUp = () => { +// clearTimeout(typingTimer.value); +// typingTimer.value = setTimeout(() => { +// if (props.autoRedirection) sendQuery(); +// }, doneTypingInterval.value); +// }; + const setType = (type: string) => { search_type.value = type; sendQuery(); @@ -224,13 +235,14 @@ const sendQuery = async (payload = {}) => { return await httpService .postRequest(url, payload) .then((res) => { - // pass res and search query to the parent. emit("response-ready", { res: res, searchQuery: searchTerm.value, disableAutoRedirect: true, }); + // pass res and search query to the parent. + loading.value = false; // check if search term is not empty if (searchTerm.value) userSearchHistory.value.add(searchTerm.value); // Add the value to the Set @@ -238,6 +250,30 @@ const sendQuery = async (payload = {}) => { // close the history dropdown menu open.value = false; + // show clear button + // if (showclearButton.value) { + // searchTerm.value = ""; + + // emit("response-ready", { + // res: { + // hits:{ + // total:0, + // hits:[] + // } + // }, + // searchQuery: searchTerm.value, + // disableAutoRedirect: true, + // }); + // } else { + // emit("response-ready", { + // res: res, + // searchQuery: searchTerm.value, + // disableAutoRedirect: true, + // }); + // } + + // showclearButton.value = !showclearButton.value; + // store search phrase useStorage("searchPhrase", searchTerm.value); }) @@ -405,7 +441,6 @@ onMounted(() => { @blur="open = false" @change="sendQuery" @keydown="onKeyDown" - @keyup="onKeyUp" @keydown.enter="sendQuery" > <!-- @update:modelValue="onUpdateModel" --> @@ -417,6 +452,7 @@ onMounted(() => { @click.prevent="sendQuery" icon="i-haditha-search" > + <!-- // @click.prevent="handleSearchClearButton" // :icon="searchButtonIcon" --> <!-- <UIcon name="i-lucide-search" /> --> </UButton> <!-- </client-only> --> @@ -574,7 +610,7 @@ onMounted(() => { {{ state.phrase.label }} <UIcon v-if="search_type == 'phrase'" - @click.self="search_type = 'normal'" + @click.self="setType('normal')" name="i-haditha-close-bg-circle" size="20px" > @@ -765,6 +801,13 @@ onMounted(() => { ); */ } } + .close-mode { + background: #f0f1f4; + color: #000; /* رنگ خاکستری */ + &:hover { + background: #e0e0e0; /* رنگ خاکستری */ + } + } .haditha-search-input { z-index: 0; diff --git a/components/haditha/CardList.vue b/components/haditha/CardList.vue index 6e03d65..6273167 100644 --- a/components/haditha/CardList.vue +++ b/components/haditha/CardList.vue @@ -30,146 +30,89 @@ const goToLibraryShow = (item) => { </script> <template> - <div class="library-list-contianer"> - <div class="page-header flex items-center"> - <span class="title">کتابخانه</span> + <UCard + v-if="props.list?.length" + v-for="(item, index) in props.list" + :key="index" + variant="solid" + :ui="{ + root: 'ring ring-[white] divide-y divide-[var(--ui-border)] rounded-0 shadow-none bg-transparent library-list-item', + header: 'header', + body: 'sm:p-0 p-0 bg-transparent', + footer: 'footer', + }" + > + <!-- <template #header></template> --> + + <ULink + v-if="item?._source?.id" + :to="{ + name: 'hadithaLibraryShow', + params: { + id: item?._source?.id, + slug: item?._source?.title, + }, + query: { + page_first: item._source.page_first, + page_last: item._source.page_last, + page_count: item._source.page_count, + }, + }" + color="neutral" + variant="outline" + :ui="{ + leadingIcon: 'text-(--ui-primary)', + }" + > <img fit="auto" quality="80" placeholder - src="/img/haditha/haditha-title.svg" + src="/img/haditha/sample-bgi.svg" /> - </div> + <p class="title">{{ item?._source?.title }}</p> + <p class="version"> + {{ item?._source?.vol_title + item?._source?.vol_num }} + </p> + </ULink> - <div - class="library-list grid grid-cols-2 gap-x-15 gap-y-12 md:grid-cols-5 md:gap-x-28 md:gap-y-12 mx-6" - > - <UCard - class="mx-auto" - v-if="props.list.length" - v-for="(item, index) in props.list" - :key="index" - variant="solid" - :ui="{ - root: 'ring ring-[white] divide-y divide-[var(--ui-border)] rounded-0 shadow-none bg-transparent library-list-item', - header: 'header', - body: 'sm:p-0 p-0 bg-transparent', - footer: 'footer', - }" - > - <!-- <template #header></template> --> + <!-- <template #footer> </template> --> + </UCard> - <ULink - v-if="item?._source?.id" - :to="{ - name: 'hadithaLibraryShow', - params: { - id: item?._source?.id, - slug: item?._source?.title, - }, - query: { - page_first: item._source.page_first, - page_last: item._source.page_last, - page_count: item._source.page_count, - }, - }" - color="neutral" - variant="outline" - :ui="{ - leadingIcon: 'text-(--ui-primary)', - }" - > - <img - fit="auto" - quality="80" - placeholder - 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> - - <!-- <template #footer> </template> --> - </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> - </div> - </div> + <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> <style scoped> -.library-list-contianer { - margin-top: 10em; - max-width: 1200px; - width: 100%; - margin: 0 1em; - margin-right: auto; - margin-left: auto; - height: 100%; +.library-list-item { + width: 140; + height: 200; + border-radius: 8px; - .page-header { - margin-bottom: 2em; - .title { - margin-left: 0.4em; - font-family: IRANSansX; - 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; - /* margin: 0 24px; */ - - .library-list-item { - width: 140; - height: 200; - border-radius: 8px; - - .title { - margin-top: 0.7em; - font-family: IRANSansX; - font-weight: 400; - font-size: 13px; - line-height: 19.5px; - letter-spacing: 0%; - text-align: right; - color: #444444; - } - - .version { - font-family: IRANSansX; - font-weight: 400; - font-size: 10px; - line-height: 15px; - letter-spacing: 0%; - text-align: right; - color: #444444; - } - } - } - .no-data-text { + .title { + margin-top: 0.7em; font-family: IRANSansX; - font-weight: 300; - font-size: 16px; - line-height: 24px; + font-weight: 400; + font-size: 13px; + line-height: 19.5px; letter-spacing: 0%; - text-align: center; + text-align: right; + color: #444444; + } + + .version { + font-family: IRANSansX; + font-weight: 400; + font-size: 10px; + line-height: 15px; + letter-spacing: 0%; + text-align: right; + color: #444444; } @media screen and (max-width: 719.99px) { .library-list { diff --git a/components/haditha/NavigationMenu.vue b/components/haditha/NavigationMenu.vue index aea7672..6bc9c1d 100644 --- a/components/haditha/NavigationMenu.vue +++ b/components/haditha/NavigationMenu.vue @@ -1,6 +1,14 @@ <script setup lang="ts"> import { useAuthStore } from "@stores/authStore"; +// { +// label: "چت بات", +// icon: "i-haditha-chat-bot", +// to: "/haditha/chat-bot", +// slot: "chat-bot", +// class: "flex flex-col lg:flex-row justify-center items-center", +// }, + const items = ref([ { label: "حانه", @@ -16,13 +24,7 @@ const items = ref([ slot: "search", class: "flex flex-col lg:flex-row justify-center items-center", }, - { - label: "چت بات", - icon: "i-haditha-chat-bot", - to: "/haditha/chat-bot", - slot: "chat-bot", - class: "flex flex-col lg:flex-row justify-center items-center", - }, + { label: "کتابخانه", icon: "i-haditha-library", @@ -124,7 +126,7 @@ const leftItem = computed(() => [ const isMobile = ref(false); const rerenderNavigation = ref(1); -const { isAuthenticatedGetter } = useAuthStore(); +const { isAuthenticatedGetter, isRealUserGetter } = useAuthStore(); const setMenu = () => { if (isAuthenticatedGetter) { @@ -150,6 +152,9 @@ const setMenu = () => { setMenu(); +// if(!(isAuthenticatedGetter && isRealUserGetter)) +// items.value = items.value.filter((item) => item.to != "/haditha/favorites"); + onMounted(() => { if (window?.outerWidth < 991) { isMobile.value = true; @@ -161,7 +166,7 @@ onMounted(() => { <template> <div class="fixed bottom-2 lg:bottom-auto lg:top-2 right-0 left-0"> - <UContainer class="flex my-navbar mx-3 lg:mx-auto"> + <UContainer class="flex my-navbar mx-3 lg:mx-auto"> <!-- :disableHoverTrigge="isMobile" --> <UNavigationMenu :key="rerenderNavigation" diff --git a/components/haditha/search-page/SearchList.vue b/components/haditha/search-page/SearchList.vue index d909a49..8f491b5 100644 --- a/components/haditha/search-page/SearchList.vue +++ b/components/haditha/search-page/SearchList.vue @@ -1,4 +1,6 @@ <script setup> +import hadithaApi from "@haditha/apis/hadithaApi"; +const httpService = useNuxtApp()["$http"]; const props = defineProps({ list: { default() { @@ -16,6 +18,7 @@ const props = defineProps({ }, }); const router = useRouter(); +const route = useRoute(); // const modal = useModal(); // const isModalOpen = ref(false); @@ -23,11 +26,21 @@ const router = useRouter(); function openModal(selectedItem) { // modal.open(SearchShow, { title: "Welcome" }); // isModalOpen.value = true; + + const slug = selectedItem?._source?.content + .split(" ") + .filter((v, i) => i < 4) + .join("-"); + router.push({ name: "hadithaSearchShow", params: { - id: selectedItem._id, - slug: "no-slug", + id: selectedItem?._source?.id, + slug: slug, + }, + query: { + firstPage: 1, + page_count: props.total, }, }); } @@ -45,6 +58,20 @@ function openModal(selectedItem) { // const SearchShow = defineAsyncComponent(() => // import("@haditha/components/haditha/search-page/SearchShow.vue") // ); +const removeFromFavorites = async (item = {}, index = 0) => { + let url = repoUrl() + hadithaApi.favorite.deleteByRefid; + url = url.replace("{{data_type}}", "bookmark"); + url = url.replace("{{index_key}}", "dhparag"); + url = url.replace("{{ref_id}}", item._id); + + const formData = { + ref_id: item._id, + title: item?._source?.title, + }; + httpService.postRequest(url, formData).then((res) => { + // this.updateListAnswer(index, "tbookmark", 0); + }); +}; </script> <template> @@ -61,20 +88,43 @@ function openModal(selectedItem) { v-for="(item, index) in props.list" :key="index" > - <p @click="openModal(item)" class="from-person"> - {{ - item?._source?.meta?.hadith_masoum ?? - item?._source?.meta?.hadith_sanad ?? - "بدون عنوان" - }} - </p> - <p @click="openModal(item)" class="arabic-text"> + <div class="flex justify-between mt-4 mb-2"> + <a + :href="`/haditha/search/${item?._source?.id}/${ + item?._source?.meta?.hadith_masoum ?? + item?._source?.meta?.hadith_sanad + }`" + @click.prevent="openModal(item)" + class="from-person block" + > + {{ + item?._source?.meta?.hadith_masoum ?? + item?._source?.meta?.hadith_sanad + }} + </a> + <UButton + v-if="route.name == 'hadithaFavorites'" + @click="removeFromFavorites(item)" + variant="ghost" + color="error" + class="copy-btn" + label="حذف" + /> + </div> + + <a + @click.prevent="openModal(item)" + class="arabic-text block" + :href="`/haditha/search/${item?._source?.id}/${item?._source?.content_ar}`" + > {{ item?._source?.content_ar }} - </p> - <p - class="persian-text" + </a> + <a + @click.prevent="openModal(item)" + :href="`/haditha/search/${item?._source?.id}/${item?._source?.content}`" + class="persian-text block" v-html="item?.highlight?.['content.fa'] ?? item?._source?.content" - ></p> + ></a> <div class="flex justify-end"> <p class="reference"> {{ item?._source?.address?.vol_title }}، صفحه @@ -195,7 +245,7 @@ function openModal(selectedItem) { .reference { height: 24px; gap: 4px; - padding-top: 0.25em; /*4px*/ + padding-top: 0.25em; /*4px*/ padding-right: 0.5em; /*8px*/ padding-bottom: 0.25em; /*4px*/ padding-left: 0.5em; /*8px*/ @@ -224,7 +274,7 @@ function openModal(selectedItem) { font-family: IRANSansX; font-weight: 300; font-size: 1rem; - line-height: 1.5rem; /*24px*/ + line-height: 1.5rem; /*24px*/ letter-spacing: 0%; text-align: center; } @@ -241,7 +291,7 @@ function openModal(selectedItem) { box-shadow: 0px 8px 20px 0px #0000001a; background: #ffffff; width: 100%; - max-width: 720px; /*18px*/ + max-width: 720px; /*18px*/ border-radius: 16px; /*18px*/ gap: 8px; border-width: 0.3px; @@ -304,4 +354,28 @@ function openModal(selectedItem) { .modal-overlay { background: #00000033; } +.copy-btn { + padding: 0.2em 1em; + + /* width: 44px; */ + /* height: 24px; */ + gap: 4px; + border-radius: 6px; + border-width: 0.5px; + padding-top: 4px; + padding-right: 12px; + padding-bottom: 4px; + padding-left: 12px; + /* background: #ffffff; */ + /* border: 0.5px solid #d9d9d9; */ + + font-family: IRANSansX; + font-weight: 300; + font-size: 12px; + line-height: 18px; + letter-spacing: 0%; + text-align: right; + + /* color: #a71111; */ +} </style> diff --git a/pages/haditha/library/index.vue b/pages/haditha/library/index.vue index 6ce3f43..e5d7c52 100644 --- a/pages/haditha/library/index.vue +++ b/pages/haditha/library/index.vue @@ -1,5 +1,8 @@ -<script setup> +<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, @@ -10,153 +13,99 @@ 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 = useTemplateRef<HTMLElement>("el"); const httpService = useNuxtApp()["$http"]; - +const route = useRoute(); +const page = ref(Number(route.query.page) || 1); // #endregion refs // #region reactive const state = reactive({ - list: [], - counts: [], - totalCounts: [], - libraryList: new Array(20).fill(0), + pagination: { + offset: 0, + limit: 10, + page: 1, + pages: 1, + }, }); -// #endregion reactive // #region methods const getLibraryList = async (dataType = "bookmark") => { - if (loading.value) return; - - loading.value = true; - let url = repoUrl() + hadithaApi.library.list; url = url.replace("@field_collapsed", "normal"); - url = url.replace("@offset", 0); - url = url.replace("@limit", 20); + url = url.replace("@offset", state.pagination.offset); + url = url.replace("@limit", state.pagination.limit); url = url.replace("@q", "none"); - - return await httpService - .postRequest(url) - .then((res) => { - state.libraryList = res.hits.hits; - console.info(state.libraryList) - }) - .catch((err) => { - console.info(err); - loading.value = false; - }) - .finally(() => (loading.value = false)); -}; -// #endregion methods -// #region hooks -onMounted(() => { - getLibraryList(); -}); + return await httpService.postRequest(url); +}; + +// Server-side initial load +const { data: initialItems } = await useAsyncData( + "libraryList", + () => getLibraryList(), + { + transform: (data) => data.hits.hits, + getCachedData: (key) => { + return useNuxtApp().payload.data[key] || useNuxtApp().static.data[key]; + }, + watch: [page], + } +); + +// Client-side state +const loadedItems = ref([]); +const loading = ref(false); +const hasMore = ref(true); +const loader = ref(null); +const totalPages = ref(10); // Set based on your API response + +// Client-side infinite scroll +useInfiniteScroll( + el, + async () => { + if (!hasMore.value || loading.value) return; + + loading.value = true; + try { + // const nextPage = page.value + 1; + await getLibraryList().then((res) => { + const hits = res.hits.hits; + + if (hits.length) { + loadedItems.value.push(...hits); + state.pagination.offset += state.pagination.limit; + } else { + hasMore.value = false; + } + }); + } finally { + loading.value = false; + } + }, + { distance: 100 } +); // #endregion methods // components declaration -const HadithaLayout = defineAsyncComponent(() => - import("@haditha/layouts/HadithaLayout.vue") +const HadithaLayout = defineAsyncComponent( + () => import("@haditha/layouts/HadithaLayout.vue") ); -const NavigationMenu = defineAsyncComponent(() => - import("@haditha/components/haditha/NavigationMenu.vue") +const NavigationMenu = defineAsyncComponent( + () => import("@haditha/components/haditha/NavigationMenu.vue") ); -const CardList = defineAsyncComponent(() => - import("@haditha/components/haditha/CardList.vue") +const CardList = defineAsyncComponent( + () => import("@haditha/components/haditha/CardList.vue") ); </script> @@ -165,11 +114,30 @@ const CardList = defineAsyncComponent(() => <div class="search-box-container h-full flex flex-col justify-center"> <navigation-menu></navigation-menu> - <card-list - no-data-text="هنوز چیزی ذخیره نکردهاید!" - no-data-icon="/img/haditha/no-data.png" - :list="state.libraryList" - ></card-list> + <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 grid grid-cols-5 gap-x-28 gap-y-12"> + <!-- Client-side loaded content --> + <card-list + v-if="loadedItems.length" + no-data-text="هنوز چیزی ذخیره نکردهاید!" + no-data-icon="/img/haditha/no-data.png" + :list="loadedItems" + ></card-list> + + <!-- Server-rendered initial content --> + <card-list + v-else + no-data-text="هنوز چیزی ذخیره نکردهاید!" + no-data-icon="/img/haditha/no-data.png" + :list="initialItems" + ></card-list> + </div> + </div> </div> </HadithaLayout> </template> @@ -178,6 +146,44 @@ const CardList = defineAsyncComponent(() => .search-box-container { padding-top: 8.3em; background: #f7fffd; + + .library-list-contianer { + 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: IRANSansX; + 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: IRANSansX; + font-weight: 300; + font-size: 16px; + line-height: 24px; + letter-spacing: 0%; + text-align: center; + } + } } @media screen and (max-width: 719.99px) { diff --git a/pages/haditha/search/[id]/[slug]/index.vue b/pages/haditha/search/[id]/[slug]/index.vue index 952f81d..2220a3a 100644 --- a/pages/haditha/search/[id]/[slug]/index.vue +++ b/pages/haditha/search/[id]/[slug]/index.vue @@ -132,6 +132,7 @@ const route = useRoute(); const router = useRouter(); const loading = ref(false); const httpService = useNuxtApp()["$http"]; +const toast = useToast(); const state = reactive({ selectedItem: {} as HadithResponseShowModel, @@ -174,7 +175,13 @@ const goToTheChatbot = () => { }; const handleFavorite = async () => { - addToFavorites(state.selectedItem); + if (state.selectedItem?._source?.tbookmark) { + await removeFromFavorites(state.selectedItem); + state.selectedItem._source.tbookmark = false; + } else { + await addToFavorites(state.selectedItem); + state.selectedItem._source.tbookmark = true; + } // // add // if (!state.selectedItem._source.tbookmark) { @@ -196,6 +203,21 @@ const addToFavorites = async (item = {}) => { }; httpService.postRequest(url, formData).then((res) => { // this.updateListAnswer(index, "tbookmark", 1); + + try { + toast.add({ + title: "انجام شد.", + description: "به نشان شده ها افزوده شد", + color: "success", + }); + } catch (err) { + console.log(err.message); + toast.add({ + title: "انجام شد.", + description: "خطایی رخ داد.لطفا دوباره امتحان کنید.", + color: "error", + }); + } }); }; @@ -211,6 +233,21 @@ const removeFromFavorites = async (item = {}, index = 0) => { }; httpService.postRequest(url, formData).then((res) => { // this.updateListAnswer(index, "tbookmark", 0); + + try { + toast.add({ + title: "انجام شد.", + description: "از لیست نشان شده ها حذف شد", + color: "success", + }); + } catch (err) { + console.log(err.message); + toast.add({ + title: "انجام شد.", + description: "خطایی رخ داد.لطفا دوباره امتحان کنید.", + color: "error", + }); + } }); }; @@ -220,8 +257,8 @@ const handlePagination = (prevNextIndicator: string) => { let url = repoUrl() + hadithaApi.search.prevNextHadith; url = url.replace("@index_key", "dhparag"); - url = url.replace("@vol_id", state.selectedItem._source.address.vol_id); - url = url.replace("@parag_order", state.selectedItem._source.parag_order); + url = url.replace("@vol_id", state.selectedItem?._source?.address?.vol_id); + url = url.replace("@parag_order", state.selectedItem?._source?.parag_order); url = url.replace("@step", prevNextIndicator); httpService @@ -305,14 +342,19 @@ const handlePagination = (prevNextIndicator: string) => { <span class="section-title"> {{ state.selectedItem?._source?.meta?.hadith_masoum ?? - "بدون عنوان" + state.selectedItem?._source?.meta?.hadith_sanad }} </span> <UButton + v-if=" + state.selectedItem?._source?.meta?.hadith_masoum + ?.length || + state.selectedItem?._source?.meta?.hadith_sanad + " @click=" copyTextToClipboard( - state.selectedItem?._source?.meta?.hadith_masoum ?? '' + state.selectedItem?._source?.content_ar ?? '' ) " variant="ghost" @@ -320,8 +362,13 @@ const handlePagination = (prevNextIndicator: string) => { label="کپی" /> </div> - <div class="arabic-text"> - <p>بدون متن عربی</p> + <div + v-if="state.selectedItem?._source?.content_ar?.length" + class="arabic-text" + > + <p> + {{ state.selectedItem?._source?.content_ar ?? "" }} + </p> </div> </div> @@ -345,7 +392,7 @@ const handlePagination = (prevNextIndicator: string) => { <p class="from"> {{ state.selectedItem?._source?.meta?.hadith_masoum ?? - "بدون عنوان" + state.selectedItem?._source?.meta?.hadith_sanad }}: </p> <p @@ -356,13 +403,16 @@ const handlePagination = (prevNextIndicator: string) => { <div class="separator"></div> - <div class="text-description-section"> + <div + v-if="state.selectedItem?._source?.description?.length" + class="text-description-section" + > <div class="section-header"> <span class="section-title"> شرح </span> <UButton @click=" copyTextToClipboard( - state.selectedItem?._source?.content + state.selectedItem?._source?.description ) " variant="ghost" @@ -372,14 +422,17 @@ const handlePagination = (prevNextIndicator: string) => { </div> <p class="description-item" - v-html="state.selectedItem?._source?.content" + v-html="state.selectedItem?._source?.description" ></p> </div> <div class="separator"></div> <div class="text-description-section"> - <div class="text-sm mb-1"> + <div + v-if="state.selectedItem?._source?.ai_keywords?.length" + class="text-sm mb-1" + > <span class=""> کلیدواژگان: </span> <span class="me-1 text-gray-400 font-light" @@ -392,7 +445,10 @@ const handlePagination = (prevNextIndicator: string) => { </span> </div> - <div class="text-sm"> + <div + class="text-sm" + v-if="state.selectedItem?._source?.ai_classes?.length" + > <span class=""> دسته بندی ها: </span> <span class="me-1 text-gray-400 font-light" @@ -413,7 +469,7 @@ const handlePagination = (prevNextIndicator: string) => { <div class="body-footer"> <div class="mt-5 z-2"> - <div class="flex justify-between actions"> + <!-- <div class="flex justify-between actions"> <UButton disabled class="similar-btn" @@ -421,8 +477,8 @@ const handlePagination = (prevNextIndicator: string) => { label="مشابه" color="neutral" variant="outline" + @click.prevent="goToTheSearch('similar')" /> - <!-- @click.prevent="goToTheSearch('similar')" --> <UButton class="explore-btn" trailing-icon="i-haditha-explore" @@ -430,7 +486,7 @@ const handlePagination = (prevNextIndicator: string) => { variant="solid" @click.prevent="goToTheChatbot" /> - </div> + </div> --> <div class="flex justify-between pagination"> <UButton @click="handlePagination('-1')" diff --git a/pages/haditha/search/index.vue b/pages/haditha/search/index.vue index e4b7efa..a25b7a4 100644 --- a/pages/haditha/search/index.vue +++ b/pages/haditha/search/index.vue @@ -90,7 +90,7 @@ const SearchList = defineAsyncComponent(() => </div> <div v-if="searchQuery?.length == 0" - class="flex justify-center flex-col items-center" + class="flex justify-center flex-col items-center mt-10" > <img fit="auto" @@ -100,7 +100,7 @@ const SearchList = defineAsyncComponent(() => /> <div class="title"> کاوش با - <span class="badge-style"> هوش مصنوعی </span> + <span class="badge-style mx-1">هوش مصنوعی</span> در احادیث اسلامی </div> </div>