- // ==UserScript==
- // @name Unlimited Paginator Works
- // @namespace https://greasyfork.org/scripts/5250
- // @description Makes any(?) page with a paginator on various Danbooru clones "bottomless"--blend pages together or separate each with a paginator.
- // @include *://behoimi.org/*
- // @include *://www.behoimi.org/*
- // @include *://*.donmai.us/*
- // @include *://konachan.tld/*
- // @include *://yande.re/*
- // @version 2022.06.25
- // @grant none
- // ==/UserScript==
-
- //If true, each added page retains its paginator. If false, elements are smoothly joined together.
- var pageBreak = false;
-
- //Minimum amount of window left to scroll, maintained by loading more pages.
- var scrollBuffer = 600;
-
- //Time (in ms) the script will wait for a response from the next page before attempting to fetch the page again. If the script gets trapped in a loop trying to load the next page, increase this value.
- var timeToFailure = 15000;
-
- //============================================================================
- //=========================Script initialization==============================
- //============================================================================
-
- var nextPage, mainTable, mainParent, pending, timeout, iframe;
-
- if( typeof(customF) != "undefined" )
- customF();
-
- initialize();
- function initialize()
- {
- //Stop if inside an iframe
- if( window != window.top || scrollBuffer == 0 )
- return;
-
- //Stop if no "table"
- mainTable = getMainTable(document);
- if( !mainTable )
- {
- //console.log("UPW: No main table");
- return;
- }
-
- //Stop if no paginator
- var paginator = getPaginator(document);
- if( !paginator )
- {
- //console.log("UPW: No paginator found");
- return;
- }
-
- //Stop if no more pages
- nextPage = getNextPage(paginator);
- if( !nextPage )
- return;
-
- //Hide the blacklist sidebar, since this script breaks the tag totals and post unhiding.
- var sidebar = document.getElementById("blacklisted-sidebar");
- if( sidebar )
- sidebar.style.display = "none";
-
- //Other important variables:
- scrollBuffer += window.innerHeight;
- mainParent = mainTable.parentNode;
- pending = false;
-
- iframe = document.createElement("iframe");
- iframe.width = iframe.height = 0;
- iframe.style.visibility = "hidden";
- document.body.appendChild(iframe);
-
- //Slight delay so that Danbooru's initialize_edit_links() has time to hide all the edit boxes on the Comment index
- iframe.addEventListener("load", function(e){ setTimeout( appendNewContent, 100 ); }, false);
-
- //Stop if empty page
- if( /<p>(Nothing to display.|Nobody here but us chickens!)<.p>/.test(mainTable.innerHTML) )
- return;
-
- //Add copy of paginator to the top
- mainParent.insertBefore( paginator.cloneNode(true), mainParent.firstChild );
-
- if( !pageBreak )
- paginator.style.display = "none";//Hide bottom paginator
- else
- {
- //Reposition bottom paginator and add horizontal break
- mainTable.parentNode.insertBefore( document.createElement("hr"), mainTable.nextSibling );
- mainTable.parentNode.insertBefore( paginator, mainTable.nextSibling );
- }
-
- //Listen for scroll events
- window.addEventListener("scroll", testScrollPosition, false);
- testScrollPosition();
- }
-
- //============================================================================
- //============================Script functions================================
- //============================================================================
-
- //Some pages match multiple "tables", so order is important.
- function getMainTable(source)
- {
- var xpath =
- [
- ".//div[contains(@class,'posts-container') or contains(@class,'media-assets-container')]" // Danbooru (posts, ai_tags, uploads)
- ,".//div[@id='a-index']/table[not(contains(@class,'search'))]" // Danbooru (/forum_topics, ...), take care that this doesn't catch comments containing tables
- ,".//div[@id='a-index']" // Danbooru (/comments, ...)
-
- ,".//table[contains(@class,'highlight')]" // large number of pages
- ,".//div[contains(@id,'comment-list')]/div/.." // comment index
- ,".//*[not(contains(@id,'popular'))]/span[contains(@class,'thumb')]/a/../.." // post/index, pool/show, note/index
- ,".//li/div/a[contains(@class,'thumb')]/../../.." // post/index, note/index
- ,".//div[@id='content']//table/tbody/tr[contains(@class,'even')]/../.." // user/index, wiki/history
- ,".//div[@id='content']/div/table" // 3dbooru user records
- ,".//div[@id='forum']" // forum/show
- ];
-
- for( var i = 0; i < xpath.length; i++ )
- {
- getMainTable = (function(query){ return function(source)
- {
- return new XPathEvaluator().evaluate(query, source, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
- }; })( xpath[i] );
-
- var result = getMainTable(source);
- if( result )
- {
- //console.log("UPW main table: "+xpath[i]+"\n\n"+location.pathname);
- return result;
- }
- }
-
- return null;
- }
-
- function getPaginator( source )
- {
- var pager = new XPathEvaluator().evaluate("descendant-or-self::div[@id='paginator' or contains(@class,'paginator') or @id='paginater']", source, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
-
- // Need clear:none to prevent the 2nd page from being pushed to below the sidebar on the Post index... but we don't want this when viewing a specific pool,
- // because then the paginator is shoved to the right of the last images on a page. Other sites have issues with clear:none as well, like //yande.re/post.
- if( pager && location.host.indexOf("donmai.") >= 0 && document.getElementById("sidebar") )
- pager.style.clear = "none";
-
- return pager;
- }
-
- function getNextPage( source )
- {
- let page = getPaginator(source);
- if( page )
- page = new XPathEvaluator().evaluate(".//a[@alt='next' or @rel='next' or contains(text(),'>') or contains(text(),'Next')]", page, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
-
- return( page && page.href );
- }
-
- function testScrollPosition()
- {
- if( !nextPage )
- testScrollPosition = function(){};
-
- //Take the max of the two heights for browser compatibility
- else if( !pending && window.pageYOffset + scrollBuffer > Math.max( document.documentElement.scrollHeight, document.documentElement.offsetHeight ) )
- {
- pending = true;
- timeout = setTimeout( function(){pending=false;testScrollPosition();}, timeToFailure );
- iframe.contentDocument.location.replace(nextPage);
- }
- }
-
- function appendNewContent()
- {
- //Make sure page is correct. Using 'indexOf' instead of '!=' because links like "https://danbooru.donmai.us/pools?page=2&search%5Border%5D=" become "https://danbooru.donmai.us/pools?page=2" in the iframe href.
- clearTimeout(timeout);
- if( nextPage.indexOf(iframe.contentDocument.location.href) < 0 )
- {
- setTimeout( function(){ pending = false; }, 1000 );
- return;
- }
-
- //Copy content from retrived page to current page, but leave off certain headers, labels, etc...
- var sourcePaginator = document.adoptNode( getPaginator(iframe.contentDocument) );
- var nextElem, deleteMe, source = document.adoptNode( getMainTable(iframe.contentDocument) );
-
- if( /<p>(Nothing to display.|Nobody here but us chickens!)<.p>/.test(source.innerHTML) )
- nextPage = null;
- else
- {
- nextPage = getNextPage(sourcePaginator);
-
- if( pageBreak )
- mainParent.appendChild(source);
- else
- {
- //Hide elements separating one table from the next (h1 is used for user names on comment index)
- var rems = source.querySelectorAll("h2, h3, h4, thead, tfood");
- for( var i = 0; i < rems.length; i++ )
- rems[i].style.display = "none";
-
- //Move contents of next table into current one
- var fragment = document.createDocumentFragment();
- while( (nextElem = source.firstChild) )
- fragment.appendChild(nextElem);
- mainTable.appendChild(fragment);
- }
- }
-
- //Add the paginator at the bottom if needed.
- if( !nextPage || pageBreak )
- mainParent.appendChild( sourcePaginator );
- if( pageBreak && nextPage )
- mainParent.appendChild( document.createElement("hr") );
-
- //Clear the pending request marker and check position again
- pending = false;
- testScrollPosition();
- }
-
- // I am the code of my script.
- // HTML is my body, and JavaScript is my blood.
- // I have incorporated over a thousand paginators.
- // Unaware of loss.
- // Nor aware of gain.
- // Withstood boredom to include many pages,
- // Striving for the script's completion.
- // I have no regrets, this is the only path.
- // My whole life was "Unlimited Paginator Works."