\d+)\b/i;
async function decorateCards(reorder = true) {
const cards = await untilDOM(() => $$$("//div[contains(@class, 'listupd')]//div[contains(@class, 'bsx')]/.."));
cards.reverse().forEach(b => {
const post_id = getSeriesId(b.firstElementChild.dataset.id, $('a', b).href);
const epxs = $('.epxs',b)?.textContent ?? b.innerHTML.match(/(?.*?)<\/div>/)?.groups.epxs;
const latest_chapter = getLatestChapter(post_id, parseInt(epxs?.match(CHAPTER_REGEX)?.groups.chapter));
const { number, id } = getLastReadChapter(post_id);
if (id) {
const unreadChapters = latest_chapter - number;
if (unreadChapters) {
// reorder bookmark, link directly to last read chapter and slap an unread count badge.
if (reorder) b.parentElement.prepend(b);
$('a',b).href = '/?p=' + id;
$('.limit',b).prepend(crel('div', {
className: 'unread-badge',
textContent: unreadChapters<100 ? unreadChapters : '💀',
title: `${unreadChapters} unread chapter${unreadChapters>1?'s':''}`
}))
} else {
// nothing new to read here. gray it out.
b.style = 'filter: grayscale(70%);opacity:.9';
}
} else {
// we don't have data on that series. leave it alone.
}
});
}
// text-mode /manga/ page. put badges at the end of each series title, and strike through what's already read.
async function decorateText() {
const links = await untilDOM(()=>$`.soralist a.series` && $$`.soralist a.series`);
links.forEach(a => {
const post_id = getSeriesId(a.rel, a.href);
const latest_chapter = getLatestChapter(post_id);
const { number, id } = getLastReadChapter(post_id);
if (id) {
const unreadChapters = latest_chapter - number;
if (unreadChapters) {
a.href = '/?p=' + id;
a.append(crel('div', {
className: 'unread-badge',
textContent: unreadChapters<100 ? unreadChapters : '💀',
title: `${unreadChapters} unread chapter${unreadChapters>1?'s':''}`
}))
} else {
// nothing new to read here. gray it out.
a.style = 'text-decoration: line-through;color: #777'
}
}
})
}
// page specific tweaks
const chapterMatch = document.title.match(CHAPTER_REGEX);
if (chapterMatch) {
until(()=>unsafeWindow.post_id).then(() => {
// We're on a chapter page. Save chapter number and id if greater than last saved chapter number.
const chapter_number = parseInt(chapterMatch.groups.chapter ?? chapterMatch.groups.ch);
const { post_id, chapter_id } = unsafeWindow;
const { number = 0 } = getLastReadChapter(post_id);
if (number {
// We're on a bookmark page. Wait for them to load, then tweak them to point to last read chapter, and gray out the ones that are fully read so far.
setTimeout(()=> {
if (!$`#bookmark-pool [data-id]`) {
// no data yet from bookmark API. show a fallback.
$`#bookmark-pool`.innerHTML = GM_getValue(BOOKMARK_HTML, localStorage.bookmarkHTML ?? '');
// add a marker so we know this is just a cached rendering.
$`#bookmark-pool [data-id]`.classList.add('cached');
// decorate what we have.
decorateCards();
}
}, 1000);
// wait until we get bookmark markup from the server, not cached.
await untilDOM("#bookmark-pool .bs:first-child [data-id]:not(.cached)");
// bookmarks' ajax API is flaky (/aggressively rate-limited) - mitigate.
GM_setValue(BOOKMARK_HTML, $`#bookmark-pool`.innerHTML);
decorateCards();
})(); else {
// try generic decorations on any non-bookmark page
decorateCards(false);
decorateText();
}
untilDOM(`#chapterlist`).then(() => {
// Add a "Continue Reading" button on main series pages.
const post_id = $`.bookmark`.dataset.id;
const { number, id } = getLastReadChapter(post_id);
// add a "Continue Reading" button for series we recognize
if (id) {
$`.lastend`.prepend(crel('div', {
className: 'inepcx',
style: 'width: 100%'
},
crel('a', { href: '/?p=' + id },
crel('span', {}, 'Continue Reading'),
crel('span', { className: 'epcur' }, 'Chapter ' + number))
));
}
});
untilDOM(()=>$$$("//span[text()='Related Series' or text()='Similar Series']/../../..")[0]).then(related => {
// Tweak footer content on any page that has them
// 1. collapse related series.
makeCollapsedFooter({label: 'Show Related Series', section: related});
related.style.display = 'none';
});
untilDOM("#comments").then(comments => {
// 2. collapse comments.
makeCollapsedFooter({label: 'Show Comments', section: comments});
});
// This page rotation thingy actually feels good now.
async function rotatePage(clockwise) {
if (rotating) return;
rotating = true;
const html = document.documentElement;
html.style.overflow = "hidden";
const { scrollHeight, scrollTop, scrollWidth, scrollLeft, clientWidth, clientHeight, style } = html;
const oldOriginY = parseInt(style.transformOrigin.split(" ")[1]);
const from = ({ angle, originY, topFactor=0 }) => next => () => {
style.transition = "initial";
style.transformOrigin = `${clientWidth/2}px ${originY()}px`;
style.transform=`rotate(${angle(next == to0)}deg)`;
scrollBy({top: topFactor*(originY() - oldOriginY), behavior:"instant"});
scrollTo({left: angle()%180?0:void 0, behavior: "instant"});
style.transition='';
return next(angle()==0);
};
const to = ({angle, ty=()=>0, left=()=>void 0, top=()=>void 0}) => (flag) => new Promise(next => {
style.transform=`rotate(${angle(flag)}deg)`;
html.addEventListener('transitionend', () => {
style.transition="initial";
if (angle()==180) style.transformOrigin = `${clientWidth/2}px ${scrollHeight/2}px`;
style.transform=`rotate(${angle(true)}deg) translate(0,${ty()}px)`;
scrollTo({left:left(), top:top(), behavior: "instant"});
style.transition ="";
style.overflow = angle()%180 ? "auto hidden" : "hidden auto";
next(angle());
}, {once: true});
});
const from0 = from({ angle:()=>0, originY:()=>scrollTop + clientHeight/2 });
const from90 = from({ angle:()=>90, originY:()=>scrollHeight - scrollLeft - clientWidth/2 - 1, topFactor:1 });
const from180 = from({ angle:()=>180, originY:()=>scrollHeight - scrollTop - clientHeight/2, topFactor:2 });
const from270 = from({ angle:to0=>to0?-90:270, originY:()=>scrollLeft + clientHeight/2, topFactor:1 });
const to0 = to({ angle:()=>0 });
const to90 = to({ angle:()=>90, ty:()=>html.scrollWidth - scrollHeight, left:()=>scrollHeight - html.scrollTop - clientWidth/2 - clientHeight/2 });
const to180 = to({ angle:()=>180, top:()=>scrollHeight - html.scrollTop - clientHeight });
const to270 = to({ angle:from0=>from0?-90:270, ty:()=>html.scrollTop, left:()=>html.scrollTop });
const rotations = {
0: [ from0(to270), from0(to90) ],
90: [ from90(to0), from90(to180) ],
180: [ from180(to90), from180(to270) ],
270: [ from270(to180), from270(to0) ]
}
orientation = await rotations[orientation][~~clockwise]();
rotating = false;
}