Ocultar de «Continuar leyendo» los mangas terminados salvo que haya capítulos nuevos
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+5
-2
@@ -179,10 +179,12 @@ function mangaCard(m) {
|
|||||||
}
|
}
|
||||||
function continueCard(h) {
|
function continueCard(h) {
|
||||||
const pct = h.total_pages ? Math.round(((h.page + 1) / h.total_pages) * 100) : 0
|
const pct = h.total_pages ? Math.round(((h.page + 1) / h.total_pages) * 100) : 0
|
||||||
return `<a class="cont" href="${readUrl(h.manga_id, h.chapter_id, h.page)}">
|
// Terminado con capítulos nuevos: a la ficha, para elegir el siguiente.
|
||||||
|
return `<a class="cont" href="${h.hasNew ? `#/manga/${h.manga_id}` : readUrl(h.manga_id, h.chapter_id, h.page)}">
|
||||||
<img src="${esc(h.cover || '/icon.svg')}" alt="" loading="lazy">
|
<img src="${esc(h.cover || '/icon.svg')}" alt="" loading="lazy">
|
||||||
<div class="info"><b>${esc(h.manga_title)}</b>
|
<div class="info"><b>${esc(h.manga_title)}</b>
|
||||||
<div class="muted">${h.chapter_num ? `Cap. ${esc(h.chapter_num)}` : 'Oneshot'} · pág. ${h.page + 1}/${h.total_pages || '?'}</div>
|
<div class="muted">${h.chapter_num ? `Cap. ${esc(h.chapter_num)}` : 'Oneshot'} · pág. ${h.page + 1}/${h.total_pages || '?'}</div>
|
||||||
|
${h.finished ? '<div><span class="badge">Terminado</span></div>' : h.hasNew ? '<div><span class="badge">Caps. nuevos</span></div>' : ''}
|
||||||
<div class="muted" style="font-size:.8rem">${timeAgo(h.updated_at)}</div>
|
<div class="muted" style="font-size:.8rem">${timeAgo(h.updated_at)}</div>
|
||||||
<div class="bar"><i style="width:${pct}%"></i></div></div></a>`
|
<div class="bar"><i style="width:${pct}%"></i></div></div></a>`
|
||||||
}
|
}
|
||||||
@@ -254,7 +256,7 @@ async function viewHome(params) {
|
|||||||
if (!q && !tag && !showCats) {
|
if (!q && !tag && !showCats) {
|
||||||
Promise.all([api('/api/history'), api('/api/follows')]).then(([hist, fol]) => {
|
Promise.all([api('/api/history'), api('/api/follows')]).then(([hist, fol]) => {
|
||||||
if (!$('#cont')) return
|
if (!$('#cont')) return
|
||||||
$('#cont').innerHTML = `<div id="news">${newsSection(fol)}</div>` + (hist.length ? `<h2>Continuar leyendo</h2><div class="continue">${hist.slice(0, 12).map(continueCard).join('')}</div>` : '')
|
$('#cont').innerHTML = `<div id="news">${newsSection(fol)}</div>` + (hist = hist.filter(h => !h.finished), hist.length ? `<h2>Continuar leyendo</h2><div class="continue">${hist.slice(0, 12).map(continueCard).join('')}</div>` : '')
|
||||||
bindNews(fol)
|
bindNews(fol)
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
}
|
}
|
||||||
@@ -452,6 +454,7 @@ async function viewReader(mangaId, chapterId, startPage) {
|
|||||||
const payload = () => ({
|
const payload = () => ({
|
||||||
mangaId, chapterId, mangaTitle: manga.title, cover: manga.cover, chapterNum: chapter.num ?? null,
|
mangaId, chapterId, mangaTitle: manga.title, cover: manga.cover, chapterNum: chapter.num ?? null,
|
||||||
chapterTitle: chapter.title, lang: manga.source === 'lc' ? 'es' : chapter.lang || lang, page: lastShown(), totalPages: total,
|
chapterTitle: chapter.title, lang: manga.source === 'lc' ? 'es' : chapter.lang || lang, page: lastShown(), totalPages: total,
|
||||||
|
isLast: idx >= 0 && !next, // al terminarlo, el manga sale de "Continuar leyendo"
|
||||||
})
|
})
|
||||||
const saveNow = () => { clearTimeout(saveTimer); saveTimer = null; api('/api/progress', { method: 'POST', body: payload() }).catch(() => {}) }
|
const saveNow = () => { clearTimeout(saveTimer); saveTimer = null; api('/api/progress', { method: 'POST', body: payload() }).catch(() => {}) }
|
||||||
const scheduleSave = () => { clearTimeout(saveTimer); saveTimer = setTimeout(saveNow, 700) }
|
const scheduleSave = () => { clearTimeout(saveTimer); saveTimer = setTimeout(saveNow, 700) }
|
||||||
|
|||||||
+16
-5
@@ -107,6 +107,8 @@ CREATE TABLE IF NOT EXISTS downloaded_chapters (
|
|||||||
CREATE INDEX IF NOT EXISTS idx_dlch_manga ON downloaded_chapters(manga_id);
|
CREATE INDEX IF NOT EXISTS idx_dlch_manga ON downloaded_chapters(manga_id);
|
||||||
`)
|
`)
|
||||||
db.prepare('DELETE FROM sessions WHERE expires_at < ?').run(Date.now())
|
db.prepare('DELETE FROM sessions WHERE expires_at < ?').run(Date.now())
|
||||||
|
// finished_at: cuándo se terminó la última página del último capítulo disponible (null = en curso).
|
||||||
|
if (!db.prepare('PRAGMA table_info(progress)').all().some(c => c.name === 'finished_at')) db.exec('ALTER TABLE progress ADD COLUMN finished_at INTEGER')
|
||||||
|
|
||||||
// ---------- Contraseñas y sesiones ----------
|
// ---------- Contraseñas y sesiones ----------
|
||||||
function hashPassword(pw) {
|
function hashPassword(pw) {
|
||||||
@@ -796,7 +798,14 @@ async function route(req, res) {
|
|||||||
|
|
||||||
// --- Historial ---
|
// --- Historial ---
|
||||||
if (p === '/api/history' && method === 'GET') {
|
if (p === '/api/history' && method === 'GET') {
|
||||||
return json(res, 200, db.prepare('SELECT * FROM progress WHERE user_id = ? ORDER BY updated_at DESC LIMIT 200').all(user.id))
|
// Un manga terminado vuelve a "en curso" si después aparecieron capítulos nuevos.
|
||||||
|
const rows = db.prepare(`SELECT p.*, f.last_new_at FROM progress p
|
||||||
|
LEFT JOIN follows f ON f.user_id = p.user_id AND f.manga_id = p.manga_id AND f.active = 1
|
||||||
|
WHERE p.user_id = ? ORDER BY p.updated_at DESC LIMIT 200`).all(user.id)
|
||||||
|
return json(res, 200, rows.map(({ last_new_at, ...r }) => {
|
||||||
|
const hasNew = !!(r.finished_at && last_new_at && last_new_at > r.finished_at)
|
||||||
|
return { ...r, finished: !!r.finished_at && !hasNew, hasNew }
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
if (p === '/api/progress' && method === 'POST') {
|
if (p === '/api/progress' && method === 'POST') {
|
||||||
const b = await readBody(req)
|
const b = await readBody(req)
|
||||||
@@ -804,15 +813,17 @@ async function route(req, res) {
|
|||||||
const page = Math.max(0, Number(b.page) | 0)
|
const page = Math.max(0, Number(b.page) | 0)
|
||||||
const total = Math.max(0, Number(b.totalPages) | 0)
|
const total = Math.max(0, Number(b.totalPages) | 0)
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
const finishedAt = b.isLast === true && total && page >= total - 1 ? now : null
|
||||||
const prevPage = db.prepare('SELECT page FROM chapter_reads WHERE user_id = ? AND chapter_id = ?').get(user.id, b.chapterId)?.page
|
const prevPage = db.prepare('SELECT page FROM chapter_reads WHERE user_id = ? AND chapter_id = ?').get(user.id, b.chapterId)?.page
|
||||||
db.prepare(`INSERT INTO progress (user_id, manga_id, manga_title, cover, chapter_id, chapter_num, chapter_title, lang, page, total_pages, updated_at)
|
db.prepare(`INSERT INTO progress (user_id, manga_id, manga_title, cover, chapter_id, chapter_num, chapter_title, lang, page, total_pages, updated_at, finished_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
ON CONFLICT (user_id, manga_id) DO UPDATE SET manga_title = excluded.manga_title, cover = excluded.cover,
|
ON CONFLICT (user_id, manga_id) DO UPDATE SET manga_title = excluded.manga_title, cover = excluded.cover,
|
||||||
chapter_id = excluded.chapter_id, chapter_num = excluded.chapter_num, chapter_title = excluded.chapter_title,
|
chapter_id = excluded.chapter_id, chapter_num = excluded.chapter_num, chapter_title = excluded.chapter_title,
|
||||||
lang = excluded.lang, page = excluded.page, total_pages = excluded.total_pages, updated_at = excluded.updated_at`)
|
lang = excluded.lang, page = excluded.page, total_pages = excluded.total_pages, updated_at = excluded.updated_at,
|
||||||
|
finished_at = excluded.finished_at`)
|
||||||
.run(user.id, b.mangaId, String(b.mangaTitle || '').slice(0, 300), b.cover ? String(b.cover).slice(0, 1000) : null,
|
.run(user.id, b.mangaId, String(b.mangaTitle || '').slice(0, 300), b.cover ? String(b.cover).slice(0, 1000) : null,
|
||||||
b.chapterId, b.chapterNum != null ? String(b.chapterNum) : null, String(b.chapterTitle || '').slice(0, 300),
|
b.chapterId, b.chapterNum != null ? String(b.chapterNum) : null, String(b.chapterTitle || '').slice(0, 300),
|
||||||
LANG.test(b.lang || '') ? b.lang : null, page, total, now)
|
LANG.test(b.lang || '') ? b.lang : null, page, total, now, finishedAt)
|
||||||
db.prepare(`INSERT INTO chapter_reads (user_id, chapter_id, manga_id, page, total_pages, updated_at) VALUES (?, ?, ?, ?, ?, ?)
|
db.prepare(`INSERT INTO chapter_reads (user_id, chapter_id, manga_id, page, total_pages, updated_at) VALUES (?, ?, ?, ?, ?, ?)
|
||||||
ON CONFLICT (user_id, chapter_id) DO UPDATE SET page = excluded.page, total_pages = excluded.total_pages, updated_at = excluded.updated_at`)
|
ON CONFLICT (user_id, chapter_id) DO UPDATE SET page = excluded.page, total_pages = excluded.total_pages, updated_at = excluded.updated_at`)
|
||||||
.run(user.id, b.chapterId, b.mangaId, page, total, now)
|
.run(user.id, b.chapterId, b.mangaId, page, total, now)
|
||||||
|
|||||||
Reference in New Issue
Block a user