Restoring the Old Opera 12 Space Bar Behavior in Vivaldi

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 😀

13 replies on “Restoring the Old Opera 12 Space Bar Behavior in Vivaldi”

  1. I’m not going to try it at this stage because my “fixing” anything I break would just be a big long cursing session – but when you get this tweaked or I get time – whichever comes first – I am SO going to exploit the you-know-what out of your labors!! Thanks!!

  2. When I wrote “it may break things” I didn’t mean the browser, but may be (unlikely, but still) a webpage or two. Additionally you can always disable or even uninstall it on the extensions page.

    The warning is more along the line: If you should notice something on a webpage that doesn’t work while this script is active, simply disable or delete it. 🙂

    Oh, and I completely forgot to mention, that it even improves the default Fast forward of the browser a bit because my script catches some more of the possible hints where the next page may be than the Vivaldi implementation as it is now 😀

  3. Ha! My very first script!!

    Or, rather, my very first Linux script that isn’t actually mine but that I put in a text editor and installed without crashing anything – but STILL! COOL! Thanks QuHno!

  4. LOL – I thought it didn’t work but all of my other problems came together to make it APPEAR it wasn’t advancing to “next page” – i.e. invisible progress bar and slowness sort of ganged up on me BUT, I am happy to report that it didn’t execute the additional spacebar hits I tried before I realized it was just being really, really slow. 😀

    So, NOW I’m going to see about making my first css file to fix that page-load indicator so I can see it…wish me luck!

  5. Yes, hitting the space bar again without allowing for a little pause when it has reached the bottom might sometimes not work immediately. The scripts does not start to analyze the page before the scrollbar is at the bottom. I did that by intend to avoid as much interference with page loading as possible.

    Apart from that: Wait for the next snapshot before editing the progress bar – you might be in for a nice surprise 🙂

  6. I figured they’d change it so as not to disenfranchise us old folks 😉 – it’s such an easy fix – but I want to play with css a little bit anyway. I can always undo it, right?

    I intend to keep notes and insert comments to mark my changes – and, of course, I’ll have the backups if I REALLY mess it up…

    But first, I’m going to have a go at “translating” your spacebar script.

  7. Yes, especially if you do it the way I described in
    https://vivaldi.net/de/blogs/entry/some-quick-vivaldi-panels-css-hacks-for-better-readability-or-accessibility
    as “method 2”.
    Hint: Keep a copy of your added files because they will be lost with the next update and need to be re-added.

    Of course you can edit resourcesvivaldistylecommon.css directly, but that might get messy soon and with so many rules it might not be to easy to find the changes again …

  8. I didn’t know you can install userscripts directly to vivaldi. Chrome and Opera stop you from doing that.

  9. Oh, you are right – I didn’t test that before. In those browsers you need a helper Extension like Tampermonkey, or Violent Monkey or however the ported Greasemonkey Extensions are named now.

    Side note: The naked Chromium (aka the base on which Chrome, Opera and Vivaldi are built) supports it like Vivaldi does, so Google and Opera switched it deliberately off.

    I could of course re-pack the script to an extension, but I prefer userJS as long as it works, because it doesn’t open another process and such has a way lighter RAM and CPU footprint.

  10. Updated the code a bit to make sure that the “weak” indicators don’t run astray.

  11. Note: Vivaldi now seems to do this on its own.

    By the way, regarding:
    [quote]Nothing special so far, almost all browsers today do that (guess from which browser they copied that function ;))[/quote]
    I’m guessing Lynx? =P It’s true that other browsers copied many features from old Opera, but using the spacebar to advance a page existed as far back as text mode browsers. (And before them, things like UNIX manual pages.) However, I’ll grant that not that all graphical browsers had it from the start. I just tested NCSA Mosaic 1.0 and 2.1 just now, and spacebar doesn’t advance the page.

  12. No, Lynx did not really have it. Back in the time there were meta elements that gave the browsers hints where the next pages are, a full set even, like home, previous, next, directory up, search etc. pp. and browsers like Lynx (and of course old Opera too) could and did use them.

    Sadly the web-“masters” never really used it because those were invisible things, so nowadays we need to *search* for the link to the next page or related pages.

    In the meantime I’ve built an extension for my private use which improves the default browser behavior a lot because it finds more links and makes less mistakes.

    Sometimes I really miss the old days when the web was a little bit less fancy but more semantic and navigable.

Comments are closed.