diff --git a/controllers/api.js b/controllers/api.js index e458745..6262cd3 100644 --- a/controllers/api.js +++ b/controllers/api.js @@ -507,6 +507,123 @@ exports.chapterLastRead = async (req, res)=> let data = chaps[0].lastImageRead; res.render('data.ejs', {data}); } + +exports.continue = async (req, res)=> +{ + async function getChaptersToContinue() + { + const favs = await FavoriteModel.find(); + const chaps = await ChapterModel.find(); + const favMap = new Map(favs.map(fav => [`${fav.title}_${fav.scanlator}`, {latestComplete: null, incomplete: []}])); + const favoritesNotYetStarted = []; + // Filter chapters directly into their respective categories + chaps.forEach(chap => + { + const key = `${chap.title}_${chap.scanlator}`; + if (favMap.has(key)) + { + const group = favMap.get(key); + if (chap.completely) { + if (!group.latestComplete || chap.chapterNum > group.latestComplete.chapterNum) group.latestComplete = chap; + } + else + { + group.incomplete.push(chap); + } + } + }); + favs.forEach((element) => + { + const {title, scanlator} = element; + if (!chaps.some(chap => chap.title == title && chap.scanlator == scanlator)) { + favoritesNotYetStarted.push(element); + } + }); + + // Compile the results + let results = Array.from(favMap.values()).flatMap(group => + group.latestComplete ? [group.latestComplete, ...group.incomplete] : group.incomplete + ); + + results = await Promise.all(results.map(async (chap) => + { + if (!chap.completely) return chap; + let next = await getNextChapter(chap); + return next ? next : null; + })); + + results = results.filter(chap => chap !== null); + // Filter to find unique chapters that are not completed + const uniqueContinuationChapters = chaps.filter(chap => + { + return !chap.completely && !results.some(f => f.scanlator === chap.scanlator && f.title === chap.title) + }); + + const chaptersToStart = []; + for(var i = 0; i< favoritesNotYetStarted.length; i++) + { + const {title, link, scanlator} = favoritesNotYetStarted[i]; + const {List, img} = await new Manga(scanlator, link, title).get(); + let aux = {byPass:true, chapterNum:0}; + const chap = await new Chapter(scanlator, List[0].link, title, List[0].num).get(); + const imgReadOutOfTotal = 0+'/'+(chap.List.length-1) + aux = {...aux, ...chap, mangaCoverImg:img, imgReadOutOfTotal,lastImageRead:chap.List[0]} + chaptersToStart.push(aux); + } + + return [...results, ...uniqueContinuationChapters, ...chaptersToStart]; + } + async function getNextChapter (chapter) + { + const {scanlator, link, title, chapterNum, _id } = chapter; + const manga = await new Manga(scanlator, link, title).get(); + if((chapterNum+1)>manga.latestChap) return false; + const chapLink = manga.List.filter(item=>item.num == (chapterNum+1))[0].link + const nextChapter = await new Chapter(scanlator, chapLink, title, (chapterNum+1)).get(); + let index = nextChapter.List.indexOf(chapter.lastImageRead) + const imgReadOutOfTotal = (index !=-1 ? index:0)+'/'+(nextChapter.List.length-1) + return { + ...nextChapter, + _id, + mangaCoverImg:manga.img, + chapterNum:nextChapter.chNum, + lastImageRead:nextChapter.List[0], + completely:false, + imgReadOutOfTotal, + byPass:true + }; + } + + const chaptersToContinue = await getChaptersToContinue(); + chaptersToContinue.sort((a, b) => { + if (a.chapterNum === 0) return 1; + if (b.chapterNum === 0) return -1; + return a.chapterNum - b.chapterNum; + }); + for(let i = 0; i<chaptersToContinue.length; i++) + { + if(chaptersToContinue[i].byPass) continue + if(!chaptersToContinue[i].mangaLink) + { + const {scanlator, link, title, chapterNum} = chaptersToContinue[i]; + const {List, img} = await new Manga(scanlator, link, title).get(); + if(!List.some(item=>item.num == chapterNum)) return; + let chapter = await new Chapter(scanlator,List[chapterNum-1].link, title,chapterNum).get(); + chaptersToContinue[i].link = chapter.link; + let index = chapter.List.indexOf(chaptersToContinue[i].lastImageRead) + const imgReadOutOfTotal = (index !=-1 ? index:0)+'/'+(chapter.List.length-1) + let aux = {...chaptersToContinue[i]._doc, mangaCoverImg:img, imgReadOutOfTotal} + chaptersToContinue[i] = aux; + } + } + res.render('chaptersToContinue.ejs', {data:chaptersToContinue}); +} +exports.removeChapter = async (req, res)=> +{ + if(!isValidID(req.params.id)) return res.sendStatus(404) + const chapter = await ChapterModel.findOneAndDelete({_id:req.params.id}); + res.render('empty.ejs'); +} exports.dashboard = async (req, res)=> { let data = 0; diff --git a/public/css/home.css b/public/css/home.css index 9c10659..72d959b 100644 --- a/public/css/home.css +++ b/public/css/home.css @@ -1020,7 +1020,6 @@ button:hover { display: flex; flex-direction: row; - gap: 1em; transition: transform 0.5s ease; } .hideOverflow @@ -1028,6 +1027,10 @@ button:hover width: 90%; overflow: hidden; } +.chapterCardWrapper +{ + margin-left:1em; +} .chapterCard { background:var(--bgBetter); @@ -1041,11 +1044,12 @@ button:hover .chapterCardOverlay { cursor: pointer; - + top:-1em } .chapterInfo { width: 100%; + margin-top: 2em; } .imgContainer { @@ -1064,16 +1068,12 @@ button:hover width:100%; object-fit:cover; } -.removeChapter:hover -{ - color:var(--purple-heart-900) -} .removeChapter { position:relative; color:var(--dropped); - top:2%; - left:95%; + top: 1em; + left: 13em; z-index: 5; } .continueTitle @@ -1101,11 +1101,15 @@ button:hover overflow: hidden; position: absolute; left: 80%; - bottom: 5%; + bottom: 15%; } .mangaCoverImg { width: 100%; height: 100%; object-fit: fill; +} +.accentColor +{ + color:var(--accent) } \ No newline at end of file diff --git a/routes/routes.js b/routes/routes.js index 4dfb0b3..0e720fa 100644 --- a/routes/routes.js +++ b/routes/routes.js @@ -12,10 +12,12 @@ module.exports = (worker)=> .post(api.search); router.route('/searchbar').get(api.searchBar); router.route('/recommended').get(api.recommended); + router.route('/continue').get(api.continue); router.route('/favorites').get(api.favorites); router.route('/bookmark/:scanlator/:title/:idorLink?').post(api.bookmark(worker)); router.route('/manga/:scanlator/:link/:title').get(api.manga); router.route('/chapter/:scanlator/:link/:title/:chapter').get(api.chapter); + router.route('/removeChapter/:id').get(api.removeChapter) router.route('/chapternavinfo/:scanlator/:mangaLink/:title/:chapter').get(api.chapterNavInfo); router.route('/dashboard').get(api.dashboard); router.route('/login').get(api.loginPage) diff --git a/views/chaptersToContinue.ejs b/views/chaptersToContinue.ejs new file mode 100644 index 0000000..f3c222e --- /dev/null +++ b/views/chaptersToContinue.ejs @@ -0,0 +1,153 @@ +<% if(data){ %> + <h1 class="continueTitle"> Continue</h1> + <div class="chapterContainer"> + <div class="hideOverflow"> + <div class="boundry"> + <% for(let chapter of data){%> + <div class="chapterCardWrapper" id="chapterCard_<%= chapter._id %>"> + <%if(chapter._id){ %> + <span + class="material-symbols-outlined removeChapter" + hx-get="/removeChapter/<%= chapter._id %>" + hx-trigger="click" + hx-target="#chapterCard_<%= chapter._id %>" + > + close + </span> + <%} else {%> + <span + class="material-symbols-outlined removeChapter accentColor" + hx-get="/manga/<%= chapter.scanlator%>/<%=chapter.mangaLink %>/<%=chapter.title %>" + hx-trigger="click" + hx-target="#container" + > + arrow_outward + </span> + <%} %> + <div + class="card__overlay chapterCardOverlay" + hx-get="/chapter/<%= chapter.scanlator %>/<%=chapter.link?encodeURIComponent(chapter.link):'nolink'%>/<%= chapter.title %>/<%= chapter.chapterNum %> " + hx-target="#container" + hx-indicator=".progress" + hx-trigger="click" + > + <div class="chapterCard" id="chapterCard_<%= chapter._id %> "> + <div class="imgContainer"> + <img class="chapterImage" src="<%= chapter.lastImageRead %>"/> + </div> + <div class="chapterInfo"> + + <h3 class="title"><%= chapter.title %> </h3> + <h4 class="chapter"> Chapter • <%= chapter.chapterNum %> </h4> + <h5 class="at"> Read • <%= chapter.imgReadOutOfTotal %> </h5> + <div class="mangaImageContainer"> + <img class="mangaCoverImg" src="<%= chapter.mangaCoverImg %> "/> + </div> + </div> + </div> + </div> + </div> + <% } %> + </div> + </div> + </div> + <div class="buttonContainer" id="buttonContainerchapters"> + <button class="prev-btn" onclick="handlePrev_chapters()"><span class="material-symbols-outlined chevron"> + chevron_left + </span></button> + <button class="next-btn" onclick="handleNext_chapters()"><span class="material-symbols-outlined chevron"> + chevron_right + </span></button> + </div> +<% } %> + +<script> + clearVariables_chapters(); + + function clearVariables_chapters() + { + ['cardsContainer_chapters', + 'cardsAmount_chapters', + 'cardWidth_chapters', + 'currentPosition_chapters', + 'maxPosition_chapters', + 'imageAmount_chapters', + 'imageOffSetWith_chapters', + 'carouselWidth_chapters', + 'gap_chapters', + 'cards_chapters', + 'lastCard_chapters', + 'lastCardBoundingRect_chapters', + 'nextButtonBoudingRect_chapters'].forEach((variable) => + { + if (window.hasOwnProperty(variable)) + { + delete window[variable]; + } + }); + } + try { + var cardAmount_chapters = document.querySelectorAll('.chapterCard').length - 3; + var cardWidth_chapters = document.querySelector('.chapterCard').offsetWidth; + var currentPosition_chapters = 0; + var maxPosition_chapters = - cardAmount_chapters; + + + } catch (error) { + clearVariables_chapters(); + var cardAmount_chapters = document.querySelectorAll('.chapterCard').length - 3; + var cardWidth_chapters = document.querySelector('.chapterCard').offsetWidth; + var currentPosition_chapters = 0; + var maxPosition_chapters = - cardAmount_chapters; + + } + function needsChevrons_chapters() + { + let cardsContainer_chapters = document.querySelectorAll('.boundry')[0] + let gap_chapters = parseFloat(window.getComputedStyle(cardsContainer_chapters).gap); + let imageAmount_chapters = document.querySelectorAll('.chapterImage').length; + let imageOffSetWith_chapters = document.querySelectorAll('.chapterImage')[0].offsetWidth+gap_chapters + let carouselWidth_chapters = document.getElementById('buttonContainerchapters').offsetWidth; + // console.log('Chevron check', carouselWidth_chapters,(imageAmount_chapters * imageOffSetWith_chapters),(imageAmount_chapters * imageOffSetWith_chapters) < carouselWidth_chapters) + if ((imageAmount_chapters * imageOffSetWith_chapters) < carouselWidth_chapters) + { + return document.getElementById('buttonContainerchapters').classList.add('hidden'); + } + document.getElementById('buttonContainerchapters').classList.remove('hidden'); + } + + // Hiding button container if all cards fit in view + window.addEventListener('resize', (event) => + { + needsChevrons_chapters(); + }); + + + // Function to handle previous button click + function handlePrev_chapters() + { + let cardsContainer_chapters = document.querySelector('.boundry'); + if (currentPosition_chapters == 0) return; + currentPosition_chapters++; + cardsContainer_chapters.style.transform = `translateX(${currentPosition_chapters*cardWidth_chapters}px)`; + } + + // Function to handle next button click + function handleNext_chapters() + { + let cardsContainer_chapters = document.querySelector('.boundry'); + let cards_chapters = document.querySelectorAll('.chapterCard') + let lastCard_chapters = cards_chapters[cards_chapters.length-1]; + let lastCardBoundingRect_chapters = lastCard_chapters.getBoundingClientRect(); + let nextButtonBoudingRect_chapters = document.querySelector('#buttonContainerchapters .next-btn').getBoundingClientRect(); + if((lastCardBoundingRect_chapters.x+lastCardBoundingRect_chapters.width)<nextButtonBoudingRect_chapters.x) return; + if (currentPosition_chapters == maxPosition_chapters) return; + currentPosition_chapters--; + cardsContainer_chapters.style.transform = `translateX(${currentPosition_chapters*cardWidth_chapters}px)`; + cards_chapters = document.querySelectorAll('.chapterCard') + lastCard_chapters = cards_chapters[cards_chapters.length-1]; + lastCardBoundingRect_chapters = lastCard_chapters.getBoundingClientRect(); + nextButtonBoudingRect_chapters = document.querySelector('#buttonContainerchapters .next-btn').getBoundingClientRect(); + } + +</script> \ No newline at end of file diff --git a/views/empty.ejs b/views/empty.ejs new file mode 100644 index 0000000..e69de29 diff --git a/views/home.ejs b/views/home.ejs index cd98e68..dd05317 100644 --- a/views/home.ejs +++ b/views/home.ejs @@ -19,6 +19,7 @@ <div id="wrapper"> <%- include('navBar.ejs', {data, selected:true}) %> <div class="container" id="container" > + <div class="continueSection" hx-get="/continue" hx-trigger="load" hx-indicator=".progress"></div> <h2 id="RecommendTitle">Recommended</h2> <div class="recommended" hx-get="/recommended" hx-trigger="load" hx-indicator=".progress"></div> </div>