11<script setup lang="ts">
22import EpisodeItem from " ~/components/items/EpisodeItem.vue" ;
3+ import type {EpisodeWithProgression } from " ~/utils/types" ;
34
45definePageMeta ({
56 layout: " navigation" ,
@@ -10,17 +11,31 @@ definePageMeta({
1011
1112const id = useRoute ().params .id ;
1213const serverUrl = useLocalStorage (" serverUrl" , " " );
13- const {data : webtoon} = await useAsyncData <Webtoon >(" webtoon" , () => $fetch (` ${serverUrl .value }/webtoons/${id } ` ), {
14+ const {data : webtoon} = await useAsyncData <Webtoon >(` webtoon-${ id } ` , () => $fetch (` ${serverUrl .value }/webtoons/${id } ` ), {
1415 server: false ,
1516});
16- const {data : episodes} = await useAsyncData <Episode []>(" episodes" , () => $fetch (` ${serverUrl .value }/webtoons/${id }/episodes ` ), {
17+ const {data : episodes} = await useAsyncData <EpisodeWithProgression []>(` episodes-${ id } ` , () => $fetch (` ${serverUrl .value }/webtoons/${id }/episodes ` ), {
1718 server: false ,
1819});
20+ const token = useCookie (" token" ).value ;
21+ const {data : progressions} = await useAsyncData <Progression []>(` progressions-${id } ` , async () => {
22+ if (! token ) return [];
23+ try {
24+ return await $fetch (` ${serverUrl .value }/user/progression/webtoon/${id } ` , {
25+ headers: {Authorization: ` Bearer ${token } ` }
26+ });
27+ }catch {
28+ return [];
29+ }
30+ }, {server: false });
1931
20- const displayCount = ref (50 );
32+ const displayCount = ref (15 );
2133const displayedEpisodes = computed (() => {
2234 if (! episodes .value )
2335 return [];
36+ episodes .value .forEach ((episode ) => {
37+ episode .progression = progressions .value ?.find ((progression ) => progression .episodeId === episode .id )?.progression ?? 0 ;
38+ });
2439 if (isIncreasing .value )
2540 return episodes .value .slice (- displayCount .value ).reverse ();
2641 return episodes .value .slice (0 , displayCount .value );
@@ -35,8 +50,37 @@ function toggleIncreasing(){
3550function resume(){
3651 // TODO: Implement resume
3752}
53+
54+ const hasMore = computed (() => {
55+ return episodes .value && displayCount .value < episodes .value .length ;
56+ });
57+
58+ const sentinel = ref <HTMLElement | null >(null );
59+ let observer: IntersectionObserver ;
60+
61+ onMounted (() => {
62+ observer = new IntersectionObserver ((entries ) => {
63+ if (entries [0 ].isIntersecting && hasMore .value )
64+ displayCount .value += 15 ;
65+ }, {
66+ root: document .querySelector (" .h-dvh" ),
67+ rootMargin: " 0px 0px 100px 0px"
68+ });
69+
70+ watchEffect (() => {
71+ if (sentinel .value && hasMore .value )
72+ observer .observe (sentinel .value );
73+ else if (sentinel .value )
74+ observer .unobserve (sentinel .value );
75+ });
76+
77+ onBeforeUnmount (() => {
78+ observer .disconnect ();
79+ });
80+ });
3881 </script >
3982
83+
4084<template >
4185 <UiScrollArea class =" h-dvh" >
4286 <div class =" flex flex-col" >
@@ -54,6 +98,7 @@ function resume(){
5498 </div >
5599 </div >
56100 <EpisodeItem v-for =" (episode, i) in displayedEpisodes" :key =" i" :episode =" episode" />
101+ <div v-if =" hasMore" ref =" sentinel" class =" h-1 w-full" />
57102 </div >
58103 </div >
59104 </UiScrollArea >
0 commit comments