In the good old days of “The Real Opera(TM)” there was one feature for lazy people like me that I heavily used: The Space Bar!
I can hear you saying “The space bar is no feature, it is a key on the keyboard!” but that is only half of the truth:
If no edit field was focused but the page itself and you pressed the space bar, “The Real Opera(TM)” scrolled one page down. Nothing special so far, almost all browsers today do that (guess from which browser they copied that function ;)) but the other browsers missed one thing:
If you were on a paginated page or if the page had a LINK
or an A rеl="neхt"
element, or if simply the linktext contained “Next” or something similar in one of many languages,”The Real Opera(TM)” took you to the next page after you had scrolled to the bottom and hit the space bar again. I am one of those persons who actually read pages until the bottom and often follow the link to the next part of the article, so trying to restore this behavior was a must.
OK. Stop babbling and show us the code instead!
// ==UserScript== // @name Restore O12 space bar behavior // @version 0.19 // @author Roland "QuHno" Reck // @include http://*.* // @include https://*.* // ==/UserScript== (function (window) { "use strict"; /*jshint browser: true, devel: false, debug: true, evil: true, forin: true, undef: true, bitwise: true, eqnull: true, noarg: true, noempty: true, eqeqeq: true, boss: true, loopfunc: true, laxbreak: true, strict: true, curly: true, nonew: true */ function is_number(obj) { return !isNaN(obj - 0); } function make_ipattern(string) { return '/' + string + '/i'; } function split_hash(obj) { //return (url + '#a').split('#')[0]; return (obj.pathname + obj.search); } function getScrollMaxY() { return document.documentElement.scrollHeight - document.documentElement.clientHeight; } function scrollToPos(el, x, y) { el.scrollLeft = x; el.scrollTop = y; } var i, j, HREF = split_hash(window.location), PROTOCOL = window.location.protocol, HOST = window.location.host, HOSTPATTERN = make_ipattern(HOST), A, LRN, theParent, searchDepth, stop = false, linkContainer = false, regexps = { trim: /^\s+|\s+$/g, normalize: /\s{2,}/g, relLink: /^next$/gi, nextLink: /(Next\s*(page)?|Neste\s*(side)?|N(ä|ae)chste\s*(Seite)?|Weiter(e.*)?|Vorw(ä|ae)rts|Volg(ende)?\s*(bladsy|pagina)?|Verder|(Page)?\s*Suiv(ant)?(e)?(s)?|(Page)?\s*(prochaine)|Avanti|(Pag(ina)?)?\s*Succ(essiv(e|a|o)|Prossim(e|a|o))|Altr(a|o)|(P(á|a)gina)?\s*(S(e|i)guie?nte)|Próxim(a|os?)|Nästa\s*(sida)?|Næste?\s*(side)?|下一頁|下一页|Sonraki|Следующая|Далее| 下一页|下一张|下一篇|下一章|下一节|下一步|下一个|下页|后页|下一頁|下一張|下一節|下一個|下頁|後頁|다음|다음\s*페이지|次へ|Seuraava|Επόμενη|Следващ(а|о|и)\s*(страница|сайт)?|Нататък|След(в|н)а|(Пълен)?\s*Напред|Dalej|Następn(a|e|y)|Więcej|Tovább|Köv(etkező|\.)|Bővebben|Înaint(ar)?e(ază)?|Avansează|(Pagina\s*)?Următoa?r(e|ul)?|>([^\|]|$)|»([^\|]|$)|→)/i, extraneous: /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single|teaser/i, back: /(back|prev|earl|old|new|zurück|vorige|rückwärts|назад|<|«)/i, images: /\.jpe?g$|\.png$|\.webp$|\.gif$/i, cut: /[?#]/ }, actHREF, prevHREF, imageResult, shouldImagePreview = false, shouldFF = false, UUID = '43D82723-A99E-4BFD-ACDC-B7D8270EE75C'; /* ********************************************************************************************* FIND LINK TO NEXT PAGE */ function analyze() { HREF = split_hash(window.location); PROTOCOL = window.location.protocol; HOST = window.location.host; HOSTPATTERN = make_ipattern(HOST); LRN = false; linkContainer = false; //* STEP 1: Look if the webmaster knew rel= *// // If there is a <link rel="next" ... and it it points to the same origin: Use it. LRN = document.querySelector('link[rel="next"]'); if (!!LRN && LRN.href.indexOf(window.location.origin) < 0) { LRN = false; } // If there is a <A rel="next" ... and it it points to the same origin: Use it. if (!LRN) { linkContainer = document.body.querySelector('A[rel="next"]'); if (!!linkContainer && linkContainer.href.indexOf(window.location.origin) < 0) { linkContainer = false; } } if (!linkContainer) { A = document.body.querySelectorAll('A'); for (i = 1, j = A.length - 2; i < j; [i++]) { // Skip this link if does not have the same origin if (A[i].href.indexOf(window.location.origin) < 0) { continue; } //* STEP 2: Try to find out if the classes and IDs of the parent elements give a hint on the next link. *// actHREF = split_hash(A[i]); prevHREF = split_hash(A[i - 1]); theParent = A[i]; // We limit the search depth to 6 to avoid wading through too many elements searchDepth = 6; stop = false; while (searchDepth > 0 && stop === false && theParent.parentElement && theParent.parentElement !== document.body) { /* if (!theParent.parentElement) { // searchDepth = 0; break; } */ theParent.classAndId = theParent.className + ' z ' + theParent.id; searchDepth = searchDepth - 1; // This is quite probably the wrong link. // May be this should be done outside of the loop because some navigations contain classes like "prev next" in the parent elements if (theParent.classAndId.match(regexps.back)) { stop = true; break; } // If we don't get "hard evidence" we don't want links in print or discussion things if (theParent.classAndId.match(regexps.extraneous)) { // stop = true; break; } // this could be the next link, if there is no better match later if (theParent.classAndId.match(regexps.nextLink)) { linkContainer = A[i]; } theParent = theParent.parentElement; } //* // seems this hurts more than it is worth the time it saves if (stop) { continue; } // stop = false; //*/ //* STEP 3: Look for numbered pagination like e.g. in forums *// if (is_number(A[i].textContent) && A[i].textContent > 0) { if (is_number(A[i - 1].textContent) === false || (A[i - 1].textContent - 0 === 0)) { if (is_number(A[i + 1].textContent) && A[i + 1].textContent > 0 && actHREF !== HREF && A[i].textContent !== '1') { linkContainer = A[i]; continue; } } if (is_number(A[i - 1].textContent) && A[i - 1].textContent > 0) { // if the previous link is to the page where we are right now it is quite possible that this is the link to the next page if (prevHREF === HREF) { linkContainer = A[i]; continue; // this is for skipped pagination where the actual page is not linked, so the link to the previous page is 2 lower than that to the next page. } else if ((A[i].textContent - A[i - 1].textContent - 0) === 2) { linkContainer = A[i]; continue; } } // I don't know anymore why this was important, but it breaks somewhere if this is not in. if (prevHREF === HREF) { linkContainer = A[i]; continue; } } //* STEP 4: Match IDs. Almost as good as rel, therefore: If there is a good ID, we take it and stop searching *// if (A[i].id) { if (A[i].id.match(regexps.nextLink)) { linkContainer = A[i]; break; } } //* STEP 5: CLASSes are weaker than ID, so store but don't stop. If there is no better Link we take it. *// if (A[i].className) { if (A[i].className.match(regexps.nextLink)) { linkContainer = A[i]; continue; } } //* STEP 6: We are down to textContent - quite weak, but better than nothing *// if (A[i].textContent) { if (A[i].textContent.replace(/\s+/g, ' ').length < 25 && A[i].textContent.match(regexps.nextLink)) { linkContainer = A[i]; continue; } } //* STEP 7: TITLE, even weaker ... *// if (A[i].getAttribute("title")) { if (A[i].textContent.replace(/\s+/g, ' ').length < 25 && A[i].getAttribute("title").match(regexps.nextLink)) { linkContainer = A[i]; continue; } } } } // Comment this out if you don't want a hint about the next link // set borders _and_ outlines because in most cases websites don't alter both at the same time if (!!linkContainer) { linkContainer.style.border = '1px solid #FF0000'; linkContainer.style.outline = '1px solid #FFff00'; if (!document.querySelector('a[rel="next"]')) { linkContainer.setAttribute('rel', 'next'); // Experimental: Maybe the Fast Forward button reacts on that at some time in the future ... probably not. var evt = document.createEvent('Event'); evt.initEvent('load', false, false); window.dispatchEvent(evt); } } } function doFastForward() { /* Hard coded exceptions for fucked up search engine pages */ var temp = null; /* extra saussage for startpage.com */ if (HOST.indexOf('startpage.com') > -1) { temp = document.querySelector('#nextnavbar > form > a > span.i_next'); console.log('startpage.com fucked up'); temp.click(); return false; } /* Normal stuff */ /* we've got a hard link rel */ if (LRN && LRN.href) { window.location.href = LRN.href; return false; } if (!!linkContainer) { linkContainer.click(); } } var handleKeyDown = function (e) { if (e.key === "PageDown" && shouldFF) { doFastForward(); } // Don't space2next if the focus is in an editable area // Did I forget something? if (/INPUT|SELECT|TEXTAREA|CANVAS/.test(e.target.tagName) || e.target.isContentEditable || document.designMode === 'on') { return; } // ToDo: If there are linked imageExtensions, add the logic to show them // If the page is already scrolled to the bottom and the user hits space, go to the next page if a link was found. if (e.keyCode === 32 && shouldFF) { doFastForward(); } }; window.addEventListener('scroll', function (e) { // don't analyze if the user did not scroll to the bottom if (window.scrollY >= getScrollMaxY() && window.scrollY > 0) { analyze(); shouldFF = true; } else if (window.scrollY < getScrollMaxY()) { console.log(shouldFF); shouldFF = false; } }, false); window.addEventListener('keydown', handleKeyDown, false); })(window);
- Copy the code into a decent text editor of your choice (Please make sure your editor is set to UTF-8 or you will get problems)
- Save it as restoreSpaceBar.user.js (yes, with 2 dots.) to some place where you can find it again
- Open vivaldi://extensions
- Drag and drop that file on (in?) the page (you might need to enable the developer mode on the extensions management page before)
- Confirm that you want to install it. That’s it.
Warning: It is still a work in progress and may break things.
If you have any idea how to improve it:
Feel free to leave a comment 🙂
PS: I totally forgot to put some tracking code into the monster RegExp 😀