Google Chrome / [Из песочницы] Google Chrome Extension: Печатаем статьи с habrahabr

в 9:37, , рубрики: chrome, extension, Google, печать, разработка, расширение, создание, статья, метки: , , , , , , ,

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

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

Начал искать другие варианты, на habrahabr я новенький, мог и не найти, но искал что то вроде версии для печати, так и не нашел, решил пойти другим путем, сохранил статью в evernote, и затем распечатать ее, страниц для печати уменьшилось, но не на много, та и комментарии остались, и еще текст был узким, в конце концов пришел к тому что нужно что то, что бы можно было в один клик распечатать статью с данного сайта.
icon_128
Так родилось расширение для браузера 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, здесь описывать не стану.

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

В файле 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. К сожалению мои скудные познания в написании расширений не позволяют мне описать более детальнее процесс создания, но я надеюсь что кому то пригодится данное расширение.
Так же хотел бы услышать предложения, правки и замечания. Всем заранее спасибо.

Автор: Bookin


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js