Feat: ReadMarkers: Read marks on chapter button, jump to last read page

master
masterhc 1 year ago
parent 5ebbae9469
commit 05c2149e47

@ -20,7 +20,7 @@ exports.searchBar = (req, res)=>
exports.search = async (req, res)=> exports.search = async (req, res)=>
{ {
if(!req.body?.searchstring) return res.sendStatus(404) if(!req.body?.searchstring) return res.sendStatus(404)
var data = await new Search(req.body.searchstring).search(); var data = await new Search(req.body.searchstring).searchExtraInfo();
if(!Array.isArray(data)) return res.sendStatus(404); if(!Array.isArray(data)) return res.sendStatus(404);
const favorites = await FavoriteModel.find() const favorites = await FavoriteModel.find()
const favoriteScanlators = favorites.map(favorite => favorite.scanlator); const favoriteScanlators = favorites.map(favorite => favorite.scanlator);
@ -109,13 +109,22 @@ exports.manga = async (req, res)=>
const scanlatorHasTitle = await new Modules(scanlator).titleExists(title); const scanlatorHasTitle = await new Modules(scanlator).titleExists(title);
if(!scanlatorHasTitle) return res.sendStatus(404); if(!scanlatorHasTitle) return res.sendStatus(404);
let manga; let manga;
let chaptersRead;
try try
{ {
manga = await new Manga(scanlator,link, title).get() manga = await new Manga(scanlator,link, title).get();
chaptersRead = await ChapterModel.find({scanlator,title});
for(let i = 0; i<manga.List.length; i++)
{
for(let j = 0; j<chaptersRead.length; j++)
{
if(manga.List[i].num==chaptersRead[j].chapterNum) manga.List[i]['Read'] = true;
}
}
} }
catch (error) catch (error)
{ {
res.sendStatus(404); console.log('There was an error:', error);
} }
res.render('mangaPage.ejs', {data:{...manga, scanlator}}); res.render('mangaPage.ejs', {data:{...manga, scanlator}});
@ -125,13 +134,14 @@ exports.chapter = async (req, res)=>
const {scanlator, title, chapter, link} = req.params; const {scanlator, title, chapter, link} = req.params;
//IF its already there even with wrong things its there just show it //IF its already there even with wrong things its there just show it
const fileExists = fs.existsSync(`./public/Saved/${scanlator}/${title}/CH_${chapter}`); const fileExists = fs.existsSync(`./public/Saved/${scanlator}/${title}/CH_${chapter}`);
if(fileExists) if(fileExists)
{ {
const imgs =fs.readdirSync(`./public/Saved/${scanlator}/${title}/CH_${chapter}`); const imgs =fs.readdirSync(`./public/Saved/${scanlator}/${title}/CH_${chapter}`);
const latestChap = fs.readdirSync(`./public/Saved/${scanlator}/${title}`).length const latestChap = fs.readdirSync(`./public/Saved/${scanlator}/${title}`).length
const List = imgs.map(filename => {return `./Saved/${scanlator}/${title}/CH_${chapter}/${filename}`}); const List = imgs.map(filename => {return `./Saved/${scanlator}/${title}/CH_${chapter}/${filename}`});
const mangaLink = await new Chapter(scanlator, link, title, chapter).getMangaLink(); const mangaLink = await new Chapter(scanlator, link, title, chapter).getMangaLink();
return res.render('display.ejs', {data:{title,latestChap,scanlator, chapterNum:parseInt(chapter), mangaLink, List}}); return res.render('chapterPage.ejs', {data:{title,latestChap,scanlator, chapterNum:parseInt(chapter), mangaLink, List}});
} }
//If it isn't make sure there is no bad params being passed //If it isn't make sure there is no bad params being passed
const scanlatorExists = await new Modules(scanlator).exists(); const scanlatorExists = await new Modules(scanlator).exists();
@ -147,7 +157,7 @@ exports.chapter = async (req, res)=>
{ {
return res.sendStatus(404); return res.sendStatus(404);
} }
res.render('display.ejs', {data:{...manga, scanlator, chapterNum:parseInt(chapter)}}); res.render('chapterPage.ejs', {data:{...manga, scanlator, chapterNum:parseInt(chapter)}});
} }
exports.favorites = async (req, res)=> exports.favorites = async (req, res)=>
@ -401,20 +411,73 @@ exports.chapterRead = async (req, res)=>
const scanlatorHasTitle = await new Modules(scanlator).titleExists(title); const scanlatorHasTitle = await new Modules(scanlator).titleExists(title);
if(!scanlatorHasTitle) return res.sendStatus(404); if(!scanlatorHasTitle) return res.sendStatus(404);
const manga = await new Manga(scanlator, link, title).get(); const manga = await new Manga(scanlator, link, title).get();
const {latestChap} = manga; const {latestChap, List} = manga;
if(chapter > latestChap || chapter < 0) return res.sendStatus(404); if(chapter > latestChap || chapter < 0) return res.sendStatus(404);
const chap = new ChapterModel(); const chap = new ChapterModel();
let chapterNum = chapter; let chapterNum = chapter;
let chapterLink = List.filter(item=>item.num == chapterNum)[0].link;
if(!chapterLink) return res.sendStatus(404);
const _chapter = await new Chapter(scanlator, chapterLink,title, chapter).get();
let lastImageRead = _chapter.List[_chapter.List.length-1];
chap.lastImageRead = lastImageRead;
const chaps = await ChapterModel.find({scanlator, title, link, chapterNum}); const chaps = await ChapterModel.find({scanlator, title, link, chapterNum});
if(chaps.length>0) return res.sendStatus(404); if(chaps.length>0) return updateChapter(chaps[0],res, lastImageRead, true)
chap.scanlator = scanlator; chap.scanlator = scanlator;
chap.title = title; chap.title = title;
chap.link = link; chap.link = link;
chap.chapterNum = chapter; chap.chapterNum = chapter;
chap.lastImageRead = lastImageRead;
chap.completely = true; chap.completely = true;
chap.save() chap.save()
res.sendStatus(200); res.sendStatus(200);
//Send to db; }
async function updateChapter(chap, res, lastImageRead, completely=false)
{
await ChapterModel.findOneAndUpdate(chap._id,{completely, lastImageRead:lastImageRead}, {new:true});
res.sendStatus(200);
}
exports.chapterIncompleteRead = async (req,res)=>
{
const {scanlator, title, chapter, link, img} = req.params;
const scanlatorExists = await new Modules(scanlator).exists();
if(!scanlatorExists) return res.sendStatus(404);
const scanlatorHasTitle = await new Modules(scanlator).titleExists(title);
if(!scanlatorHasTitle) return res.sendStatus(404);
let chapterNum = chapter;
const manga = await new Manga(scanlator, link, title).get();
const {latestChap, List} = manga;
let chapterLink = List.filter(item=> item.num == chapterNum)[0].link;
if(chapter > latestChap || chapter < 0) return res.sendStatus(404);
const chap = new ChapterModel();
const _chapter = await new Chapter(scanlator, chapterLink,title, chapterNum).get();
let lastImageRead = _chapter.List.filter(item=>
{
let parsedImageURL = item.split('.')[item.split('.').length-2].split('/')[item.split('.')[item.split('.').length-2].split('/').length-1]
return(parsedImageURL==img)
})[0];
const chaps = await ChapterModel.find({scanlator, title, link, chapterNum});
if(chaps.length>0) return updateChapter(chaps[0],res, lastImageRead)
chap.scanlator = scanlator;
chap.title = title;
chap.link = link;
chap.chapterNum = chapter;
chap.lastImageRead = lastImageRead;
chap.completely = false;
chap.save()
res.sendStatus(200);
}
exports.chapterLastRead = async (req, res)=>
{
const {scanlator, title, chapter, link} = req.params;
const scanlatorExists = await new Modules(scanlator).exists();
if(!scanlatorExists) return res.sendStatus(404);
const scanlatorHasTitle = await new Modules(scanlator).titleExists(title);
if(!scanlatorHasTitle) return res.sendStatus(404);
let chapterNum = chapter;
const chaps = await ChapterModel.find({scanlator, title, link, chapterNum});
if(chaps.length == 0) return res.sendStatus(404);
let data = chaps[0].lastImageRead;
res.render('data.ejs', {data});
} }
exports.dashboard = async (req, res)=> exports.dashboard = async (req, res)=>
{ {

@ -8,7 +8,7 @@ new Schema(
title: {type: String, required: true, max: 100}, title: {type: String, required: true, max: 100},
link:{type: String, require:true, max:100}, link:{type: String, require:true, max:100},
chapterNum:{type: Number, require:true}, chapterNum:{type: Number, require:true},
lastImageRead:{type:Number, required:false}, lastImageRead:{type:String, required:false},
completely:{type:Boolean, required:false} completely:{type:Boolean, required:false}
} }
); );

@ -353,6 +353,7 @@ button:hover
margin-left: 1em; margin-left: 1em;
margin-right: 1em; margin-right: 1em;
margin-top: 1em; margin-top: 1em;
min-height: 3.6em;
} }
.card:hover > .card__img .card:hover > .card__img
{ {
@ -463,12 +464,27 @@ button:hover
text-decoration: none; text-decoration: none;
color: var(--text); color: var(--text);
} }
.buttonWrapper
.list > .chapterButton > a > h4 , .list > .chapterButton > a > h5 {
display:flex;
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
justify-content: center;
height:100%
}
.list > .chapterButton > a >.buttonWrapper > div > h4 ,
.list > .chapterButton > a >.buttonWrapper > div > h5
{
margin: 0;
}
.readMarker
{ {
/* margin-left: 0.6em; */ height:auto;
margin-bottom: 0.1em; width:auto;
margin-top: 0.1em; color:var(--text);
position: relative;
left: 0.7em;
} }
.infoCard > .infoText > .tags .infoCard > .infoText > .tags
{ {

@ -23,6 +23,7 @@ module.exports = (worker)=>
router.route('/logout').get(api.logout); router.route('/logout').get(api.logout);
router.route('/chapterRead/:scanlator/:link/:title/:chapter/').post(api.chapterRead); router.route('/chapterRead/:scanlator/:link/:title/:chapter/').post(api.chapterRead);
router.route('/chapterRead/:scanlator/:link/:title/:chapter/:img').post(api.chapterIncompleteRead); router.route('/chapterRead/:scanlator/:link/:title/:chapter/:img').post(api.chapterIncompleteRead);
router.route('/chapterReadLastImage/:scanlator/:link/:title/:chapter/').get(api.chapterLastRead);
//Undefined Routes v //Undefined Routes v

@ -1,10 +1,20 @@
<div class="chapterButton"> <div class="chapterButton">
<a hx-get="/chapter/<%=scanlator %>/<%=encodeURIComponent(chapter.link)%> /<%= title %>/<%= chapter.num %>" hx-trigger="click" hx-target="#container" hx-indicator=".progress"> <a href="/chapter/<%=scanlator %>/<%=encodeURIComponent(chapter.link)%>/<%= title %>/<%= chapter.num %>" >
<h4 class="chapterNum"> <div class="buttonWrapper">
Chapter <%= chapter.num %> <% if(chapter.Read){ %>
</h4> <span class="material-symbols-outlined readMarker">
<h5 class="date"> beenhere
<%= chapter.date %> </span>
</h5> <% } %>
<div>
<h4 class="chapterNum">
Chapter <%= chapter.num %>
</h4>
<h5 class="date">
<%= chapter.date %>
</h5>
</div>
</div>
</a> </a>
</div> </div>

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="icon" type="image/ico" href="images/favicon.ico"/>
<title>Manga Reader</title>
<link rel="stylesheet" type="text/css" href="/css/home.css" id="pagesheet"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<script type="module" src="/js/home.js"></script>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
</head>
<body>
<div class="progress" style="height: 3px; background-color: white;">
<div class="indeterminate" style="background-color: red;"></div>
</div>
<div id="wrapper">
<%- include('navBar.ejs', {data, selected:'none'}) %>
<div class="container" id="container" >
<%- include('display.ejs', {data}) %>
</div>
</div>
</body>
</html>

@ -0,0 +1,3 @@
<data data-src="<%= data %>">
</data>

@ -2,10 +2,10 @@
<div class="chapterOverview" id="chapterOverview"> <div class="chapterOverview" id="chapterOverview">
<div class="flexBox" id="flexBox"> <div class="flexBox" id="flexBox">
<% for(var [index, img] of data.List.entries()){ %> <% for(var [index, img] of data.List.entries()){ %>
<img class="overview" id="img<%= index %>" src=<%= img.replaceAll(' ','%20') %> onclick="_scrollTo('<%= '_img'+index%>')" /> <img class="overview" id="img<%= index %>" src=<%= img.startsWith('.')?img.replace('.',''):img.replaceAll(' ','%20') %> onclick="_scrollTo('<%= '_img'+index%>')" />
<% } %> <% } %>
</div> </div>
<div class="expand" onclick="toggle()"> <div class="expand" onclick="_toggle()">
<span class="material-symbols-outlined" id="expandChevron">chevron_right</span> <span class="material-symbols-outlined" id="expandChevron">chevron_right</span>
</div> </div>
</div> </div>
@ -24,7 +24,7 @@
<button class="pullBackUp" onclick="pullUp()"> <span class="material-symbols-outlined chevronUp">expand_less</span></button> <button class="pullBackUp" onclick="pullUp()"> <span class="material-symbols-outlined chevronUp">expand_less</span></button>
<div id="display"> <div id="display">
<% for(var [index, img] of data.List.entries()){ %> <% for(var [index, img] of data.List.entries()){ %>
<img id="_img<%= index %>" src=<%= img.replaceAll(' ','%20') %> alt="img<%= index %>"/> <img class="_<%= img.split('.')[img.split('.').length-2].split('/')[img.split('.')[img.split('.').length-2].split('/').length-1]%>" id="_img<%= index %>" src=<%= img.startsWith('.')?img.replace('.',''):img.replaceAll(' ','%20') %> alt="img<%= index %>"/>
<% } %> <% } %>
<div id="last-image" <div id="last-image"
hx-post="/chapterRead/<%= data.scanlator %>/<%= encodeURIComponent(data.mangaLink)%>/<%= data.title %>/<%= data.chapterNum %>" hx-post="/chapterRead/<%= data.scanlator %>/<%= encodeURIComponent(data.mangaLink)%>/<%= data.title %>/<%= data.chapterNum %>"
@ -40,64 +40,70 @@
> >
</div> </div>
</div> </div>
<div
id="lastImageRecordedAsRead"
hx-get= "/chapterReadLastImage/<%=data.scanlator %>/<%=encodeURIComponent(data.mangaLink)%>/<%=data.title%>/<%=data.chapterNum%>""
hx-trigger="load"
hx-indicator=".progress"
>
</div>
</div> </div>
<script> <script>
clearVariables(); checkPreview();
handleInView(); document.getElementById('lastImageRecordedAsRead').addEventListener('htmx:load', (event) =>
function clearVariables()
{ {
[ 'lastImage', let link = event.target.outerHTML.split('"')[1];
'observer', let aux = link.split('.')[link.split('.').length-2].split('/');
'flexBox', let id = '_'+aux[aux.length-1];
'chapterOverview', const elements = document.getElementsByClassName(id);
'chevron', for(let i = 0; i<elements.length; i++)
'spacer', {
'preview', elements[i].scrollIntoView({
'elements', behavior: 'smooth',
'windowHeight', block: 'start'
'bounding', });
'element', }
'elementId' });
].forEach((variable) => handleInView();
{
if (window.hasOwnProperty(variable))
{
delete window[variable];
}
});
}
function pullUp() function pullUp()
{ {
window.scrollTo({ top: 0, behavior: 'smooth' }) window.scrollTo({ top: 0, behavior: 'smooth' })
} }
function _scrollTo(elementId) function _scrollTo(elementId, where='start')
{ {
document.getElementById(elementId).scrollIntoView({ document.getElementById(elementId).scrollIntoView({
behavior: 'smooth', behavior: 'smooth',
block: 'start' block: where
}); });
} }
window.addEventListener('scroll', handleInView); window.addEventListener('scroll', handleInView);
function handleInView() function handleInView()
{ {
var elements = document.querySelectorAll('#display img'); // Replace '.your-element-class' with the class of the elements you want to check var elements = document.querySelectorAll('#display img');
var windowHeight = window.innerHeight; var windowHeight = window.innerHeight;
elements.forEach((element)=> for(let i = 0; i<elements.length; i++)
{ {
var bounding = element.getBoundingClientRect(); var bounding = elements[i].getBoundingClientRect();
if (bounding.top<windowHeight && (bounding.top + bounding.height) > windowHeight) if (bounding.top<windowHeight && (bounding.top + bounding.height) > windowHeight)
{ {
if(document.querySelector('.inView')) document.querySelector('.inView').classList.remove('inView'); if(document.querySelector('.inView'))
document.getElementById(element.alt).classList.add('inView'); {
if(document.querySelector('.inView').id != elements[i].id.split('_')[1])
{
fetch(window.location.origin+`/chapterRead/<%= data.scanlator %>/<%= encodeURIComponent(data.mangaLink)%>/<%= data.title %>/<%= data.chapterNum %>/${elements[i].classList[0].split('_')[1]}`, {method:'post', cache:"no-cache"})
.then(res=>console.log('Lastread Update'))
.catch(err=>console.log('Lastread Update Failed'));
}
document.querySelector('.inView').classList.remove('inView');
}
document.getElementById(elements[i].alt).classList.add('inView');
_scrollTo(elements[i].alt, 'end');
} }
}); }
}; };
function toggle() function _toggle()
{ {
let flexBox = document.getElementById('flexBox'); let flexBox = document.getElementById('flexBox');
if( flexBox.style.display!='none') return closePreview(); if( flexBox.style.display!='none') return closePreview();
@ -164,12 +170,5 @@
if(preview=='preview') return openPreview(); if(preview=='preview') return openPreview();
return closePreview(); return closePreview();
} }
try {
hasFinished();
checkPreview();
} catch (error) {
clearVariables();
hasFinished();
checkPreview();
}
</script> </script>
Loading…
Cancel
Save