Продолжаем расширять функционал браузера Vivaldi

Вот уже полгода прошло с появления на свет самой первой версии браузера Вивальди.
За этот период группа разработчиков многое успела сделать и добавить, радуя пользователей еженедельными билдами. Прогресс виден налицо. А совсем скоро подтянется и Technical Preview 4.

Но многим пользователям до сих пор не хватает каких-то фич. И пока разработчики браузера заняты совсем другим, мы попытаемся кое-что реализовать сами.

image

Эта статья является неким сборником некоторых функций, расширяющих возможности UI. Что-то подсмотрено на официальном форуме Vivaldi, что-то изменено, а что-то написано собственноручно. В статье мы попробуем реализовать фичи из других браузеров и вернём пару привычных вещей, которых некоторым так не хватает.

А под катом мы начнём с самого вкусного.


Именно так называлась галочка в настройках вкладок Opera Presto. Она позволяла при повторном нажатии на активную вкладку открывать предыдущую. Для некоторых это была весьма полезная вещь, которая со временем, к сожалению, пропала.
Так давайте же теперь вернём её в Вивальди.
Вот как выглядела она в Опере:

Иллюстрация:
image


Как и в прошлой статье здесь работаем также по старой схеме — только уже создаём не css, а js файл с каким-нибудь названием, например, «last-tab.js». И вставляем туда код:

last-tab.js
var browser = document.body.querySelector('#browser');
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;
};

var dispatchMouseEvent = function(target, var_args) {
        var e = document.createEvent("MouseEvents");
        e.initEvent.apply(e, Array.prototype.slice.call(arguments, 1));
        target.dispatchEvent(e);
};

browser.addEventListener('mousedown', function(e){
    for (var i = 0; i < e.path.length; i++) {
        if (e.path[i].className.indexOf('active') > -1) {
            var active = browser.querySelector('.tab.active');

            listTabs();
            dispatchMouseEvent(list[1], 'mousedown', true, true);
            break;
        }
    }
});



А в browser.html после

    


вставляем строчку:

    


Теперь при повторном нажатии на активную вкладку будет открываться предыдущая.
У Яндекса же есть свой альтернативный взгляд на то, как можно использовать щелчок по активной вкладке.
За эти полгода я часто слышал в комментариях и на форумах просьбы пользователей сделать эту фичу. Сам я Яндекс.Браузером никогда не пользовался, но по описанию всё, в принципе, кристально ясно что требуется.

tab-to-top.js
// Click on active tab to scroll top
var browser=document.body.querySelector('#browser');
browser.addEventListener('click', function(e){
        for (var i = 0; i < e.path.length; i++) {
                if (e.path[i].className.indexOf('active') > -1) {
                        var active = browser.querySelector('.tab.active');
                        var webview = document.querySelector('#webview-container webview[tab_id="'+active.dataset.tabId+'"]');
                        webview.executeScript({ code: "document.body.scrollTop=0" });
                        return;
                }
        }
});



В browser.html соответственно:

    


Запускаем, проверяем. Вроде работает.

Иллюстрация (1 Mb):
image


Так как эти две функции исключают друг друга, настоятельно не рекомендуется совмещать их.
В Maxthon есть довольно удобная функция перехода по ссылке из буфера обмена.
image
После того, как я её попробовал, мне стало её очень не хватать во всех остальных браузерах, да и другие пользователи тоже просят у разработчиков сделать подобное.
Благо мы можем не ждать их и сами попробуем повторить эту функцию в Вивальди.

Создаём «click-and-go.js» и вставляем код:

click-and-go.js
// Right click on plus-button to paste and go
var browser=document.body.querySelector('#browser');
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";

var dispatchMouseEvent = function(target, var_args) {
    var e = document.createEvent("MouseEvents");
    e.initEvent.apply(e, Array.prototype.slice.call(arguments, 1));
    target.dispatchEvent(e);
};

browser.addEventListener('contextmenu', function(e) {
    //Area near square
    if (e.target.className.toString().indexOf('newtab') > -1) {
        isItMouse = true;
        document.execCommand('paste');
        return;
    }
    //Plus-symbol
    if (e.target.parentNode.parentNode.className.indexOf('newtab') > -1) {
        initPaste();
        return;
    }
    //Square
    if (e.target.getTotalLength() > 0) { // 104 — length of new tab Button SVG
        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?hl=ru#hl=ru&q='; 
    //  var searchEngine = 'http://yandex.ru/search/?text=';
    //  var searchEngine = 'https://duckduckgo.com/?q=';
        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;
function checkUrl(str) {
    return patternUrl.test(str);
}
//url without protocol
function checkUrlWithoutProtocol(str) {    
    return patternUrlWithout.test(str);    
}




Соответственно в browser.html добавляем:

    


Функция сделана по такому принципу: нажатие правой кнопки мыши по плюсику создаёт новую вкладку, и происходит переход по ссылке из буфера обмена, если же там находится не ссылка, то идёт автоматический поиск по строке в поисковой системе.
Но у решения есть пара минусов.
Во-первых, регулярное выражение, которое ищет ссылку в строке без протокола, может ошибаться. То есть, например, «test.test» воспримется как ссылка ровно как и «ya.ru». Но в целом на практике это не должно мешать.
А во-вторых, заданная поисковая система не связана с поисковой системой, что поставлена в браузере как основная. В данном случае поиск будет происходить через Google. Но я специально добавил ещё строчки для DuckDuckGo и Yandex, если кого-то не устроит Google. Для других поисковых систем также вполне несложно написать нужную ссылку.

Иллюстрация (1 Mb):
image


Убрать строку состояния (status bar) можно было в настройках с самой первой версии. Но проблема в том, что именно там отображаются ссылки при наведении на них. И тем, кому status bar не нужен, либо приходилось уживаться с ним, либо же не видеть ссылок при наведении. Сейчас мы попробуем это исправить с помощью css.

status-bar.css
/** Избавляемся от status bar **/

#footer.disabled{
        display:block !important;
        position:static !important;
        padding:0 !important;
        height:0 !important;
        width:0 !important;
}

#footer.disabled > *{
        display:none !important;
}
#footer.disabled #status_info{
        display:block !important;
}

#footer.disabled #status_info span{
        position:fixed !important;
        bottom:0 !important;
        left:0 !important;
        margin:0 !important;
        color:#333 !important;
        background-color:#FEFEFE !important;
        padding:2px 5px !important;
        border:#9E9E9E solid 0 !important;
        border-width:1px 1px 0 0 !important;
        max-width:75% !important;
        overflow: hidden !important;
        white-space: nowrap !important;
        text-overflow: ellipsis !important;
        z-index:50 !important;
}

#footer.disabled #status_info span:empty{
        display:none !important;
}

/******/



Соответственно после

    


вставляем строчку

    


Принцип здесь простой: мы просто убираем отображение строки состояния и рисуем прямоугольник для status_info в углу.
image

Но у данного решения есть некоторые минусы: ссылка отображается всегда в углу поверх всех элементов пользовательского интерфейса. Поэтому для пользователей с вкладками снизу это решение не подойдёт, и нужно будет блок приподнимать через bottom.


Данное дизайнерское решение может показаться интересным для пользователей, у которых вкладки расположены не сверху, а по бокам или снизу. Потому что именно при таком расположении вкладок сверху появляется заголовок окна, который отъедает лишнее место по вертикали, что может быть критично для некоторых.
Так будет выглядеть окно со совмещением:
image

header.css
/** Совмещаем заголовок вместе с адресбар **/
#header {
        min-height: 2px !important;
        height: 2px !important;
        z-index: auto !important;
}

.win .vivaldi {
        color: #fff;
        position: absolute;
        top: 5px;
        left: 98px;
}

.vivaldi+#tabs-container.top {
        border-bottom: 1px solid;
        position: absolute;
        top: 62px;
        width: 100%;
        z-index: 1 !important;
}

#tabs-container.bottom #tabs, #tabs-container.top #tabs {
        max-height: 30px !important;
}

.toolbar.toolbar-addressbar {
        padding-right: 100px;
}

.addressfield { margin-left: 40px !important; }

.button-toolbar {
        position: relative !important;
        left: 30px !important;
}

.bookmark-bar { margin-bottom: 37px; }

.vivaldi { z-index: 3; }

.window-buttongroup { z-index: 2; }

.home,.rewind,.next,#pagetitle{display:none}
/********************/



Это вариант для тех, кто пользуется Vivaldi-кнопкой.
Но есть и пользователи, которые используют меню в его классическом представлении. В таком случае адресной строке придётся ужаться и уступить место кнопкам меню. Причём для разных языков ширина меню будет разная.

Варианты для английского и русского языков
image
image
#header {
min-height: 2px !important;
z-index: auto !important;
}

.win .topmenu {
color: #fff;
position: absolute;
top: 5px;
left: 0px;
}

.topmenu+#tabs-container.top {
border-bottom: 1px solid;
position: absolute;
top: 62px;
width: 100%;
z-index: 1 !important;
}

#tabs-container.bottom #tabs, #tabs-container.top #tabs {
height: 30px !important;
}

.toolbar.toolbar-addressbar {
padding-right: 100px;
}

.button-toolbar.back{ margin-left: 350px }

.home,.rewind,.next,#pagetitle{display:none}

.addressfield { margin-left: 3px !important; }

.bookmark-bar { margin-bottom:0px; }

.window-buttongroup { z-index: 2; }


#header {
min-height: 2px !important;
z-index: auto !important;
}

.win .topmenu {
color: #fff;
position: absolute;
top: 5px;
left: 0px;
}

.topmenu+#tabs-container.top {
border-bottom: 1px solid;
position: absolute;
top: 62px;
width: 100%;
z-index: 1 !important;
}

#tabs-container.bottom #tabs, #tabs-container.top #tabs {
height: 30px !important;
}

.toolbar.toolbar-addressbar {
padding-right: 100px;
}

.button-toolbar.back{margin-left: 270px}

.home,.rewind,.next,#pagetitle{display:none}

.addressfield { margin-left: 3px !important; }

.bookmark-bar { margin-bottom:0px; }

.window-buttongroup { z-index: 2; }



Наверно многие уже забыли, но Opera Presto умела отображать вкладки в несколько строчек. Сейчас такое разве что в FF можно встретить. Пришло время и Вивальди научить этому.

tab-lines.css
#tabs-container.top #tabs, #tabs-container.bottom #tabs {
  height: auto !important;
  display: block !important;
}
#tabs-container.bottom #tabs .tab, #tabs-container.top #tabs .tab {
  max-width: 150px !important;
  min-width: 150px;
  display: inline-block !important;
  float: left;
}
#tabs .tab .tab-thumb { display: none; }
#tabs .newtab { margin-top: -9px; }
#tabs .trash {
  margin-top: -10px; 
  display: inline-block !important;
  float: right;
}



Так будет выглядеть полученный результат:
image
При открытии новых вкладок верхняя панель будет увеличиваться в высоту соответственно.

Ну и на последок пара мелочей:

Если вы захотите использовать какие-то функции одновременно, то можно вполне безболезненно скопировать несколько файлов в один какой-нибудь custom.js файл, чтобы не плодить лишних файлов.
Тоже самое и с css.

Ещё хотелось бы поделиться гитхабом одного пользователя Вивальди с форума, который сделал вполне неплохие вещи и даже реализовал собственную прокрутку вкладок через ПКМ+скролл с превью. Хотя конкретно это более неактуально, т.к. в браузере уже есть такая функция, пусть и в самом зачаточном виде.

А на этом пожалуй всё. Надеюсь что-то из этого для вас было полезно. Конечно, эти решения не идеальны, и могут требовать индивидуальной доработки, т.к. от части некоторые вещи я создавал под себя.
Если же у вас есть мысли по поводу более лаконичного решения в js-функциях или предложения, что можно ещё сделать, то буду рад послушать.

Не забывайте, что при каждом обновлении все подобные изменения будут слетать. После обновления созданные вами файлы будут находиться в папке предыдущей версии. Всё, что нужно будет это скопировать нужные два или три файла в новую версию.

Пара готовых вариантов от меня:
last-tab + status-bar + click&go + hideplus + tab-lines
(Вкладки должны быть заранее перемещены из заголовка) tab-top + status-bar + click&go + hideplus + header

Vivaldi Forum
Github.

© Habrahabr.ru