Feature: Read marker functionality in manga page.

master
masterhc 1 year ago
parent 7d884e91b9
commit 7c67c5b122

@ -4,7 +4,6 @@ const FavoriteModel = require('../models/favorite.js');
const ChapterModel = require('../models/readChapter.js');
const fs = require('fs');
const path = require('path');
const { isNullOrUndefined } = require('util');
exports.home = async (req,res)=>
{
@ -45,7 +44,6 @@ exports.search = async (req, res)=>
const scanlatorB = b.scanlator.toLowerCase();
return scanlatorA.localeCompare(scanlatorB);
})
console.log(data[1].Results)
res.render('searchResults.ejs', {data})
}
@ -118,6 +116,7 @@ exports.manga = async (req, res)=>
{
manga.List[i]['completely'] = chaptersRead[j].completely
manga.List[i]['Read'] = true;
manga.List[i]['_id'] = chaptersRead[j]._id;
}
}
}
@ -127,8 +126,13 @@ exports.manga = async (req, res)=>
const sortedChapters = chaptersRead.sort((a, b) => a.chapterNum - b.chapterNum);
const sortedAndFilteredChapters = sortedChapters.filter(item=> item.lastImageRead && !item.completely);
const lastChapterWithReadImages=sortedAndFilteredChapters.shift();
const chapterNumToContinue = getChapterNumToContinue(lastCompletelyRead, lastChapterWithReadImages);
manga.List[chapterNumToContinue-1]['continue'] = true;
let chapterNumToContinue = getChapterNumToContinue(lastCompletelyRead, lastChapterWithReadImages, manga.List);
if(!manga.List.some(item=> item.num==chapterNumToContinue)) chapterNumToContinue = Math.trunc(chapterNumToContinue);
let index = manga.List.indexOf(manga.List.filter(item=>
{
return item.num == chapterNumToContinue
})[0]);
manga.List[index]['continue'] = true;
}
catch (error)
{
@ -136,15 +140,19 @@ exports.manga = async (req, res)=>
}
res.render('mangaPage.ejs', {data:{...manga, scanlator}});
}
function getChapterNumToContinue(lastCompletelyRead, lastChapterWithReadImages)
function getChapterNumToContinue(lastCompletelyRead, lastChapterWithReadImages, List)
{
const firstIndex = List.indexOf(List.filter(item=>item.num == lastCompletelyRead.chapterNum)[0]);
if(lastCompletelyRead)
{
if(lastCompletelyRead.chapterNum < lastChapterWithReadImages.chapterNum)
{
return lastChapterWithReadImages.chapterNum;
}
return lastCompletelyRead.chapterNum+1;
const chNum = lastCompletelyRead.chapterNum+1;
const secondIndex = List.indexOf(List.filter(item=>item.num == chNum)[0]);
if(secondIndex - firstIndex > 1) return List[firstIndex + 1].num;
return chNum
}
return lastChapterWithReadImages.chapterNum;
}
@ -161,7 +169,7 @@ exports.chapter = async (req, res)=>
const latestChap = fs.readdirSync(`./public/Saved/${scanlator}/${title}`).length
const List = imgs.map(filename => {return `./Saved/${scanlator}/${title}/CH_${chapter}/${filename}`});
const mangaLink = await new Chapter(scanlator, link, title, chapter).getMangaLink();
return res.render('chapterPage.ejs', {data:{title,latestChap,scanlator, chapterNum:parseInt(chapter), mangaLink, List}});
return res.render('chapterPage.ejs', {data:{title,latestChap,scanlator, chapterNum:parseFloat(chapter), mangaLink, List}});
}
//If it isn't make sure there is no bad params being passed
const scanlatorExists = await new Modules(scanlator).exists();
@ -181,7 +189,7 @@ exports.chapter = async (req, res)=>
{
return res.redirect('/')
}
res.render('chapterPage.ejs', {data:{...manga, scanlator, chapterNum:parseInt(chapter)}});
res.render('chapterPage.ejs', {data:{...manga, scanlator, chapterNum:parseFloat(chapter)}});
}
exports.favorites = async (req, res)=>
@ -313,7 +321,7 @@ exports.chapterNavInfo = async (req, res)=>
if(!scanlatorExists) return res.sendStatus(404);
const scanlatorHasTitle = await new Modules(scanlator).titleExists(title);
if(!scanlatorHasTitle) return res.sendStatus(404);
let chapter = parseInt(req.params.chapter);
let chapter = parseFloat(req.params.chapter);
let manga;
try
{
@ -429,6 +437,8 @@ exports.errorPage = (req, res)=>
exports.chapterRead = async (req, res)=>
{
const cookieStr = req.headers.cookie;
if(!cookieStr) return res.sendStatus(404);
const {scanlator, title, chapter, link} = req.params;
const scanlatorExists = await new Modules(scanlator).exists();
if(!scanlatorExists) return res.sendStatus(404);
@ -439,13 +449,21 @@ exports.chapterRead = async (req, res)=>
if(chapter > latestChap || chapter < 0) return res.sendStatus(404);
const chap = new ChapterModel();
let chapterNum = chapter;
let chapterLink = List.filter(item=>item.num == chapterNum)[0].link;
let chapFromList = List.filter(item=>item.num == chapterNum)[0];
let chapterLink = chapFromList.link;
let chapterDate = chapFromList.date;
if(!chapterLink) return res.sendStatus(404);
const _chapter = await new Chapter(scanlator, chapterLink,title, chapter).get();
const _chapter = await new Chapter(scanlator, chapterLink,title, chapterNum).get();
let lastImageRead = _chapter.List[_chapter.List.length-1];
chap.lastImageRead = lastImageRead;
const chaps = await ChapterModel.find({scanlator, title, link, chapterNum});
if(chaps.length>0) return updateChapter(chaps[0],res, lastImageRead, true)
if(chaps.length>0) updateChapter(chaps[0], lastImageRead, true);
_chapter['num'] = _chapter.chNum;
_chapter['date'] = chapterDate;
_chapter['Read'] = true;
_chapter['_id'] = chap._id;
_chapter['completely'] = true;
res.render('chapterButton.ejs', {chapter:_chapter, scanlator, title, link, shortCut:false});
chap.scanlator = scanlator;
chap.title = title;
chap.link = link;
@ -453,12 +471,11 @@ exports.chapterRead = async (req, res)=>
chap.lastImageRead = lastImageRead;
chap.completely = true;
chap.save()
res.sendStatus(200);
}
async function updateChapter(chap, res, lastImageRead, completely=false)
async function updateChapter(chap,lastImageRead, completely=false,res)
{
await ChapterModel.findOneAndUpdate(chap._id,{completely, lastImageRead:lastImageRead}, {new:true});
res.sendStatus(200);
if(res) return res.sendStatus(200)
}
exports.chapterIncompleteRead = async (req,res)=>
{
@ -569,26 +586,7 @@ exports.continue = async (req, res)=>
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) => {
@ -614,11 +612,49 @@ exports.continue = async (req, res)=>
}
res.render('chaptersToContinue.ejs', {data:chaptersToContinue});
}
async function getNextChapter (chapter)
{
const {scanlator, link, title, _id } = chapter;
let chapterNum = chapter.chapterNum;
const manga = await new Manga(scanlator, link, title).get();
if((chapterNum+1)>manga.latestChap) return false;
if(!manga.List.some(item=>item.num == (chapterNum+1))) chapterNum = Math.trunc(chapterNum);
const chapterFromList = manga.List.filter(item=>item.num == (chapterNum+1))[0]
const {date, num} = chapterFromList;
const nextChapter = await new Chapter(scanlator, chapterFromList.link, title, (chapterNum+1)).get();
let index = chapter.lastImageRead?nextChapter.List.indexOf(chapter.lastImageRead):-1;
const imgReadOutOfTotal = (index !=-1 ? index:0)+'/'+(nextChapter.List.length-1)
return {
...nextChapter,
num,
date,
_id,
mangaCoverImg:manga.img,
chapterNum:nextChapter.chNum,
lastImageRead:nextChapter.List[0],
completely:false,
imgReadOutOfTotal,
byPass:true
};
}
exports.chapterToContinue = async (req, res)=>
{
const {scanlator, mangaLink, title} = req.params;
const chaptersRead = await ChapterModel.find({scanlator, title}).then(res=>{return res});
let sorted = chaptersRead.sort((a,b)=>{return b.chapterNum-a.chapterNum});
const newChapter = await getNextChapter({scanlator, link:mangaLink, title,chapterNum:parseFloat(sorted[0].chapterNum), _id:null});
res.render('chapterButton.ejs',{chapter:newChapter, scanlator, title,shortCut:true})
}
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');
const cookieStr = req.headers.cookie;
if(!cookieStr) return res.sendStatus(404);
if(!isValidID(req.params.id)) return res.sendStatus(404);
const {link, scanlator, title, chapterNum} = await ChapterModel.findOneAndDelete({_id:req.params.id}).then(deleted=>{return deleted});
const {List} = await new Manga(scanlator, link, title).get();
const chapter = List.filter(item=>item.num == chapterNum)[0];
res.render('chapterButton.ejs', {chapter, scanlator, title, shortCut:false});
}
exports.dashboard = async (req, res)=>
{

@ -396,6 +396,7 @@ button:hover
border-radius: 15px;
margin-left: 10%;
background-color: var(--lbg);
margin-top: 8em;
}
.infoCard > .infoText
{
@ -464,11 +465,21 @@ button:hover
border: 2px solid var(--border);
border-radius: 15px;
background-color: var(--lbg);
margin: 5px;
width: 130px;
height: 40px;
cursor: pointer;
position: relative;
top:-1.3em;
}
.chapterButtonWrapper
{
margin: 5px;
width: 140px;
height: 50px;
overflow: hidden;
}
.chapterButton:hover
{
border-color: var(--border-hover);
@ -486,7 +497,7 @@ button:hover
flex-direction: row;
align-items: center;
justify-content: center;
height:100%
height:100%;
}
.chapterButton > a >.buttonWrapper > div > h4 ,
.chapterButton > a >.buttonWrapper > div > h5
@ -499,7 +510,7 @@ button:hover
width:auto;
color: var(--purple-heart-400);
position: relative;
left: 0.7em;
left: 1em;
}
.readMarkerComplete
{
@ -1112,4 +1123,31 @@ button:hover
.accentColor
{
color:var(--accent)
}
}
.markAsRead
{
color: var(--text);
transform: scaleX(-1) rotate(90deg);
z-index: 2;
position: relative;
}
.markAsRead:hover
{
color:var(--accent);
}
.chapterSpacer
{
width: 1em;
height: 1em;
}
.hitbox
{
height:1.3em;
width:1.3em;
position: relative;
top:0.6em;
left:.4em;
z-index: 4;
}

@ -18,6 +18,7 @@ module.exports = (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('/chaptertocontinue/:scanlator/:mangaLink/:title/').get(api.chapterToContinue);
router.route('/chapternavinfo/:scanlator/:mangaLink/:title/:chapter').get(api.chapterNavInfo);
router.route('/dashboard').get(api.dashboard);
router.route('/login').get(api.loginPage)

@ -16,116 +16,6 @@ mongoose.connection.on('connected', ()=>{console.log('MongoDB - Connected')})
(async ()=>
{
// const manga = await new Manga('Reaper-scans', 'https://reaper-scans.com/manga/kagurabachi/','Kagurabachi').get();
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;
}
}
// console.log(chaptersToContinue)
const c = chaptersToContinue.filter(item=>item.title=='Kagurabachi').sort((a,b)=>a.chapterNum-b.chapterNum)
console.log(c)
// const chapter = await ChapterModel.findOneAndDelete({_id:'66251e66ab92b12ec150dad9'});
})();

@ -1,4 +1,4 @@
<span class="material-symbols-outlined bookmark" hx-post="/bookmark/<%=scanlator%>/<%=title%>/<%= link %>" hx-trigger="click" hx-target="this" hx-swap="outerHTML">
bookmark_add
bookmark
</span>

@ -1,3 +1,9 @@
<span class="material-symbols-outlined bookmark fill " hx-post="/bookmark/<%=scanlator%>/<%=title%>/<%= id %> " hx-trigger="click" hx-target="this"hx-swap="outerHTML">
<span
class="material-symbols-outlined bookmark fill "
hx-post="/bookmark/<%=scanlator%>/<%=title%>/<%= id %> "
hx-trigger="click"
hx-target="this"
hx-swap="outerHTML"
>
bookmark
</span>

@ -1,20 +1,43 @@
<div class="chapterButton">
<a href="/chapter/<%=scanlator %>/<%=encodeURIComponent(chapter.link)%>/<%= title %>/<%= chapter.num %>" >
<div class="buttonWrapper">
<% if(chapter.Read){ %>
<span class="material-symbols-outlined readMarker <%= chapter.completely?'readMarkerComplete fill':'' %> ">
<div
class="chapterButtonWrapper chapterButton_<%= (new String(chapter.num)).replaceAll('.', '_') %><%= shortCut?'shortCut':'' %>"
>
<div class="hitbox">
<% if(!chapter.Read){%>
<span
hx-post="/chapterRead/<%= scanlator %>/<%= encodeURIComponent(chapter.mangaLink) %>/<%= title %>/<%= chapter.num %>/"
hx-target=".chapterButton_<%= (new String(chapter.num)).replaceAll('.', '_') %>"
hx-swap="outerHTML"
class="material-symbols-outlined markAsRead"
>
new_label
</span>
<%} else { %>
<span
class="material-symbols-outlined readMarker <%= chapter.completely?'readMarkerComplete fill':'' %> "
hx-get="/removeChapter/<%= chapter._id %>"
hx-trigger="click"
hx-target=".chapterButton_<%= (new String(chapter.num)).replaceAll('.', '_') %>"
hx-swap="outerHTML"
>
beenhere
</span>
<% } %>
<div>
<h4 class="chapterNum">
Chapter <%= chapter.num %>
</h4>
<h5 class="date">
<%= chapter.date %>
</h5>
</span>
<% }%>
</div>
<div class="chapterButton" >
<a href="/chapter/<%=scanlator %>/<%=encodeURIComponent(chapter.link)%>/<%= title %>/<%= chapter.num %>" >
<div class="buttonWrapper">
<div class="chapterSpacer readMarker_<%= scanlator.concat(title) %>"></div>
<div>
<h4 class="chapterNum">
Chapter <%= chapter.num %>
</h4>
<h5 class="date">
<%= chapter.date %>
</h5>
</div>
</div>
</div>
</a>
</a>
</div>
</div>

@ -1,17 +1,67 @@
<div class="shortcutWrapper">
<div>
<p> First:</p>
<%- include('chapterButton.ejs', {chapter:List[0], scanlator, title}) %>
<%- include('chapterButton.ejs', {chapter:List[0], scanlator, title, shortCut:true}) %>
</div>
<%for(let i = 0; i<List.length; i++){if(List[i].continue) { %>
<div>
<div id="continueWrapper">
<p> Continue:</p>
<%- include('chapterButton.ejs', {chapter:List[i], scanlator, title}) %>
<%- include('chapterButton.ejs', {chapter:List[i], scanlator, title,shortCut:true}) %>
</div>
<%}}%>
<div>
<p>Last:</p>
<%- include('chapterButton.ejs', {chapter:List[List.length-1],}) %>
<%- include('chapterButton.ejs', {chapter:List[List.length-1], scanlator, title,shortCut:true}) %>
</div>
</div>
</div>
<script>
clearVariablesChapterShortCut();
function clearVariablesChapterShortCut()
{
[
'classList',
'classListIndex',
'chapterNum'
].forEach((variable) =>
{
if (window.hasOwnProperty(variable))
{
delete window[variable];
}
});
}
function createListener()
{
document.getElementById('List').addEventListener('htmx:afterSwap', event=>
{
let classList = event.target.classList;
for(let i = 0; i<classList.length; i++)
{
if(classList[i].includes('chapterButton_'))
{
let classListIndex = Array.from(document.getElementById('continueWrapper').childNodes[3].classList).indexOf(classList[i]+'shortCut');
if(classListIndex!=-1)
{
// let chapterNum = classList[i].split('_')[1].split('shortCut')[0];
htmx.ajax('GET', `/chaptertocontinue/<%=scanlator%>/<%= encodeURIComponent(List[0].mangaLink)%>/<%=title%>`,'.'+classList[i]+'shortCut');
}
}
}
});
}
try {
createListener();
} catch (error) {
clearVariablesChapterShortCut();
createListener();
}
</script>

@ -19,11 +19,9 @@
</div>
</div>
<br>
<%- include('chapterShortcutButtonRow.ejs', {...data}) %>
<div class="list">
<div class="list" id="List">
<% data.List.forEach(chapter=>{%>
<%- include('chapterButton.ejs', {chapter, scanlator:data.scanlator, title:data.title}) %>
<%- include('chapterButton.ejs', {chapter, scanlator:data.scanlator, title:data.title, _id:chapter._id, shortCut:false}) %>
<% }) %>
</div>

@ -0,0 +1,6 @@
<span
class="material-symbols-outlined readMarker readMarkerComplete fill"
>
beenhere
</span>
Loading…
Cancel
Save