Google Chrome Extension: Печатаем статьи с habrahabr

Как-то вечером, уходя с работы, наткнулся здесь на интересную статью. Так как я люблю читать печатный вариант, да и время было уже позднее — хотелось домой, но и хотелось прочесть — решил распечатать и почитать в дороге.

Ну и полез я печатать, браузер мне предложил распечатать более 35 страниц, но откуда там может быть 20 страниц? Дело в том что печать шла вместе со всеми элементами, что делает пост узким, соответственно длинным, та и еще и комментарии ту да же.

Начал искать другие варианты, на habrahabr я новенький, мог и не найти, но искал что то вроде версии для печати, так и не нашел, решил пойти другим путем, сохранил статью в evernote, и затем распечатать ее, страниц для печати уменьшилось, но не на много, та и комментарии остались, и еще текст был узким, в конце концов пришел к тому что нужно что то, что бы можно было в один клик распечатать статью с данного сайта.

Так родилось расширение для браузера Google Chrome — HabraPrint.
Все что от вас требуется нажать на одну кнопку и распечатать.


Это мое первое расширения, как и первый пост, надеюсь меня исправят там где я ошибся.
И так что мы имеем внутри:

Создаем папку с названием нашего расширения, куда будем сохранять последующие файлы.
Далее первым делом создаем файл manifest:
Здесь указываем браузеру, что он и с какими разрешениями ставит.

{
  "name": "HabraPrint",
  "version": "0.1",
  
  "description": "Печать в один клик поста с сайта habrahabr.ru", 
  "icons": {
    "128": "img/icon_128.png",
    "64": "img/icon_64.png",
    "48": "img/icon_48.png",
    "32": "img/icon_32.png",
    "16": "img/icon_16.png"
  },
  
  "minimum_chrome_version":"6.0",
  
  "permissions": [ "tabs","http://habrahabr.ru/*", "https://habrahabr.ru/*"],
  "background_page": "background.html",
  "content_scripts": [
    {
      "js": [ "js/jquery-1.7.1.min.js","js/content.js" ],
      "css": ["css/content.css"],
      "run_at": "document_end",
      "matches": [ "http://habrahabr.ru/*", "https://habrahabr.ru/*" ]
    }
  ],
"page_action": {
    "default_icon": "img/icon_19.png",
    "default_title": "HabrPrint"
  },
  "options_page": "options.html"
}


Указываем:
name — Имя расширения
version — текущая версия
description — описания вашего расширения
icons — указываем пути к иконкам расширения <key/размер>:<value/путь к изображению>
icon_128
icon_64
icon_48
icon_32
icon_16


minimum_chrome_version-указываем минимальную версию браузера
permissions — здесь указываем к чему вам потребуется доступ
background_page — указываем путь к странице которая будет выполнятся в фоне
content_scripts — указываем пути к файлам которые будут внедрены в страницу
page_action — данное свойство указывает на то что кнопка расширения будет размещена в адресной строке браузера, указываем путь к иконке и название
options_page — путь к странице настроек

с подробным описанием файла можно ознакомится в документации

Данный файл является обязательным для каждого расширения, как и все файлы указанные в нем.

Теперь будем двигаться по списку.
Я разнес файлы относительно их типа по папкам для удобства использования.
Следующим файлом создаем background_page:
<!DOCTYPE html>
<html>
  <head>
    <script src="js/background.js"></script>
  </head>
</html>


Как видите здесь имеется только каркас страницы, и подключение javascript скрипта который отвечает за действия в фоновом режиме

background.js
	chrome.tabs.onCreated.addListener(function(tab){
		urlDetected(tab.id, null, tab);
	});
	chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab){
		if(changeInfo.status=='complete'){
			urlDetected(tabId, changeInfo, tab);
		}
	});

	function urlDetected(tabId, changeInfo, tab){
		chrome.tabs.getSelected(null,function(tab) {
			var re=/.+habrahabr.+\/(\d+)\//;
			if(re.test(tab.url)){
				chrome.pageAction.show(tabId);
			}else{
				chrome.pageAction.hide(tabId);
			}
		}); 
	}

	chrome.pageAction.onClicked.addListener(function(tabId) {
		//узнаем какие настройки и выполняем действия
		if(!localStorage["radio"]||localStorage["radio"]=='popup'){
			PrintIt();
		}else if(localStorage["radio"]=='same'){
			chrome.tabs.getSelected(null, function(tab) {
				chrome.tabs.sendRequest(tab.id, {
					type:'print-same'
				});
			});
		}
	});


В данном файле вешаем обработчики события на создание, и обновление вкладок, проверяем url на наличие нашего домена и цифр (использую проверку для того что бы теоретически определить находится ли пользователь на странице с постом или нет), на основе чего отображаю или скрываю значок расширения. Таким образом расширение отображается теоретически только на странице с постом.
Далее вешаем обработчик на клик по кнопке нашего приложения и на основе настроек, нашего расширения выполняем действие.
В настройках можно выбрать печатать в том же окне или в всплывающем, но об этом позднее.
в том же окне

в всплывающем окне

Так как вызов всплывающего окна мне разрешил выполнить только данный файл(background_page), здесь разместилась функция отвечающая за показ того самого окна.

 function PrintIt(){ 
    if(wnd){
	wnd.close();
    }
    stext='';
    chrome.tabs.getSelected(null, function(tab) {
	chrome.tabs.sendRequest(tab.id, {
		type:'returnHtml'
	}, function(response) {
		stext=response.html;
		wnd=window.open("", "habrPrint", 'statusbar=no,toolbar=no,scrollbars=yes,resizable=yes'');
		wnd.document.write("<!DOCTYPE html>\
			<html lang='ru'>\
			<head>\
			<meta content='text/html; charset=utf-8' http-equiv='Content-Type'>\
			<meta content='ru' name='language'>\
			<title>"+response.title+"</title>\
			<link href=\"/css/print.css\"rel=\"stylesheet\"type=\"text/css\" media=\"all\"/></style>\
			</head>\
			<body onclick=\"window.close()\">\
			<div class='post'>");
		wnd.document.write(stext);
		wnd.document.write("</div><body></html>");
		wnd.document.close();
		setTimeout(function(){
			wnd.print();
			wnd.close();
		}, 100)
			
			
	});
    });
	
}


Тут я укоротил немного функцию бы было нагляднее. Как видите используется обычный window.open и в него передается html со страницы с текстом поста. Для получения текста отправляем запрос, с названием действия, в content script, в ответ получаем innerHTML поста и записываем в окно wnd.document.write().

Далее напишем наш content script:
var getElementsByClassName = function(getClass){
	if(document.querySelectorAll) {
		return document.querySelectorAll("." + getClass);
	}
	else if(document.getElementsByClassName) {
		return document.getElementsByClassName(getClass);
	}
	else {
		var list = document.getElementsByTagName('*'), i = list.length,
		classArray = getClass.split(/\s+/), result = [];
		while(i--) {
			if(list[i].className.search('\\b' + classArray + '\\b') != -1) {
				result.push(list[i]);
			}
		}
		return result;
	}
};

function pageCleaner(){
	$('body *').removeClass('habrNoPrint').removeClass('habrPrint');
}
function printSame(){
	$('body *').addClass('habrNoPrint');
	$('#layout, .content_left, .company_post, .post, .post *').removeClass('habrNoPrint');
	$('.content_left').addClass('habrPrint');
	window.print();
	window.setTimeout(pageCleaner, 0);
}

chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
  if(request.type == 'print-same'){
	printSame();
  }
  if(request.type == 'returnHtml'){
	var elem=getElementsByClassName('post')[0];
	var title=document.getElementsByTagName('title')[0];
	sendResponse({'html':elem.innerHTML,'title':title.innerHTML});
  }
});


Здесь размещаем слушатель запросов, и при получении его выполняем действие указанное в параметрах.
Здесь всего два действия либо отдать html поста, предыдущему скрипту, либо выполнить функции, которая отвечает за выполнение печати. В данной функции использую стили для печати, то есть указываю стилями что выводить на печать а что скрыть.

И в конце напишем страницу настроек.
В options.html делаем каркас страницы

<!DOCTYPE html>
<html lang='ru' xml:lang='ru' xmlns='http://www.w3.org/1999/xhtml'>
<head>
<meta content='text/html; charset=utf-8' http-equiv='Content-Type'>
<meta content='ru' name='language'>
<style>@import "css/options.css";</style> 
<script src="js/options.js"></script>
</head>
<body>
        <header>
            <h3>Настройки:</h3><span id="options_callback"></span>
        </header>
	<div id='habrPrint_options'>
                <form name="habr_options_form">
		<div class='options_form'>
			<div>
			  <input id="radio_popup" type="radio" name="window" value='popup' checked="checked"/>
			  <label id="append-label">В сплывающем окне</label>
			  <p>Данный режим открывает всплывающее окно с печатью страницы</p>
			</div>
			<div>
			  <input id="radio_same" type="radio" name="window" value='same' />
			  <label id="append-label">В том же окне</label>
			  <p>Данный режим работает с ограничение, браузер ограничивает количество вызовов, не более одного вызова раза в 5-ть секунд</p>
			</div>
		</div>
                </form>
                <div class="button">
                    <div class="button_blue">
                        <button id="save">Сохранить</button>
                    </div>
                </div>
	</div>
</body>
</html>


Здесь всего пару радио кнопок которые устанавливают режим печати и кнопка сохранения, стили указаны в css/options.css, здесь описывать не стану.

Итого вышла вот такая страница:
options

В файле options.js пишем javascript который будет отвечать за сохранение настроек.
	function getRadioGroupValue(radioGroupObj)
	{
	  for (var i=0; i < radioGroupObj.length; i++)
	    if (radioGroupObj[i].checked) return radioGroupObj[i].value;

	  return null;
	}
	function readProperty(property, defValue)
	{
	  if(localStorage[property] == null)
	  {
	    return defValue;
	  }
	  return localStorage[property];
	}
	window.addEventListener("load", function(){
		
	  chrome.tabs.getSelected(null, function(tab) {
	    var save = document.getElementById("save");
			if(localStorage["radio"]){
				document.getElementById("radio_"+localStorage["radio"]).checked =readProperty("radio", false);
			}
	        save.addEventListener("click", function(){
				var radio_value = getRadioGroupValue(document.habr_options_form.window);
				localStorage["radio"] = radio_value;
				if(localStorage["radio"]){
					var sum=document.getElementById('options_callback');
	                sum.innerHTML='Настройки сохранены'
				}
	        });
	  });
	});


Вешаем обработчик на клик кнопки сохранения и по нему заносим значение в объект localStorage.
localStorage является ассоциативным массивом хранящий пары «название», «значение». Для сохранения значения достаточно написать:

	localStorage["radio"] = radio_value;


Вот, в принципе, и все осталось загрузить наше расширение в браузер.
Переходим на вкладку «Управление расширениями» (Инструменты->Расширения), включаем «Режим разработчика», и загружаем распакованное расширение.

Для того что бы воспользоваться расширениям, откройте любой пост на habrahabr и нажмите на появившейся иконке расширения, в адресной строке браузера.

Проблемы:
У себя заметил что при первой загрузке расширения, появляется ошибка что то вроде «Вам не разрешено использовать функции для tabs проверьте манифест», с чем это связанно я так и не разобрался, если что объяснит буду признателен.

Источники:
Google Chrome Extension FAQ
Создание расширения для Google Chrome

Расширение:
Скачать

P.S. К сожалению мои скудные познания в написании расширений не позволяют мне описать более детальнее процесс создания, но я надеюсь что кому то пригодится данное расширение.
Так же хотел бы услышать предложения, правки и замечания. Всем заранее спасибо.

UPD: по просьбе bo883, добавил подсветку синтаксиса, и добавил подложку под код (пока что работает только в «В всплывающем окне», в скором времени добавлю и при печати в том же окне). При реализации столкнулся с проблемой что фон, добавленный через css, не печатает Chrome, но зато прекрасно печатает картинки (<img ...>), основываясь на этом добавляем картинку к блоку с кодом устанавливаем ей ширину и высоту равную размерам блока кода, тем самим растягивая ее на весь блок, и меняем им z-index, что бы подложить под код картинки, в итоге при печати у нас область кода имеет подложку. (расширение обновил)
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 50

    +5
    Вы молодец, статью я плюсанул. Но вообще — чем плох способ дописать «m.» к URL статьи и распечатать мобильную версию? Например, вот эта Ваша статья у меня занимает 5 с четвертью страниц в таком варианте.
      +4
      Спасибо за интерес, к сожалению про способ, описанный вами, я не знал, не думаю что он плох, думаю даже наоборот удобно без извращений, но как по мне теперь я лучше нажму одну кнопку чем буду править url.

      Еще, я так понял, не достаточно добавить просто «m.», к примеру мой пост имеет url — «habrahabr.ru/blogs/google_chrome/139427/» то для мобильной версии url будет- «m.habrahabr.ru/post/139427/»

      Я здесь новенький, еще многого не знаю, буду потихоньку осваиваться.
      В любом случае спасибо.
        +2
        Мне хватает закладки

        javascript:(function(){location.href=location.href.replace(/habrahabr.ru\/.*?(\d+)/, 'm.habrahabr.ru/post/$1')})()

        а потом ещё

        javascript:(function(){document.getElementsByClassName('txt')[0].style.fontSize = '9px'; document.getElementsByClassName('adv')[0].innerHTML=''; document.getElementsByClassName('cmts')[0].style.fontSize = '7px';})()

        для приведения текста в удобный формат.
          0
          Спасибо за пример, думаю многие, теперь, могут выбрать удобный для них вариант:)
            0
            Регулярка из первого скрипта навернется на блоге HTML5 :)
              0
              Точно! Спасибо ;)
              Работает вот так:
              javascript:(function(){location.href=location.href.replace(/habrahabr.ru\/.*?(\d+)[^\d]*$/, 'm.habrahabr.ru/post/$1')})()
                0
                А если у меня сейчас в браузере адрес habrahabr.ru/blogs/google_chrome/139427/#comment_4659522
                ? :)
                  0
                  честно говоря, не силен в регулярках, но чем не подходит моя — /.+habrahabr.+\/(\d+)\//?
                    0
                    можно предположить, что последнего / может не быть
        0
        Вот буквально сегодня с проблемой столкнулся: что делать со статьями из закрытых блогов? на минихабре я получаюсь разлогиненным (опера), и способа залогиниться не нашел.
          0
          ваш способ подкупает своей элегантностью
          0
          «при первой загрузке расширения, появляется ошибка что то вроде «Вам не разрешено использовать функции для tabs проверьте манифест»» — Имея некоторый опыт разработки расширений под Chrome, могу сказать, что периодически попадаются глюки и баги. Не удивляйтесь)
            0
            Пытался найти ответ на свою проблему, и находил только ответы похожие на ваш, и все же меня немного смущало это, и казалось что все же где то, что то, пишу не так. Теперь более спокоен, спасибо.
            +3
            Помимо мобильной версии можно воспользоваться расширениями вроде iReader или Readability. Но конечно изобретение велосипедов повышает опыт и самообразование :-)
              0
              Вы правы, первое что пришло в голову найти какое то расширение которое превратит страницу в вид для чтения и распечатать ее, но так я и не нашел подходящее расширение, к примеру iReader у меня почему то делал что то вообще не понятное, никак он не хотел определить статью. По этому пришло мнение что надо свое, и да по большей части хотелось принять опыт написания расширений.
                0
                Evernote Clearly довольно неплохо с этим справляется (сам evernote ему не нужен). Он как раз заточен под чтение: оставлает на странице только статью, скрывая все остальное. При печати, правда, оставляет слишком большое правое поле.

                Но я Вас полностью понимаю — я тоже сначала написал свое расширение, потом нашел почти такое же на github'е.
                  0
                  Спасибо, сейчас и его попробую в действии. :)
              +1
              Поймал себя на том, что такие посты листаю сразу в конец и ищу кнопку/ссылку — «Скачать».

                0
                Вы не один, я сам такой же, смотрю что это такое, ищу где скачать и попробовать в действии, читаю полное описание и как оно там внутри работает :)
                0
                Классное расширение, я только в pdf сохранял и читал. Пользовался Clearly или IReader ранее для этого, ваше удобней (подсветка кода бы вообще бомба).
                  0
                  «подсветка кода бы вообще бомба» — что вы имеете ввиду? Выделить цветом область кода? Может сейчас быстренько получится подправить.

                  Спасибо за отзыв)
                    0
                    >Выделить цветом область кода?
                    да
                      0
                      сейчас что то придумаем ;D
                        +1
                        Наверное, имелась в виду синтаксическая расцветка ключевых слов, которая видна, если код заключён в теги source lang=…
                        Пример:
                        var scr = document.createElement('script');
                        scr.addEventListener('load', function(ev){
                        	wcl(scr)
                        },!1);
                        scr.setAttribute('type', 'application/javascript');
                        document.body.appendChild(scr);
                        scr.src = 'http://js/opera.test.cross.js'; //xUrl;
                        
                        0
                        Просто темной подложкой, как сейчас серая область под кодом? или, как говорит spmbt, нужна подсветка синтаксиса кода?
                          0
                          подсветка синтаксиса было бы очень круто.
                      0
                      Обновил расширение, теперь при печати в всплывающем окне, есть и подложка и цвета синтаксиса (стиль синтаксиса взят с хабра).
                      0
                      Я для stylebota добавил стили на страницы блогов. В хроме есть встроенный принтер пдф. Получалось неплохо.
                        +1
                        Еще у кого нет хрома но очень хочется печатать статьи, может себе добавить юзерскрипт:
                        $('#layout > *[class!=content_left]').hide(); 
                        $('#comments').hide();
                        $('.comments_form').hide(); 
                        $('.content_left').css('width', '100%');
                        window.print();
                        $('#layout > *[class!=content_left]').show(); 
                        $('#comments').show();
                        $('.comments_form').show(); 
                        $('.content_left').css('width', '66.6%');
                        


                        Скрипт скрывает лишнее, вызывает диалог печати, и после того как диалог закрывается, возвращает все скрытое назад как было.
                          0
                          Почему-то значёк HabrPrint появляется только на этой странице, а на других статьях не появляется…
                            0
                            Пожалуйста приведите пример странице на которой не отображается?
                                0
                                  0
                                  То есть сейчас, на данной странице вы обновляете ее и значок есть а на той что привели открываете и его нет?

                                  Посмотрите возможно выдаются какие то ошибки, или попробуйте переустановить расширение, не могу проверить, потому что и на этой и на той что вы дали у меня после загрузки странице заначек отображается
                                  0
                                  Что-то видимо просто не сразу схватывает, не с первого раза. Вроде сейчас на большинстве страниц ок.
                                    0
                                    Последнюю ссылку открывал, страница долго грузилась, после чего я не увидел значок, перегрузил, быстренько загрузилась страница, и значок есть, может слушатель обновления/создания страницы отваливается при длительной загрузке, честно не могу ответить однозначно, из-за отсутствия опыта и знаний
                                –1
                                Вот бы оно еще самое ценное из статьи вытаскивало ;)
                                  0
                                  Подумываю расширить функционал, но незнаю стоит ли делать, что бы можно было мышкой выберать участки для печати, но в этом случае уже нужно отходить от хабра, и распространять на любой сайт, что бы сохранять участки текста, блоков, но по-моему уже есть похожие расширения
                                  0
                                  iReader — готовое решение для данной задачи…
                                    0
                                    Как то его пробовал, он мне выдавал что то не внятное и непонятное, сейчас же выдал хороший вариант, будем знать, спасибо.
                                      0
                                      Оставлю цинк на instapaper. Может кому пригодится.
                                      0
                                      Хм, а не достаточно ли было бы расширением подключать один единственный css @media print в котором и навести порядок?
                                        0
                                        к сожаление не вспомню что меня увело от этого решения, но первоначально так и было задумано, при печати в том же окне.
                                        0
                                        AdBlock'ом рубим блок с ссылками/меню/т.п. справа и всё печатается без проблем без всяких дополнительных экстеншенов
                                          0
                                          AdBlock'ом рубим блок с ссылками/меню/т.п. справа и всё печатается без проблем без всяких дополнительных экстеншенов
                                            0
                                            Вот так, хабраюзер уходя домой, написал по дороге расширение к хрому (:
                                            Раньше пользовался «костылями»-сохранял в PDF а потом просто ненужные страницы удалял/не отправлял на печать.
                                            Спасибо за удобный плагин!
                                              0
                                              Пожалуйста, еще можете подобрать для себя решение из предложеных людьми в комментариях, правда мой глаз пал только на iReader
                                              0
                                              Сначала прочитал название как «Печатаем деньги с Хабрахабр».

                                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                              Самое читаемое