Эта статья посвящена написанию простого расширения для браузера Opera
. Наше расширение будет примитивным, т.к. весь его функционал будет заключаться в user-JS
для habrahabr.ru
. Лента комментариев оснащена блоком, который отображает количество новых комментариев в топику и кнопку, позволяющую эту ленту обновить. Давайте добавим туда стрелки для навигации по новым комментариям.
C чего начнём?
- Создадим новую директорию для файлов расширения
- В ней создадим файл
config.xml
Содержимое XML:
<?xml version="1.0" encoding="UTF-8"?> <widget xmlns="http://www.w3.org/ns/widgets" id="http://faiwer.ru" version="0.9a" defaultlocale="en"> <name xml:lang="en">HabrCommentSwitcher</name> <description xml:lang="en">Habrahabr. New comment switcher</description> <description xml:lang="ru">Habrahabr. Переключение новых комментариев</description> <author href="faiwer.ru" email="faiwer@gmail.com">Faiwer</author> <icon src="icons/64x64.png"/> <icon src="icons/48x48.png"/> <icon src="icons/32x32.png"/> </widget>
Так как эта статья обучающая, я не буду подробно описывать каждый пункт, с этим лучше меня справится документация. Остановимся на самом главном:
<name>
Название нашего расширения</name>
. Не стоит делать слишком длинным;<description>
Краткое описание</description>
. Хватит и пары строчек;<icon />
— Иконки используются на странице расширений, на сайте-репозитории (если ваше расширение там примут), и в кнопке, которой в данном расширении не будет. Желательно вынести в отдельную директорию, дабы не создавать беспорядок
Доступных опций намного больше. Но для начала хватит и этих. Думаю, вы обратили внимание, что в config.xml можно указать сразу несколько языковых версий для названия и описания.
В качестве последнего штриха нужно создать index.html
. Он нужен для функционирования «закадрового» скрипта, который будет запущен вместе со стартом браузера, и не будет привязан ни к одной из вкладок. Нам он не нужен, но без него Opera не даст нам «подебажить». Файл можно оставить пустым. Теперь при помощи drag-n-drop
перетаскиваем наш config.xml
в браузер. Если всё прошло хорошо, откроется страница со списком установленных расширений, и наше там будет сверху, в разделе «режим разработчика
».
UserJS
В начале следует определиться с тем, что же должно делать будущее расширение:
- дожидаться окончания загрузки страницы и появления искомого блока (далее я буду называть его
slider
). - разместить в нём наши кнопки-стрелки.
- разместить необходимый
CSS-код
для стрелок и выделения текущего комментария. - оживить стрелки — они должны перемещать скролл страницы по новым комментариям.
Для всего этого достаточно 1-го файла, который будет исполняться для каждой habrahabr
-страницы. Т.е. нам идеально подходит UserJS
. Но если Chrome
умеет преобразовывать UserJS
в расширения сам, а Firefox
-у для этого нужен Greasemonkey
, то в случае Opera
мы можем его оформить в виде расширения или установить вручную (F12
— Настройки для сайта
— Скрипты
).
Создадим директорию includes
, Opera будет искать «внедрённые» скрипты именно там. В ней создадим файл habr_comment_switcher.js
(тут название можно выбрать любое). В начало файла поместим:
// ==UserScript== // @include http://habrahabr.ru/* // ==/UserScript==
Это не просто js-комментарии
, это специальная разметка для UserJS
, которая в нашем случае объясняет опере, что сий внедряемый файл должен запускаться только на habrahabr.ru
.
JavaScript
Помимо этого файла мы могли бы внедрить ещё и какую-либо библиотеку вроде jQuery
или Prototype
. Но я строго не рекомендую так поступать. Такого рода библиотеки весьма весомые, а т.к. они будут загружаться не только для каждой вкладки, а ещё и для каждого iframe
, которых на странице бывает много, 5-10 таких расширений могут вызвать тормоза. Учитывая что наши задачи весьма скромны, мы не сильно много теряем.
UPD 2
. Спасибо, @kns
. Если же на странице уже используется 1 из популярных библиотек, то мы можем воспользоваться ею. Подробнее об этом можно прочесть <a href=“habrahabr.ru/post/140825/” target=“_blank”>здесь</a>. В случае Opera это будет выглядеть примерно так:
var $ = window.jQuery;
Начнём писать код. Для начала, для удобства, поместим всё в анон.функцию
. Т.к. наше javascript-окружение
изолировано от javascript-окружения сайта, этого делать не обязательно, но на мой взгляд, подобное уже давно стало правилом хорошего тона:
+function( w ) { }( window );
Не знаю как вы, но я привык работать в пределах конкретного объекта, а посему определим его:
var Engine = function(){ this._init(); } Engine.prototype = { _init: function() { } }
Теперь необходимо оформить условия его создания:
if( w.location.href.indexOf( 'habrahabr.ru' ) > 0 ) { var engine = false; d.addEventListener( 'DOMContentLoaded', function() { setTimeout( function(){ engine = new Engine(); }, 1500 ); }, false ); }
Проверка на адрес страницы вызвана не логическими доводами, а паранойей. Дело в том, что я пару раз натыкался в сети на сведения о том, что Opera
иногда не справляется с правилами для UserJS
. Наш объект будет запущен после того, как всё DOM-древо
страницы будет построено + 1.5 сек. Почему 1.5 сек.? Дело в том, что slider
появляется не сразу, поэтому мы его подождём. Сие можно реализовать более изящно, но пока сойдёт и это.
Работа расширения
Сейчас Opera не самый высоко-технологичный браузер, но всё же его возможности намного опередили IE6,7,8. Следовательно мы можем воспользоваться такими вещами, которые не стали бы применять в обычном web-программировании. Немного упростим себе работу:
var d = w.document, $ = d.querySelector.bind( d ), $$ = d.querySelectorAll.bind( d )
Методы querySelector
и querySelectorAll
позволяют находить DOM-объекты
по CSS-селекторам
. Такой подход вам наверняка знаком по опыту использования jQuery
. В нашем случае функция $
будет искать один элемент, удовлетворяющий запросу, а $$
список.
Что там у нас по списку? Да не важно, давайте внедрим на страницу нужный нам CSS
:
_cssInject: function() { var style = this._createElem( this.elem.style_inject ), text = ''; for( var i = 0, n = this.css.length; i < n; ++ i ) { text += this.css[ i ]; } style.innerHTML = text; d.head.appendChild( style ); }
Здесь мы создаём новый DOM-объект
<style />
и в качестве содержимого задаём необходимый CSS-код
. Т.к. страница уже готова нам доступен document.head
, куда мы и поместим наш тег. Теперь о функции _createElem
:
_createElem: function( data ) { var item = d.createElement( data.tagName ); if( data.attr ) { for( var rule in data.attr ) { item.setAttribute( rule, data.attr[ rule ] ); } } return item; },
Организовать работу с настройками можно как угодно, например так:
_initConst: function() { this._extend( this, { css: [ '.__hcsc_button { border-top: 1px solid white; line-height: 22px; height: 22px; ' + 'cursor: pointer; }', '.__hcsc_button:hover { color: white; }', '.info.__hcsc_active { outline: 2px solid #222; }', ], elem: { style_inject: { tagName: 'style', attr: { id: '__habr_comment_switcher_css' } } } } ); }, _extend: function( object, extend ) { for( var name in extend ) if( extend.hasOwnProperty( name ) ) { object[ name ] = extend[ name ]; } },
Перейдём к основной логике. Нам нужно найти slider и добавить к нему две кнопки:
_prepareSlider: function() { var slider = $( this.s.slider ); if( !slider ) { return; } this.up_button = this._createElem( this.elem.button ); this.up_button.innerHTML = '▲'; slider.appendChild( this.up_button ); this.down_button = this._createElem( this.elem.button ); this.down_button.innerHTML = '▼'; slider.appendChild( this.down_button ); },
Стрелки можно задать текстом. Теперь нам нужно эти кнопки оживить:
_observe: function() { this.up_button.addEventListener( 'click', this._slideClick.bind( this, -1 ), false ); this.down_button.addEventListener( 'click', this._slideClick.bind( this, +1 ), false ); },
И наконец, долгожданная листалка:
_checkItems: function() { var items = $$( this.s.info_panel ); if( !this.items || !this.items.length || !items.length || ( this.items[ 0 ] !== items[ 0 ] ) ) { this.position = -1; this.items = items; } return this.items; }, _slideClick: function( diff ) { if( this.current ) { this.current.classList.remove( this.c.active ); } if( !this._checkItems().length ) { return; } this.position += diff; if( this.position < 0 ) { this.position = this.items.length - 1; } else if( this.position >= this.items.length ) { this.position = 0; } this.current = this.items[ this.position ]; this.current.scrollIntoView( true ); this.current.classList.add( this.c.active ); }
Её логика проста. Ищем все новые комментарии по CSS-селектору
, заданному в this.s.info_panel
( “.comment_item > .info.is_new
” ). Он находит нам все блоки-заголовки новых комментариев. Затем, в зависимости от того, на какую кнопку мы нажали, перемещаем скролл страницы к нужному комментарию, используя scrollIntoView
. Чтобы сие событие было более наглядным, добавляем к нему класс, для которого выше определили CSS с тёмной рамкой (outline
).
Учитывая, что доступна кнопка обновить, и наш список новых комментариев может устареть, расширение каждый раз сверяет старый список с текущим, и если они различны обнуляет счётчик позиции.
Отдельно я хотел бы остановится на функциях работы с классом DOM-объекта
. Нет нужны вручную парсить строчку item.className
, т.к. доступны следующие методы:
this.current.classList.add( 'my_class' ); this.current.classList.remove( 'my_class' );
Немного о «дебаге»
Начнём с того, что у нас есть такой инструмент как Dragonfly
(стрекоза), который вызывается через ctrl+shift+i
(либо правая кнопка мыши
— «проинспектировать объект
»). В нём на вкладке «Скрипты
» мы можем отыскать в выпадающем списке наш habr_comment_switcher.js
. Теперь нам доступны точки останова и «трейсинг
» (F8
, F10
, F11
). Также нам доступна консоль, но чтобы она работала в том же js-окружении
, что и наш скрипт, нам нужно предварительно посмотреть его номер в выпад.списке скриптов.
Посмотреть ошибки можно путём нажатия кнопки «открыть консоль ошибок
» на странице установленных расширений. Объект console
для расширений не работает. Чтобы обновить расширение, нам нужно закрыть стрекозу, на странице расширений нажать «обновить
», открыть стрекозу на нужной вкладке и нажать в браузере «обновить
». В целом, впечатления от работы с расширением в стрекозе самые ужасные. Особенно после опыта разработки расширения для Chrome
.
Финальный штрих
Вроде всё работает, так что самое время упаковать расширение. Для этого сожмём содержимое папки расширения в zip-архив
, и сменим расширение файла на oex
. Всё, расширение готово. Можно пользоваться. Если Opera
ругается на то, что расширение повреждено, проверьте — возможно вы сжали не содержимое папки, а её саму. Так же проверьте наличие файлов config.xml
и index.html
.
Эпилог
УРА! Наше расширение готово, в стадии альфа-версии. Его можно улучшить, добавить поддержку Chrome
и Greasemonkey
(хотя я не уверен, что не взлетит так), добавить страницу настроек (к примеру, чтобы задавать цвета или изменять CSS-селекторы).
Посмотреть уже готовое расширение можно здесь. Первый раз пользуюсь git
-ом, извиняйте, если что не так.