Feat: Chapters to continue

master
masterhc 12 months ago
parent 0f05b96feb
commit 2f57b3d353

@ -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;

@ -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)
}

@ -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)

@ -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>

@ -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>

Loading…
Cancel
Save