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 😀

Some quick Vivaldi Javascript hacks to add additional functions

Warning: Do not apply this code to a current version of Vivaldi. It will not work anymore because the Vivaldi code has changed!

 

As follow-up to the previous entry about Hacking the CSS, this time we add some new functions to Vivaldi. The Idea is based on the posts you can find at http://habrahabr.ru/users/23rd/topics/ (in Russian, Google translate helps if you don’t speak Russian). I changed them a bit to avoid some errors I saw in the debugging console and to make sure that they only do what they should (there were some minor glitches where unexpected things happened if you clicked in the wrong place – especially if you run the browser in UI debugging mode)

I ported 2 of the scripts:

“Click to minimize/hide the tab” which deactivates the current tab and shows the last active tab. (don’t click at the [x] – that still closes the tab ;))

“Right-click [+] button to paste and go in a new tab.

 

For testing reasons you can deactivate each of them by removing the first slash at the leading //*.

How to …?

Look in the application folder of Vivaldi for browser.html in the ressources/vivaldi folder and open it with a text editor.

Insert after the string and save it.

Create a folder in the same directory as browser.html and rename the folder to custom_ui . It should be already in the right place if you followed Method 2 in the previous entry about Hacking the CSS.

Save the following code as custom.js into the new folder:

 

(function () {     // get the whole browser UI. Do this only once!     var browser = document.body.querySelector('#browser');          // we need to emulate a click sometimes. Do this only once!     var dispatchMouseEvent = function (target, var_args) {         var e = document.createEvent("MouseEvents");         e.initEvent.apply(e, Array.prototype.slice.call(arguments, 1));         target.dispatchEvent(e);     };      /*     Single click on a tab to hide it and show the last active tab.      */ //*     var list = [];     function listTabs() {         var tabs = browser.querySelectorAll('#tabs>.tab');         list = [];         for (var i = 0; i < tabs.length; i++) {             list.push(tabs[i]);         }         list.sort(function (a, b) {             return recent(b) - recent(a);         });     }          function recent(tab) {         var page = document.querySelector('.webpageview webview[tab_id="' + tab.dataset.tabId + '"]');         if (page) {             page = page.parentNode.parentNode.parentNode.parentNode;             return parseInt(page.style.zIndex);         }         return 0;     };      function startAction(e) {         for (var i = 0; i < e.path.length; i++) {             // clicking on tab group switches should not fire, neither should clicking anywhere in the webpageview             if (e.path[i].classList && (e.path[i].classList.contains('tab-indicator') || e.path[i].classList.contains('webpageview'))) {                 break;             }             if (e.path[i].classList && e.path[i].classList.contains('active')) {                 var active = browser.querySelector('.tab.active');                 listTabs();                 dispatchMouseEvent(list[1], 'mousedown', true, true);                 break;             }         }     };          browser.addEventListener('mousedown', function (e) {         // Make sure that it fires on left mouse click only         //  e = e || window.event;         switch (e.which) {         case 1:             startAction(e);             break; // left         case 2:             break; // middle         case 3:             break; // right         }     }); //*/       /*     Right click on plus-button to paste and go      */ //*     var isItMouse = false; // Exclude responses from keyboard      //Tweak for paste in this input-field     var hiddenInput = document.createElement("input");     hiddenInput.type = "text";     browser.appendChild(hiddenInput);     hiddenInput.style.width = "0px";     hiddenInput.style.height = "0px";     hiddenInput.style.display = "none";      browser.addEventListener('contextmenu', function (e) {         // Area near square         if (e.target.className.toString().indexOf('newtab') > -1) {             isItMouse = true;             document.execCommand('paste');             return;         }         // Plus-symbol         // changed to parentElement instead of parentNode         if (e.target.parentElement.className.indexOf('newtab') > -1) {             initPaste();             return;         }         // Square         if (e.target.parentElement.parentElement.className.indexOf('newtab') > -1) {             initPaste();             return;         }     });      function initPaste() {         isItMouse = true;         hiddenInput.style.display = "block";         hiddenInput.focus();         document.execCommand('paste');     }      document.addEventListener('paste', function (e) {         if (isItMouse) {             isItMouse = false;             var url = e.clipboardData.getData('text/plain');             hiddenInput.style.display = "none"; //hide input-field for pasting              var re = new RegExp('\\r\\n', 'g'); // Delete newline characters             url = url.replace(re, '');                          // Search engines             var searchEngine = 'https://google.com/webhp?q=';             // var searchEngine = 'http://yandex.ru/search/?text=';             // var searchEngine = 'https://duckduckgo.com/?q=';             // ... or insert search string of your favorite search engine                          var active = browser.querySelector('.tab.active');             var webview = document.querySelector('#webview-container webview[tab_id="' + active.dataset.tabId + '"]');              if (url.length > 0) {                 if (checkUrl(url)) {                     webview.executeScript({                         code : "window.open('" + url + "','_blank')"                     });                 } else if (checkUrlWithoutProtocol(url)) {                     webview.executeScript({                         code : "window.open('http://" + url + "','_blank')"                     });                 } else {                     webview.executeScript({                         code : "window.open('" + searchEngine + url + "','_blank')"                     });                 }             }             //    console.log(url)         }     });          //Check url     var patternUrl = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?/#]\S*)?$/i;     var patternUrlWithout = /^(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,3})).?)(?::\d{2,5})?(?:[/?/#]\S*)?$/i;          //url with protocol     function checkUrl(str) {         return patternUrl.test(str);     }          //url without protocol     function checkUrlWithoutProtocol(str) {         return patternUrlWithout.test(str);     } //*/ })();

Some quick Vivaldi panels CSS hacks for better readability or accessibility

Deprecated. While the basic technique would still work, too much has changed in the new Versions of Vivaldi and now we have Themes.

While I generally like the design idea that stands behind behind Vivaldi, I have some problems with it. I don’t know which screens the designers use or if they are calibrated, if their eyesight is perfect or not, but the contrast of some elements in the default light and dark themes is a little bit too low for me to feel comfortable, especially in the panels. Luckily the whole UI of Vivaldi is based on web technology—or in non marketing speak: HTML, JavaScript and CSS—it is quite easy to hack.

b2ap3_thumbnail_Vivaldi-Panel-CSS-Mod_20150702-163235_1.png

Yes I resized the screenshot deliberately to a smaller size. As you can see everything in the panels stays visible, despite the smaller size . You can try that with the default interface, but don’t use sharpening while resizing 😉

The following hack does not solve all problems I see, e.g. the less than sufficient contrast of the Tab titles in the dark interface if you do not use transparent tabs, but you can see it as a first step for your own experiments.

 

How to …?

Method 1:

Look for common.css in the application folder of the browser and open it with a text editor.

Add the following to the end of it (make sure you add it at the end!)

Method 2:

Look in the application folder of Vivaldi for browser.html in the ressources/vivaldi folder and open it with a text editor.

Insert <link rel="stylesheet" href="custom_ui/panel.css" /> after the string <link rel="stylesheet" href="style/common.css" /> and save it.

Create a folder in the same directory as browser.html and rename the folder to custom_ui

Save the following code as panel.css into the new folder:

 

#switch {     background: #dfdfdf;     box-shadow: 2px 0 0 -1px rgba(255, 255, 255, .3), -2px 0 0 -1px rgba(255, 255, 255, .3); } .ui-dark #switch {     background: #333333;     box-shadow: 2px 0 0 -1px rgba(255, 255, 255, .9), -2px 0 0 -1px rgba(255, 255, 255, .9); } .ui-light #switch button svg {     fill: rgba(0, 0, 0, .7) } .ui-dark #switch button svg {     fill: rgba(255, 255, 255, .7) } .ui-dark #panel_switch {     background-color: #3f3f3f;     box-shadow: 2px 0 0 -1px rgba(0, 0, 0, .4), -2px 0 0 -1px rgba(0, 0, 0, .4); } .ui-light .panel-group {     box-shadow: 2px 0 0 -1px rgba(0, 0, 0, .2), -2px 0 0 -1px rgba(0, 0, 0, .2); } .ui-dark .panel-group {     box-shadow: 2px 0 0 -1px rgba(255, 255, 255, .2), -2px 0 0 -1px rgba(255, 255, 255, .2); } .ui-light #switch button.active {     box-shadow: inset 1px 1px 1px 0px rgba(0, 0, 0, .3); } .ui-dark #switch button.active {     background-color: #464646;     box-shadow: inset 1px 1px 1px 0px rgba(255, 255, 255, .3); } .ui-light #switch button:not(.active):hover {     background: #eeeeee; } .ui-dark #panels-container.left #panel_switch,  #panels-container.right #panel_switch.off {     background-repeat: no-repeat;     background-position: center;     background-size: contain;     /* arrow-left white */     background-image: url("data:image/svg+xml;utf8,"); } .ui-dark #panels-container.left #panel_switch.off, #panels-container.right #panel_switch {     background-repeat: no-repeat;     background-position: center;     background-size: contain;     /* arrow-right white */     background-image: url("data:image/svg+xml;utf8,"); } .ui-dark .addbookmark-cardwrapper, .ui-dark .downloaddialog-cardwrapper, .ui-dark .notes-cardwrapper {     box-shadow: inset 1px 1px 0 0px rgba(255, 255, 255, .2), 2px 2px 0px 1px rgba(0, 0, 0, .4); } /* Dark UI: Make the color of the font a bit lighter in the tab .ui-dark #tabs .tab{color:rgba(255,255,255,.6);  */  .ui-dark #tabs .tab {     color: rgba(255, 255, 255, .8);     background-color: #333; } .ui-dark #tabs .tab:hover {     color: rgba(255, 255, 255, 1);     box-shadow: inset 1px 1px 1px 0px rgba(255, 255, 255, .8); } .ui-dark #tabs .tab:not(.active):hover {     background-color: #444; }  .ui-dark #tabs .tab.unread.tab-transparent {     box-shadow: inset 0 -2px 0 0 #CCCCCC,inset 0 -4px 0 0 rgba(0,0,0,.2); }  .ui-dark  .close {     background-image: url(/resources/close-white.png); }  .ui-dark  #footer .paneltogglefooter svg {     fill: rgba(255, 255, 255, .9) }  .ui-dark #footer #status_info {     color: #ffffff; } /* Make the free space right to the tabs draggable if tab container is at top #browser:not(.mac) .tab-spacer,#tabs .tab,#zoom_control,.tab-group-indicator,.topmenu,button,input{-webkit-app-region:no-drag}  Only needed for builds lower than 1.0.230.#, see help > about */ /*  #browser:not(.mac) .top .tab-spacer{ -webkit-app-region:drag } */  .vivaldi-tree .tree-row.folder .folder-item-count {     opacity: .5;     color: rgba(0, 0, 0, 1) } .ui-dark .vivaldi-tree .tree-row.folder .folder-item-count {     opacity: .5;     color: rgba(255, 255, 255, 1) } .hasfocus .bookmark-bar button:hover img.icon {     background-color: rgba(255, 255, 255, 0);     border-color: rgba(255, 255, 255, 0); } .bookmark-bar button img.icon {     background-color: rgba(255, 255, 255, 0);     border-color: rgba(255, 255, 255, 0); } .bookmark-bar button {     border: 1px outset rgba(0, 0, 0, 0) } .hasfocus .bookmark-bar button:hover {     -webkit-filter: saturate(150%) brightness(150%); } /* a bit of blue tint ... */  .vivaldi-tree .tree-row:not([data-selected]):hover {     background-color: rgba(0, 110, 215, .4); } .ui-dark .vivaldi-tree .tree-row:not([data-selected]):hover {     background-color: rgba(0, 110, 215, .4); } .vivaldi-tree .tree-row[data-selected] , .vivaldi-tree .tree-row[data-selected]:hover{     color: #eee;     background-color: #006ed7;     border: 0 } .vivaldi-tree .tree-row[data-selected]:hover{     color: #fff; } .vivaldi-tree .tree-row[data-selected][data-nofocus] {     background-color: rgba(54, 95, 135, .2);     color: inherit;     border: 0 } .ui-dark .vivaldi-tree .tree-row[data-selected][data-nofocus] {     background-color: rgba(54, 95, 135, 1);     color: inherit;     border: 0 }  /* a bit of blue tint ... */  .ui-dark ::-webkit-scrollbar {      width: 15px;      height: 15px; } .ui-dark ::-webkit-scrollbar-button {       background-color: #333;      border: 1px solid #555; } .ui-dark ::-webkit-scrollbar-track {       background-color: #000000; } .ui-dark ::-webkit-scrollbar-track-piece {      background-color: #333333; } .ui-dark ::-webkit-scrollbar-thumb {      height: 50px;      background-color: #666; } .ui-dark ::-webkit-scrollbar-corner {      background-color: #999; } .ui-dark ::-webkit-resizer {      background-color: #666; } .ui-dark ::-webkit-scrollbar-button {     background-repeat:no-repeat;     background-position:center;     background-size: contain; } .ui-dark ::-webkit-scrollbar-button:vertical:increment {     /* arrow-down white */     background-image: url("data:image/svg+xml;utf8,"); } .ui-dark ::-webkit-scrollbar-button:vertical:decrement {     /* arrow-up white */     background-image: url("data:image/svg+xml;utf8,"); } .ui-dark ::-webkit-scrollbar-button:horizontal:increment {     /* arrow-right white */     background-image: url("data:image/svg+xml;utf8,"); } .ui-dark ::-webkit-scrollbar-button:horizontal:decrement {     /* arrow-left white */     background-image: url("data:image/svg+xml;utf8,"); }

 

edit: There is a follow up about Hacking the UI with Javascript to add functions.

“Du” vs “Sie” – oder: Wie förmlich darf’s denn sein?

Note for English speaking readers: It is about the differences in addressing people, formal or informal, in the German translation of https://vivaldi.com and if we should use the formal (but not unfriendly) “Sie”, or the personal “Du”. I know, this is easy for you, because you can use “you” for both and I can understand that you think we are crazy (and that assumption is probably correct), but that’s how we are. If you want to know how it works here, take a look at this:

http://bibliobrary.net/2010/01/04/when-to-use-du-and-sie/

btw: I have been in the USA several times and while they use the you, you always know if it is used in a formal or non formal way. You will earn some looks of disapproval if you forget the Ma’am or the Sir in Huston,Tx or somewhere in the Bible Belt if you talk to a female who is not clearly a little girl or to a male person being older than you or carrying a weapon. No such problems in California – unless you are in a region with a high percentage of Mexicans. It is not that easy even in the USA, there are some lines you don’t want to cross. 😉

 

Es gibt Sachen, die meine Neugier anregen und denen ich auf den Grund gehen möchte, denn oftmals ist die wahrgenommene “Wirklichkeit” nicht gleich  dem, wie die Dinge tatsächlich sind. Diesmal geht es darum, wie “förmlich” wir Deutschen sind. Ausgelöst wurde es von einer hart (aber fair) geführten Diskussion, ob die Vivaldi.com Webseite die Besucher mit “Sie” oder mit “Du” ansprechen sollte.

Ich gebe zu, dass ich auf Grund meines Alters voreingenommen bin (meine jugendliche Prägung fand vor MTV und dem WWW statt), aber das Selbe gilt natürlich auch für jüngere Personen, die eigentlich keine Welt ohne WWW kennen und schon deutlich durch die wahrscheinlich aus dem englischsprachigen Raum übernommene “You” → “Du” Umgangsform beeinflusst sind.

Genug geschwafelt, ab zu den “Fakten”:

Wenn man wissen will, was die Leute in Deutschland so anstellen, befragt man am besten Google (Marktanteil bei den Suchen >90%, deshalb “optimieren” “alle” ihre Webseiten dafür – und Google ist ziemlich gut im analysieren) – also fleißig ab den Suchparametern geschraubt, mittels site:.de auf Seiten mit .de als TLD eingeschränkt und eine wörtlich zu nehmende Suche mittels &lit=1 eingestellt.

Hier die Ergebnisse, die ich bekommen habe:

https://www.google.de/search?q=Sie+site:.de&lit=1 “Ungefähr 1.370.000.000 Ergebnisse” für “Sie”
https://www.google.de/search?q=Du+site:.de&lit=1  “Ungefähr 246.000.000 Ergebnisse” für “Du”

Das sieht auf den ersten Blick nach einem klaren Sieg für “Sie” aus – aber ganz so einfach darf man es sich meiner Meinung nach nicht machen:

Zum einen wird “Sie/sie” auch für andere Sachen verwendet als nur die persönliche Anrede und zum anderen kann man mit ziemlicher Sicherheit vermuten, dass sich auf einem großen Teil der Seiten, wo “man” sich duzt (z.B. nahezu 100% aller Foren) oftmals Sätze befinden wie “Verfolgen Sie uns auf Twitbook”.

Um es mir etwas einfacher zu machen (Haben Sie ernsthaft erwartet, dass ich hier eine wasserdichte statistische Analyse anfertige? Nicht wirklich, oder? 😉 ) habe ich kurzerhand die “Du” Seiten von der Anzahl der “Sie” Seiten abgezogen – das macht aber immer noch eine abgerundete Milliarde Seiten, die die Wörter “Sie/sie” beinhalten. Folgend begebe ich mich vollständig in den Bereich der Spekulation und nehme an, dass auf 50% aller Seiten, auf denen “Sie” in irgendeiner Form verwendet wird, dies nicht aus Gründen der Anrede geschieht. Übrig bleiben immer noch eine halbe Millarde Seiten. Der “Sieg” ist knapper, aber immer noch deutlich.

 

Könnte das mit der Altersstruktur unseres Landes zusammenhängen? 

Für solche Statistikfragen gibt es einschlägig bekannte Seiten wie Statista
http://de.statista.com/statistik/daten/studie/71539/umfrage/bevoelkerung-in-deutschland-nach-altersgruppen/ 
Wer will, kann sich gerne dort durchwühlen und nachrechnen. Ich bin faul und lasse das lieber andere machen, also habe ich einfach bei Wolframalpha gefragt, wie alt wir Deutschen im Schnitt sind:
http://www.wolframalpha.com/input/?i=Germany+%7C+median+age
Für mich erklären 43,8 Jahre einiges:

Ein großer Teil der Bevölkerung erlebte seine “prägenden Jahre” bzw. fast die Hälfte seiner Gesamtlebenszeit vor der verbreiteten Einführung des persönlichen Computers, vor MTv, vor dem WWW und vor dem “Handy™”. Damals wurde man vom größten Teil der Bevölkerung tatsächlich gesiezt (sogar unsere Lehrer haben uns ab der 5 Klasse auf dem Gymnasium gesiezt – und wir sie natürlich auch) so lange man sich nicht gut kannte oder wirklich befreundet war (nicht nur auf einen noch nicht existierenden “Freund” Button geklickt hatte). Die Flower Power Generation war zwar schon vorbei und dadurch hatten sich glücklicherweise schon einige Umgangsformen gelockert, aber dennoch hielten die meisten hartnäckig am “Sie” fest – und das waren nicht nur die Ewiggestrigen: Selbst die ’68er redeten Leute, die sie nicht kannten, mit “Sie” an, außer sie wollten gezielt “Das Establishment” provozieren.

Solche Verhaltensweisen legt man nur relativ langsam ab, ich spreche da aus Erfahrung.

 

Zurück zum Anfang:

Sollte man auf der Vivaldi.com Seite “Sie” oder “Du” verwenden?

Für mich persönlich keine Frage. Seiten von Firmen, die nicht gerade Betreiber von sozialen Netzwerken sind, sprechen ihre Kunden im Allgemeinen mit “Sie” an (OK, IKEA nicht – außer es kommt eine Durchsage:  „Gesucht wird der Halter des Fahrzeugs mit dem Kennzeichen DU – DA 496. Bitte melden Sie sich umgehend an der Information!“ 😀 ). Das ist zwar formal, muss aber dennoch nicht distanziert sein. Der Grat zwischen anbieten und anbiedern, zwischen persönlich ansprechen und zu kumpelhaft – und somit meiner Meinung nach eher unprofessionell – ist ein sehr, sehr schmaler. Die Gefahr, auf den Empfindlichkeiten der unbekannten Personen herumzutrampeln, die die Website besuchen, ist beim “Du” meiner Meinung nach größer als beim “Sie” – aber das ist nicht wirklich messbar, nur von jedem einzelnen fühlbar, dennoch vermute ich einen Grund hinter der Verwendung des “Sie” auf folgenden Seiten:

 

Für die Vivaldi.net Seite (also die mit den Foren und Blogs) ist natürlich das “Du” völlig OK.

 

Ein wenig Literatur zum Thema:

 

Letztendlich stellen sich folgende Fragen:

  • Würde ich einen Fremden, über den ich nichts weiß, außer dass er mein Produkt verwendet, am Telefon mit “Du” anreden?
  • Verärgert es eine unbekannte “Du” Person mehr wenn man sie mit “Sie” anspricht, als eine “Sie” Person, die man mit “Du” anspricht?
  • Ist der Kunde nicht der König, sondern das Produkt? (Facebook)
  • Will man ernst genommen werden?
  • Ist man in der Lage eine vollständige Image Kampagne mit einer weitergehenden Botschaft und einer gezielten kleinen Provokation durchzuziehen? (IKEA, Lebensstil)

Kann man alle Fragen mit “Ja” beantworten, ist das “Du” kein Problem – sobald man eine Frage mit Nein beantwortet, sollte man sich meiner Meinung nach wirklich überlegen ob man bereit ist, diese Grenze zu überschreiten.

 

… und nun möge die Schlacht um das “Du” und das “Sie” in den Kommentaren beginnen 😀

 

Sometimes You Might Not Need jQuery

While I was idly surfing through the web I stumbled over an older blog post from 2011 that featured Earthquakelet, a bookmarklet that lets you experience the funny side of an earthquake by shaking the contents of the window. (Disclaimer: Real earthquakes are no fun!)

While I am sure that the author can do much better when writing code (In fact he proved that he can often enough) this time he simply hacked things together for the laughs. So, nothing against the author—it is totally OK to do as he did—but it is a nice example that, depending on your audience, you might not need jQuery to get things done. The code might be shorter and often faster even if you use the famous vanillaJS library …

 

The following example is provided only to show that this is no false claim and because it is so nice and short.

 

Earthqualelet, a bookmarklet found on this page

917 bytes (635 minified) of code plus additional 72357 bytes download of jQuery

(function(w,url,hasjq) {
     if (!(hasjq = w.jQuery)) {
         var s = document.createElement('script');
         s.src = url;
         (document.getElementsByTagName('head')[0] ||
          document.getElementsByTagName('body')[0]).appendChild(s);
     }     (function go() {         if (!w.jQuery) {
             return w.setTimeout(go, 200);
         }         var $ = w.jQuery;
         if (!hasjq) $ = $.noConflict();
         var $s = $('p,div,img,span,strong,em');
         (function shake() {
             $s.each(function() {
                 if (this.shift) this.shift = 0;
                 else this.shift = (2*(Math.floor(Math.random() * 2)) - 1) * (Math.floor(Math.random() * 4));
                 $(this).css('margin-left', this.shift);             });
             w.setTimeout(shake, 80);
         })();
     })();
 })(window, 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js');

 

The “minified” bookmarklet:

javascript:(function(w,url,hasjq){if (!(hasjq=w.jQuery)){var s=document.createElement('script');s.src=url;(document.getElementsByTagName('head')[0]||document.getElementsByTagName('body')[0]).appendChild(s);} (function go(){if (!w.jQuery){return w.setTimeout(go, 200);}var $=w.jQuery;if(!hasjq)$=$.noConflict();var $s=$('p,div,img,span,strong,em');(function shake(){$s.each(function(){if(this.shift)this.shift=0;else this.shift=(2*(Math.floor(Math.random()*2))-1)*(Math.floor(Math.random()*4));$(this).css('margin-left',this.shift);});w.setTimeout(shake, 80);})();})();})(window,'http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js');

 

 

Hand coded Earthquakelet, no jQuery

400 bytes (223 minified)

(function (w) {
     var ele = document.querySelectorAll('p,div,img,span,strong,em'),
         len = ele.length,
         i = 0,
         shift = 0,
         M = Math;
     (function quake() {
         for (i = 0; i < len; i++) {
             shift = M.floor(M.random() * 6) - 3;
             ele[i].style.marginLeft = shift+'px';
         };         w.setTimeout(quake, 80);
     })();
 })(window);

 

The “minified” bookmarklet:

javascript:(function(w){var e=document.querySelectorAll('p,div,img,span,strong,em'),l=e.length,i=0,s=0,M=Math;(function q(){for(i=0;i<l;i++){s=M.floor(M.random()*6)-3;e[i].style.marginLeft=s+'px';};w.setTimeout(q, 80);})();})(window);

 

As you can see, the hand coded bookmark is even shorter than the bookmarklet using jQuery. It works in every modern browser that supports bookmarklets and in older browsers that support querySelectorAll. So if you don’t need to support IE7 or older, you should be quite safe to go.

 

hat tip to Orinoco for the Math improvement – see comments below

Deadline

Beware of the

 

Yes, it is an SVG image 😀

 

It seems that Firefox and Internet Explorer have a problem with raw SVG code as image source. I had to URL encode it (base64 would have worked too, but it is about 30% bigger and gzips worse).

Dear Mozilla and Microsoft programmers:

It is legit and standard conform to use the raw stuff. “The Real Opera(TM)” (aka <14) can do that since version 7 (or may be even earlier), Chrome since about version 7 (that is several years later than Opera 7), and Vivaldi since the beginning too. Fix it!

PO2JSON converter for Vivaldi translation purposes

Outdated. Use André’s online converter instead.

You can find it here: https://an-dz.github.io/PurePO2JSON/


Translating Vivaldi was always a bit difficult when it came to string lengths or if we needed to see exactly where the string is and if it is the right one in that place. Luckily An_dz invested some time to write a nice little JavaScript for the dev console that can convert files from the POEDIT format used for translation to the JSON format used in Vivaldi, which works well …

… but I am a lazy guy and did not want to copy the file to the console each time I needed to test new translations, so I modified some (few) lines in his script, added some file read/write stuff and stuffed it into a simple webpage. When run locally, it allows to load the .po file,  convert and save it as messages.json file. This file can be used with Vivaldi to test the translations.

 

Alternatively you can use ludev‘s online converter which is based on this script. It can either download the PO file directly from Transifex, convert it for you and store it to your computer for further use – or you can use your local PO file for the conversion.

The online converter is availlable at http://ludev.rocks/vivaldipo2json/.

 

The code:


<!DOCTYPE html> <html>      <head>         <meta http-equiv="Content-Type" content="text/html; charset=utf-8">         <title>Convert Vivaldi PO to JSON</title>         <style>             body {                 font-family: sans-serif;             }             textarea {                 width: 90%;                 height: 600px;             }          </style>     </head>      <body>         <form>             <div>                 <p id="status">File read</p>                 <textarea id="output"></textarea>             </div>             <input type="file" id="files" name="files">         </form>         <script>             function convertToJson(file) {                 'use strict';                 // VivaldiPO2JSON v1.1                 // by André Zanghelini (An_dz)                 /* modified by Roland Reck (QuHno) to work with the web page,  				* added some missing "," and "=" 				* added some {}, replaced i++ by i += 1 and moved some var declarations to other places because JSLint was complaining 				* 2016-01-25: changed order of functions so that they are already defined _before_ they are caled. 				* 2016-01-25: replace multiple space by one 				*/                  var textArea = document.getElementById('output'),                     start = 0,                     end = 0,                     i = 0,                     oldStr = '',                     newStr = '',                     s,                     tempStr = '', 					arr = [], 					msgid;                  // Remove useless header stuff                 file = file.replace(/msgid ""\n/, "");                 file = "{" + file.substring(file.indexOf("msgid"));                 // Remove other comments                 file = file.replace(/(#.*\n|"\n")/g, "");                 // Change translation strings to JSON message                 file = file.replace(/msgstr (".*")\n/g, "    {\"message\":$1},");                 // Change msgid so Vivaldi can understand them                 // Special and uppercase chars must be decimal unicode value                 tempStr = file;                 // While we still find "msgid "                 while (start > -1) {                     start = tempStr.search(/msgid /);                     if (start > -1) {                         end = start + tempStr.substr(start).search(/\n/);                         oldStr = tempStr.substring(start, end);                         newStr = oldStr.replace(/msgid /, "");                         s = 0; 						// Q-mod: Replace multiple whitespace by one 						newStr = newStr.replace(/\s+/g, " ");                         // We change the special characters for later manipulation                         newStr = newStr.replace(/([A-Z(){}å\[\]—.,;:\/\\<>\=\+*#@'\|!’%&$“”?\_\-]| )/g, "%$1_");                         // Now we get rid of the newlines which are written as \n                         // Remember that the above added a % in front of \n and an _ after \                         newStr = newStr.replace(/%\\_n/g, "_10_");                         // Add 0 at end of string name and : at end of string                         newStr = newStr.replace(/(.)"/, "$10\":");                         // Replace special chars and uppercase to char code                         while (s !== -1) {                             s = newStr.search(/%/);                             if (s > -1) {                                 newStr = newStr.replace(/%./, "_" + newStr.charCodeAt(s + 1)); 							}                         }                          file = file.replace(oldStr, newStr);                         // Just search where we did not search before                         tempStr = tempStr.substr(end + 1);                     }                 }                  // Replace plurals, start copying msgid                 arr = file.split("\nmsgid_plural");                 file = arr[0];                 for (i = 1; i < arr.length; i += 1) {                     // Take msgid from previous part                     msgid = arr[i - 1].substring(arr[i - 1].lastIndexOf("\n\"") + 1, arr[i - 1].lastIndexOf(":") - 2);                     // remove msgid_plural and replace msgstr[0] in current part                     arr[i] = arr[i].replace(/.*\nmsgstr\[0\] (".*")/, "\n    {\"message\":$1},");                     // Replace other variations                     arr[i] = arr[i].replace(/msgstr\[(\d+)\] (".*")/g, msgid + "$1\":\n    {\"message\":$2},");                     // Join again to main                     file = file.concat(arr[i]);                 }                  // Add msgctxt string                 arr = file.split("msgctxt ");                 file = arr[0];                 for (i = 1; i < arr.length; i += 1) {                     // Add # at begining, this will help our selector                     arr[i] = "#" + arr[i];                     // Find first newline                     end = arr[i].search(/\n/);                     // Find spaces, special chars and uppercase in first line                     newStr = arr[i].substring(1, end - 1).replace(/([A-Z(){}\[\].,;:\/\\<>\=\+*#@'\|!%&$“”?\_\-]| )/g, "%$1_");                     // Replace special chars and uppercase to char code                     s = 0;                     while (s !== -1) {                         s = newStr.search(/%/);                         if (s > -1) {                             newStr = newStr.replace(/%./, "_" + newStr.charCodeAt(s + 1)); 						}                     }                     arr[i] = arr[i].replace(/#.*\n"/, newStr + "_4_");                     // arr[i] = arr[i].replace(/"\n"/, "_4_")                     file = file.concat(arr[i]);                 }                  // Remove double newline                 file = file.replace(/\n\n/g, "\n");                  // Replace last comma to closing bracket                 file = file.substring(0, file.lastIndexOf(",")) + "\n}";                  // Replace on screen and alert                 textArea.textContent = file;                 document.getElementById('status').innerText = "File successfully converted";              }               function saveTextAsFile(fileNameToSaveAs) { 			    'use strict';                 var textToWrite = document.getElementById("output").value,                     textFileAsBlob = new Blob([textToWrite], {                         type : 'application/json'                     }),                     downloadLink = document.createElement("a"); // Create temporary link. No need to add it to the DOM                  downloadLink.download = fileNameToSaveAs;                 downloadLink.innerText = "Download File";                 downloadLink.href = window.URL.createObjectURL(textFileAsBlob);                 downloadLink.click();                 // Do I need to remove the objectURL? 				window.URL.revokeObjectURL(downloadLink.href);             }  			function handleFileSelect(evt) { 			    'use strict';                 var files = document.getElementById('files').files,                     reader = new FileReader();                  if (!files.length) {                     alert('Please select a file!');                     return;                 }                  //var file = files[0];                 // Not the best check but should be sufficient                 if (files[0].name.search(/\.po$/i) === -1) {                     alert('Please select a .po file!');                     files = null;                     return;                 }                   reader.onloadend = function (evt) {                     if (evt.target.readyState === FileReader.DONE) { // DONE == 2                         convertToJson(evt.target.result);                         // Extract file name from e.g. "for_translation_vivaldi-localisation_vivaldipot_de.po" to "de.json" for saving                         var start = files[0].name.search(/vivaldipot/i),                             end = files[0].name.search(/\.po$/i),                             filename = files[0].name.substring(start + 11, end) + '.json';                         saveTextAsFile(filename);                     }                 };                 reader.readAsText(files[0], "UTF-8");             }               document.getElementById('files').addEventListener('change', handleFileSelect, false);         </script>     </body>  </html>

 

Usage:

  • Save the code as VivaldiPO2JSON.html to your computer somewhere where you can find it again
  • Open it in Vivaldi
  • Choose the right PO file
  • Convert it
  • Save the converted file

All mistakes are mine An_dz code works just fine. Feel free to post improvements below 🙂

 

edit: Thanks to An_dz for the changes! (See his comment below) 🙂

Thanks to ludev for providing the online converter.

Some useful JavaScript snippets or libraries found in the net

Image related

Find the most dominant color in an image
ColorFinder, License: WTFPL
Similar to above but a bit heavier
Color Thief, License: CC BY 2.5
EXIF and IPTC data viewer
exif-js, License: MIT

Debugging

Debug inside of the Chromium dev tools console
deb.js, License: MIT
with mere 1,5kB (minified) probably one of the tiniest debuggers ever. Nice display but one little drawback, you have to control the source in order to use it

Before using any of these in an extension a test might be needed which has less impact on the system.

If you know other nice “one trick ponies” that do their job well without relying on other dependencies like jQuery, MooTools etc., feel free to leave a comment.

JSON

Find the differences between 2 JSON files
JSON Diff, License: CC BY-NC
Analyzes the JSON file and shows it as tree with the differences. The page can be easily modified to run purely local.

To be continued …

CSS beautifier

I confess that am a messy coder when it comes to formatting. I edit, insert and delete until the code runs like I want to. This often leads to perfectly readable code – for machines, but not for other humans – so if I decide to publish it, I shouldn’t publish it in the same form I write it.

 

When I was editing my userCSS for vivaldi once again, I messed up the well formatted code the same way as usual. Of course I could have used an editor that formats on the fly, but I don’t like editors that change the formatting while I am coding, all that moving around makes me dizzy. While I was looking for a solution for that problem I found this:

 

CSS Beautifier – http://html.fwpolice.com/css/

 

Simple to use, you can even download it to run it offline in your browser, with a few clicks the code looks just fine and you can change the way it is formatted in a blink. Exactly the simple thing I wanted 😀

 

Helps with the usual one-line-we-save-every-bit code that is presented in some webpages too (Google, I am looking at you!)

“Any man who would letterspace lowercase would steal sheep.”

The headline is rumored to be a quote from the German Typographer Erik Spiekermann paraphrasing American Typographer Frederic W. Goudy‘s

Any man who would letterspace blackletter would shag sheep.

Dear web page designers!

Whoever told you differently:
letter-spacing: -1px or -2px is no good choice, because the word kerning quickly becomes keming with negative letter-spacing and the readability is worse than with the default letter-spacing.

Example of a sentence with completely messed up letterspacing(← Do you see why?).

There is no excuse for it, even Google knows that:

(take a close look at the appearance of the search results page)

The designers of the fonts you use on your web pages put much work and thought into it, please don’t ruin it. If you want a more compact look, change the font stack and use different fonts – but

Please do not mess with letter-spacing. Ever.