Greasy Fork is available in English.
Dual-page manga reading mode for desuarchive threads with navigation controls and backlinks display
当前为
// ==UserScript==
// @name desuarchive manga reader
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Dual-page manga reading mode for desuarchive threads with navigation controls and backlinks display
// @author sakanon
// @match *://desuarchive.org/a/thread/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let active = false;
let currentIndex = 0;
let images = [];
document.querySelector('.post_is_op').classList.add('post');
// Add toggle button
const toggleButton = document.createElement('button');
toggleButton.innerText = '📖';
toggleButton.style.position = 'fixed';
toggleButton.style.bottom = '10px';
toggleButton.style.right = '10px';
toggleButton.style.zIndex = 9999;
document.body.appendChild(toggleButton);
toggleButton.addEventListener('click', () => {
active = !active;
if (active) {
enterReadingMode();
} else {
exitReadingMode();
}
});
async function enterReadingMode() {
document.body.style.overflowY = 'hidden';
toggleButton.style.display = 'none';
images = Array.from(document.querySelectorAll('.thread_image_link')).map(a => {
return {
src: a.href.endsWith('.webm') ? a.href.replace('/thumb/', '/image/').replace('.webm', 's.jpg') : a.href,
postId: a.closest('.post').id,
backlinks: a.closest('.post').querySelector('.backlink_list')?.innerHTML || ""
};
});
currentIndex = 0;
await showImages();
document.addEventListener('keydown', navigateImages);
}
function exitReadingMode() {
document.body.style.overflowY = 'auto';
toggleButton.style.display = 'block';
const overlay = document.getElementById('reading-overlay');
if (overlay) overlay.remove();
document.removeEventListener('keydown', navigateImages);
}
async function showImages() {
const overlay = document.getElementById('reading-overlay') || createOverlay();
overlay.innerHTML = ''; // Clear previous images
// Create elements for the first image
const img1 = await createImage(images[currentIndex].src);
const img1Backlinks = createBacklinks(images[currentIndex].backlinks, true);
// Create elements for the second image (if it exists and both images are portrait)
const img2 = ((currentIndex != 0) && (currentIndex + 1 < images.length) && await isPortrait(images[currentIndex].src) && await isPortrait(images[currentIndex + 1].src)) ? await createImage(images[currentIndex + 1].src) : null;
const img2Backlinks = img2 ? createBacklinks(images[currentIndex + 1].backlinks, false) : null;
const pageWrapper = document.createElement('div');
pageWrapper.style.display = 'flex';
pageWrapper.style.justifyContent = 'center';
pageWrapper.style.flexDirection = 'row-reverse'; // For right to left reading
if (img1) pageWrapper.appendChild(img1);
if (img2) pageWrapper.appendChild(img2);
overlay.appendChild(pageWrapper);
if (img1Backlinks) overlay.appendChild(img1Backlinks);
if (img2Backlinks) overlay.appendChild(img2Backlinks);
document.body.appendChild(overlay);
}
async function navigateImages(e) {
if (e.key === 'ArrowLeft' && currentIndex + 1 < images.length) {
currentIndex += (await isPortrait(images[currentIndex].src) && currentIndex != 0 && await isPortrait(images[currentIndex + 1].src)) + 1;
await showImages();
} else if (e.key === 'ArrowRight' && currentIndex > 0) {
currentIndex -= (await isPortrait(images[currentIndex].src) && (currentIndex - 1) > 0 && await isPortrait(images[currentIndex - 1].src)) + 1;
await showImages();
} else if (e.key === 'Escape') {
exitReadingMode();
} else if (e.key === 'o') {
offsetPages();
}
}
function createOverlay() {
const overlay = document.createElement('div');
overlay.id = 'reading-overlay';
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.9)';
overlay.style.zIndex = 9998;
overlay.style.overflowY = 'hidden';
overlay.style.display = 'flex';
overlay.style.alignItems = 'center';
overlay.style.justifyContent = 'center';
return overlay;
}
async function createImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
img.style.maxHeight = '100vh';
img.style.maxWidth = (img.width < img.height) ? '50vw' : '100vw';
resolve(img);
};
img.onerror = () => {
reject(new Error('Failed to load image'));
};
img.src = src;
});
}
function createBacklinks(backlinksHtml, isRight) {
if (!backlinksHtml) return null;
const backlinksDiv = document.createElement('div');
backlinksDiv.innerHTML = backlinksHtml;
backlinksDiv.style.margin = '5px';
backlinksDiv.style.cursor = 'pointer';
backlinksDiv.className = 'backlink_list';
backlinksDiv.style.position = 'fixed';
backlinksDiv.style.top = '0';
backlinksDiv.style.width = '50%';
backlinksDiv.style.fontSize = '11px';
if (isRight) {
backlinksDiv.style.right = '0';
backlinksDiv.style.textAlign = 'right';
} else {
backlinksDiv.style.left = '0';
}
// Add hover event to display post content
backlinksDiv.querySelectorAll('.backlink').forEach(link => {
link.addEventListener('mouseenter', () => {
const postId = link.href.split('#')[1];
const post = document.getElementById(postId);
if (post) {
const tooltip = createTooltip(post.innerHTML);
if (isRight) {
tooltip.style.right = '0';
} else {
tooltip.style.left = '0';
}
link.appendChild(tooltip);
}
});
link.addEventListener('mouseleave', () => {
const tooltip = link.querySelector('.replytooltip');
if (tooltip) tooltip.remove();
});
});
return backlinksDiv;
}
function createTooltip(content) {
const tooltip = document.createElement('div');
tooltip.className = 'replytooltip';
tooltip.innerHTML = content;
tooltip.style.position = 'absolute';
tooltip.style.color = 'white';
tooltip.style.padding = '5px';
tooltip.style.zIndex = '10000';
tooltip.style.fontSize = '10pt';
tooltip.style.wordBreak = 'break-word';
return tooltip;
}
async function isPortrait(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = src;
img.onload = () => {
resolve(img.width < img.height);
};
img.onerror = () => {
reject(new Error('Failed to load image'));
};
});
}
function offsetPages() {
currentIndex += 1;
showImages();
}
})();