Десктопные приложения на html, css и js для windows, mac os, linux. Подробный обзор nw.js (бывший node-webkit)

nw

nw.js – это платформа, которая позволяет создавать кроссплатформенные десктопные приложения для windows, mac os и linux, используя веб-технологии. При этом javascript, html и css используются для построения интерфейса, а node.js(io.js) – для описания основной логики. Кроме того, Вам предоставляется высокоуровневый JS API для доступа ко многим элементам операционных систем (настройкам окна, меню, буферу обмена, системному трею и др.)

Мы все любим веб-приложения! При этом большинство софта, который мы используем на ноутбуках для работы и развлечений, – это десктопные приложения. Да-да, те самые, с иконкой на рабочем столе, которые могут висеть в памяти целыми днями или неделями (файловые менеджеры, системные утилиты, фото, аудио и видео редакторы, IDE, чаты и др.).

Что же не позволяет веб-приложениям выйти на уровень десктопных?

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

  • Имело полный доступ к файловой системе, системным процессам, могло выполнять команды в командной оболочке той машины, на которой запущено.
  • Могло работать в фоне, запускаться при старте системы и оффлайн.
  • Не спрашивало подтверждения доступа при использовании камеры и микрофона.
  • Могло использовать Web Gl, Web Audio, Web RTC и т.д. в той версии браузера, которая определена мной.
  • Могло создавать элементы управления на уровне операционной системы: меню, окна, иконки в трее (менюбаре), управлять ими.
  • Могло создавать несколько окон.
  • Могло делать скриншоты.
  • Имело доступ к буферу обмена на запись и чтение.
  • Могло подписываться на системные сочетания клавиш (в том числе, когда приложение не в фокусе)
  • Могло пользоваться системной нотификацией.
  • Могло делать кроссдоменные запросы.
  • Могло изменять UserAgent
  • Могло загружать любые сайты во фрейм, который они не считали бы фреймом.
  • Могло включать полноценный полноэкранный режим kiosk-mode (как в десктопных играх).
  • Я хочу распространять и рекламировать приложение через магазины приложений.
  • И просто, но важно… Я хочу запускать приложение нажатием на иконку!

nw.js позволяет реализовать вышеописанные задумки.

Кстати, если вам удобнее воспринимать видео, нежели читать дальше, то вот запись с моего доклада на Frontend Conf. Но, хочу предупредить, в статье все описано подробне ;)

Почему браузера может быть мало?

Похожие подходы и их недостатки

На данный момент существует несколько различных технологий для создания десктопных приложений на базе веб-технологий. Вот их неполный список:

На мой субъективный взгляд, основным недостатком этих технологий является то, что обычному веб-разработчику придется немало помучиться, осваивая все тонкости API и внутренней философии любой из них. Вам нужно будет перелопатить тонны документации и примеров, чтобы просто начать что-то делать и увидеть первый результат.

С nw.js все намного проще. Если вы разрабатываете веб-приложения и имеете немного опыта работы с node.js, то уже через несколько часов вы поймете как писать и собирать десктопные приложения под mac, windows и linux.

От node-webkit к nw.js. Немного истории

roger-wang

В 2011 году разработчик из Китая Roger Wang основал проект node-webkit, основной задумкой которого стало использование веб-технологий для написания полнофункциональных кроссплатформенных десктопных приложений. Как уже понятно из названия, основными составляющими проекта стали Node.js и Webkit (Chromium).

Шли годы и в апреле 2013-го Chromium, а вместе с ним и проект node-webkit переехал на новый движок Blink (форк Webkit-а). В добавок к этому с января 2015 проект начал использовать io.js вместо node.js.

В итоге изначальное название “node-webkit” стало совсем не актуальным и было принято решение переименовать проект в nw.js. Бытует мнение, что теперь в буквах NW заложен новый смысл – “Native Web”.

Основными спонсорами проекта до сих пор являются intel и Gnor Tech.

intel-gnor-logos

Основная идея. Зачем “скрещивать” Chromium c node.js?

Когда мы говорим о десктопных приложениях, мы представляем себе некий графический интерфейс, взаимодействуя с которым мы осуществляем изменения на системном уровне операционной системы (например, копируем файлы, запускаем процессы, выполняем системные команды и т.п.)

Для того, чтобы понять всю мощь идеи nw.js, давайте коротко рассмотрим основные составляющие nwjs.

Chromium – браузер с открытым исходным кодом, который разрабатывается силами Google, Opera Software, Яндекс, NVIDIA и других компаний. В качестве движка для отображения веб-страниц в Chromium используется Blink (форк Webkit). В качестве движка для обработки JavaScript используется v8.

node.js(io.js) – это JavaScript runtime на основе движка v8, изначально используемого в Chromium. Он написан на С++ и работает на уровне протоколов прикладного уровня, где ему доступны множество различных API операционных систем, таких как файловая система и сетевые взаимодействия. В силу этого, node чаще всего используется для построения системных приложений и серверов.

iojs-and-chromium-logo

Основной идеей nw.js является объединение Chromium и node.js в единый контекст, используя один и тот же v8. Если сказать точнее, node.js использует v8 Chromium-a. Т.е. при помощи Chromium мы можем создать графический интерфейс на основе html, css, js, как и в любом веб-браузере. Но, в отличие от обычного браузера, nw.js позволяет из этого же контекста вызывать функции node.js для работы с системными API операционной системы.

Давайте разберем простейший пример. При помощи модуля fs для io.js мы будем следить за изменениями какого-нибудь файла в системе. В случае, если файл изменился, отобразим его содержимое в div-e c id=”log”.

js

var fs = require('fs');
var path = '/usr/local/var/log/nginx/error.log';
fs.watchFile(path, function(a){
  document.getElementById('log').innerHTML = fs.readFileSync(path, {encoding:'utf8'});
});

Как мы видим, здесь нет сервера и клиента, нет ajax, нет сокетов, нет http, нет обмена данными по сети. Как мы говорили, вся прелесть nwjs заключается в возможности работы с node.js из контекста Chromium.

Как они это сделали

Разработчики nw.js приложили немало усилий для обеспечения единого event loop и построения “моста” между контекстами node.js и chromium. Здесь можно почитать подробнее о технических деталях и проблемах, возникших при реализации этой задумки.


Начинаем погружение

Для начала скачайте и установите последнюю версию nw.js для Вашей платформы. Далее следуйте указаниям документации для настройки командной строки и алиасов для Вашей операционной системы.

Структура приложения nw.js

Приложение для nw.js состоит из обычных html, css, js файлов, структурированных произвольно. Нет никаких дополнительных правил и условностей по их компоновке.

project-structure-nwjs

При запуске nw.js ищет файл манифеста package.json.

Пример package.json

{
  "main": "index.html",
  "name": "dummydemo",
  "description": "Dummy demo of nnwjs app",
  "version": "0.0.1",
  "keywords": [ "demo", "nwjs" ],
  "window": {
    "title": "Dummy demo",
    "icon": "icon.png",
    "toolbar": false,
    "frame": true,
    "width": 700,
    "height": 400,
    "position": "mouse",
    "min_width": 400,
    "min_height": 200,
    "max_width": 800,
    "max_height": 600
  },
  "author": "nedudi",
  "license": "MIT",
  "dependencies": {
    "moment": "latest",
    "handlebars": "^2.0.0"
  }
}

Этот файл ничем не отличается от обычного package.json в node.js, за исключением того, что он содержит дополнительные параметры: window, user-agent, chromium-args, js-flags и другие. Они позволяют nw.js настраивать и контролировать поведение Chromium и его окружения, добавлять флаги при запуске node.js, менять режим отображения, размеры окна, иконки, точку входа в приложение и множество других установок.

Для примера мы создадим файл index.html и добавим немного CSS.

index.html

<div class="hello">
  Привет, NW.JS!
</div>

Затем откроем консоль и наберем:

$ nw my/lovely/app

где my/lovely/app – путь к папке с приложением, а nwправильно настроенный алиас для nw.js

И все! В результате запустится десктопное приложение, которое выглядит примерно так:

nwjs-window-frame-toolbar


Настройка отображения окна

Результат, который мы видим очень похож на обычный браузер с адресной строкой. В принципе, это и есть браузер! Да-да, тот самый Chromium, о котором мы говорили.

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

Для этого существует целый ряд настроек, доступных в package.json. Давайте подробнее остановимся на некоторых из них.

Для начала можем убрать тулбар.

package.json

...
"window": {
  "toolbar": false
}
...

nwjs-window-frame

При желании можно скрыть фрейм окна.

package.json

...
"window": {
  "toolbar": false,
  "frame": false
}
...

nwjs-window

Или вообще убрать фон окна, оставив только контент.

package.json

...
"window": {
  "toolbar": false,
  "frame": false,
  "transparent": true
}
...

nwjs-window-transparent

Kiosk-mode

Еще одной мощной опцией является возможность запуска приложений в Kiosk-mode. Этот режим часто используется в десктопных играх, а также на экранах в публичных местах (например, для показа рекламы на больших мониторах). Выйти из приложения, запущенного в Кiosk-mode не так просто как из браузерного fullscreen. Это можно сделать только при помощи методов API nw.js, Alt-Tab или Ctrl-Alt-Del(Windows), поэтому, разрабатывая приложения, работающие в этом режиме, Вы сами должны позаботиться о наличии в интерфейсе некоторой кнопки “Выход”, которая поможет пользователю его закрыть.

package.json

... 
"window": {
  "kiosk": true
}
...

nwjs-macbook-kiosk

Ну и, наконец, мы просто можем скрыть окно, оставив приложение в фоновом режиме

"window": {
   "show": false
}

Элементы интерфейса операционной системы.

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

Для этого потребуется всего лишь подключить модуль nw.gui, который уже есть в стандартной поставке. Он дает возможность абстрагироваться от реализации элементов интерфейса в конкретной операционной системе, предоставляя общий API.

var gui = require('nw.gui');

gui.Window.get(); //окно
gui.Shell(); //оболочка
gui.Tray // трей
gui.Menu // менюшки
gui.Clipboard // буффер обмена
gui.Shortcut // сочетания клавиш
// ...и другие элементы

А теперь давайте посмотрим подробнее на некоторые из вышеописанных возможностей.

Создание системных контекстных меню

js

// Создать пустое меню
var menu = new gui.Menu();
// Добавить в него пункты или разделители
menu.append(new gui.MenuItem({ label: 'Ничего не делать' }));
menu.append(new gui.MenuItem({ type: 'separator' }));
// .. и повесить на них обработчики
menu.append(new gui.MenuItem({
  label: 'Сказать "Привет!"',
  click: function() {
    alert('Привет!')
  }
}));

// Показывать в виде контекстного меню
document.body.addEventListener('contextmenu', function(e) { 
  e.preventDefault();
  // В том месте, где мы кликнули
  menu.popup(e.x, e.y);
  return false;
}, false);

Вот что мы получим в итоге при клике правой клавишей на окне:

nwjs-window-cmenu

Создание меню окна

js

// Создать верхнее меню
var menubar = new gui.Menu({ type: 'menubar', title: 'Menu Title' });

// В качестве вложенных меню используем такой же код, как в примере c контекстным меню.
menubar.append(new gui.MenuItem({ label: 'Главное', submenu: menu}));
menubar.append(new gui.MenuItem({ label: 'О нас', submenu: menu}));

//Получить текущее окно и подключить к нему верхнее меню
gui.Window.get().menu = menubar;

Результат:

nwjs-window-menubar

Создание иконок и меню в трее (менюбаре)

js

// Создаем иконку в трее (менюбаре)
var tray = new gui.Tray({ 
  title: 'ololo', 
  icon: 'icon.png',  
  alticon: 'icon.png'
});

// Добавляем меню при клике на иконке в трее
var traymenu = new gui.Menu();
traymenu.append(new gui.MenuItem({label: 'Тот', type: 'checkbox'}));
traymenu.append(new gui.MenuItem({label: 'Этот', type: 'checkbox' }));
traymenu.append(new gui.MenuItem({label: 'Другой', type: 'checkbox'}));
traymenu.append(new gui.MenuItem({type: 'separator' }));
// В качестве вложенных меню используем такой-же код как в примере c контекстным меню.
traymenu.append(new gui.MenuItem({label: 'или ...',   submenu: menu}));

tray.menu = traymenu;  

Результат:

nwjs-window-tray

Обработка сочетаний клавиш

js

var shortcut = new gui.Shortcut({
  key : "Ctrl+Shift+L",
  active : function() {
    console.log('Нажато сочетание клавиш', this.key)
  },
  failed : function(msg) {
    // невозможно зарегистрировать такое сочетание клавиш
    // возможно, оно уже занято
    console.log(msg);
  }
});

// регистрируем сочетание клавиш в системе
gui.App.registerGlobalHotKey(shortcut);

// отписываемся от сочетания клавиш в системе
gui.App.unregisterGlobalHotKey(shortcut);

Работа с буфером обмена

js

var clipboard = gui.Clipboard.get();

// получить значение
var text = clipboard.get('text');

// установить значение
clipboard.set('Привет', 'text');

// очистить буфер
clipboard.clear();

Хранение данных и ресурсов в приложениях на nw.js

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

Эту задачу можно разделить на 2 части:

1) Хранение структурированных данных 2) Хранение локальных статических ресурсов (фото, видео и других файлов)

Хранение структурированных данных

На сегодняшний день в арсенале разработчика есть несколько встроенных браузерных API. Все они есть в Chromium, который является частью nw.js

Мы не будем останавливаться на каждом из них. Подробнее об удобных плагинах для работы с этими API можно почитать здесь.

Хранение локальных статических ресурсов

Для хранения файлов нужно использовать специально предусмотренную директорию App.dataPath. Её расположение будет зависеть от операционной системы и имени проекта

  • Windows %LOCALAPPDATA%/<name>
  • Linux ~/.config/<name>
  • OSX ~/Library/Application Support/<name>

где <name> – это поле, заданное в package.json

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


Отладка

Для отладки можно использовать кнопку на верхней панели.

nwjs-window-debugger

Она запустит привычный отладчик (в точности такой же, как в браузере Chrome).

Более того, Вы можете запустить nw.js c флагом --remote-debugging-port:

$ nw --remote-debugging-port=1234 app

После этого отладчик будет доступен по ссылке http://localhost:1234

Кроме этого, есть возможность делать Livereload приложения при каждом изменении в коде. Почитать об этом подробнее можно здесь.


Сборка приложений на nw.js

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

В стандартных же случаях лучше использовать готовый плагин node-webkit-builder.

Все что нужно сделать, это установить node-webkit-builder и запустить команду nwbuild с указанием пути к папке приложения.

$  npm i -g node-webkit-builder
$  nwbuild ./

Если требуется собрать приложение под конкретные операционные системы, их список можно задать с помощью флага -p

$  nwbuild ./ -p win32,win64,osx32,osx64,linux32,linux64

При этом nwbuild сам скачает последние версии nw.js и сделает сборки для всех указанных платформ (каждую в отдельной папке).

Вот что получилось у меня после открытия собранного тестового приложения в различных ОС:

Mac OS

macnwjs

Windows

windows-nwjs

Ubuntu

ubuntu-nwjs


Примеры приложений на nw.js

Popcorn time

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

popcorntime.io

popcorn-time

Mongo management studio

Mongo Management Studio – клиент для mongodb. Удобный интерфейс для выполнения команд и просмотра структуры коллекций. litixsoft.de/english/mms

mong-management-studio

Lighttable

Lighttable – IDE для разработчиков с уникальным интерфейсом и пождходом (правда я так и не понял в чем его прелесть). С виду похож на sublime или atom.

lighttable.com

lighttable

Spreaker

Spreaker позволит Вам стать радиоведущим и вещать аудио в сеть. Сделано при помощи node-webkit, portaudio и lame.

Mac: download.spreaker.com/spreaker-osx.dmg Win: download.spreaker.com/spreaker-win.exe

spreaker

sizeChecker

sizeChecker – предаставляет удобный интерфейс для слежения и управления местом на вашем лаптопе/маке/pc.

github.com/airbob/sizeChecker

sizechecker

Sdal za mig (Сдал за миг)

Приложения для сдачи налоговых отчетов для российского рынка. Доступно в виде веб-приложения, десктопного приложения и приложения для терминалов (инфо-киосков).

sdalzamig

Здесь можно найти еще множество приложения и идей для nw.js.

Что еще почитать по nw.js?

Выводы

Почему уже можно использовать nw.js и веб-технологии для десктопных приложений?

1) Легкая кроссплатформенность (Mac, Linux, Windows)
2) Работает достаточно быстро.
3) Нарастающий тренд использования веб-приложений во всех сферах.
4) Легко писать приложения для себя (например, графический интерфейс для часто выполняемых рутиных операций).
5) Дешево во всех отношениях (обучение, разработка, поддержка)!

Жду вопросов и Ваших отзывов и мнений в комментариях ;)

Метки: , , , , , , ,
23 комментария на “Десктопные приложения на html, css и js для windows, mac os, linux. Подробный обзор nw.js (бывший node-webkit)
  1. Сеяр пишет:

    Приветы.
    Спасибо за статью.

    Вероятно, в примере про то, где мы можем скрыть окно пример package.json неверный. "kiosk": true

    • Спасибо, Сеяр!

      Исправил на "show": false

      • Рамиль пишет:

        У меня возник вопрос.
        В packaje.json, главным html документом я указываю index.html на хостинге. То есть ссылку типа http://site.ru/index.html
        На этой странице у меня есть рекламный баннер. Я хочу что бы при клике по нему ссылка баннера открывалась в браузере по умолчанию. Для этого прописываю в коде баннера <a onclick=”var g = require(‘nw.gui’); g.Shell.openExternal(‘https://google.com’);> .
        Но увы не работает. Работает только на локальных файлах (если index.html находиться в папке NWJS)
        Как мне можно решить эту проблему? Я не хочу делать баннер локальным. Так как баннеры на сайте я постоянно меняю, я хочу что бы они менялись во всех скаченных приложениях.
        Заранее спасибо за ответ.)

  2. ctepeo пишет:

    Спасибо за статью

  3. Руслан пишет:

    Дмитрий,

    спасибо за статью!

    Очень здорово, что ты её подготовил. Я ничего раньше не слышал про эту платформу. Как-то все больше говорят о портировании веб-приложений с помощью PhoneGap-a и Apache Cordova.

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

  4. Вадим Макишвили пишет:

    Спасибо, Дим. Захотелось что-то сделать самому. За ссылку на Монго-просмотрщик спасибо.

  5. Phil Rigovanov пишет:

    Актуально и содержательно! Спасибо!

  6. Stanislav пишет:

    Замечательная статья, но хотелось бы еще и архив с рабочим примером на всякий пожарный :)

  7. Stanislav пишет:

    Еще есть ошибка в:

    document.body.addEventListener('contextmenu', function(e) {...

    нужно

    window.addEventListener('contextmenu', function (e) {...

  8. afdw пишет:

    tray.menu = menu;
    Не уверен насчёт этой строки… Может быть имелось ввиду
    tray.menu = traymenu;

  9. Oleg Android пишет:

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

  10. Артём пишет:

    Найс :)

  11. Михаил пишет:

    Спасибо большое за обзор!
    Возник такой вопрос: а что представляет собой конечная сборка приложения? Все html, css и javascript файлы остаются открытыми (лежат в свободном доступе в какой-то директории с ресурсами)? Или же при сборке все это зашивается в исполняемый файл или еще куда-то? Есть какая-то возможность спрятать html, css и javascript и защитить файлы от свободного изменения? Или весь код остается доступным пользователю?

    Заранее спасибо за помощь!

  12. Роман пишет:

    Скажите, пожалуйста, а можно ли на основе nw.js создавать платный софт?

  13. Дмитрий пишет:

    Если уж быть совсем объективным то вот вам навскидку пару ложек дёгтя:

    Даже приложение аля “привет мир” тянет за собой целиком webkit раздувая размер приложения.
    Тоже касается оперативной памяти, ~60Mb многовато для белого окна с черной надписью.
    Заявленная кроссплатформенность валится из-за зоопарка сборок Linux на Kubuntu и её Plasma иконки в трее нет =(
    Скорость приложения… скорость запуска приложения…

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

    Но за статью всё равно спасибо. обязательно покручу на досуге…

    P.S написал себе программу для быстрого монтирования удаленных дисков на Java. Не стал возиться с Layouts натянул на всё приложение WebView и в нём рисовал интерфейс, затем повесил обработчик на событие Webview.OnAlert при клике на любой элемент интерфейса внутри WebView делал onclick=”alert(this.id)” и в зависимости от id уже внутри Java выполнял нужные действия.. Вроде всё как и хотелось.. НО -> скорость запуска ужасает, скорость работы тоже, необходимо тянуть за приложением JRE, запуск программы по jar файлу… в общем я пошел искать дальше решение..

  14. Николай пишет:

    Вступление: Есть такая вещь как xulrunner и xul от МОЗИЛЛЫ, те же веб технологии для создания интерфейса, но движок другой.
    У них есть возможность сборки своей программы на основе этого движка с “достаточной” кроссплатформенностью.

    Важно, что по умолчанию у них можно подключить “установку дополнений” и организованно средствами движка обновление программы с сервера. В браузере ХРОМ, тоже подобное реализовано, но есть ли такое в NW или “Электрон”?

    Как я понял у них движок WebKit урезанный и вероятно такого не реализовано .

    Также в XulRunner есть технология XPCOM – в кратце можно “закрыть” свою уникальный код в gjlrk.xftvjv компоненте (кстати можно писать на JS или С)

    Вопросы:)
    1. NW – поддерживает систему обновления десктопной программы с сервера?
    2. NW – по умолчанию (или с плагинами) позволяет реализовать механизм установки дополнений для своего приложения, как из файлов , так и с сервера?
    3. Есть ли в NW возможность наподобии XPCOM. То есть подключить к Движку Webkit свой закрытый код?
    4. Платное распространение возможно ? Лицензия позволяет продавать программу, у которой уникального только интерфейс на JS и пара функций тянущих инфу с интернета?

  15. Владимир пишет:

    Здравствуйте.
    Помогите, не могу понять, что я делаю не так. Скачал и распакавал в отдельную папку nwjs-sdk-v0.18.8 и хочу запустить простое тестовое приложение, но не как не выходит. В папке (где тестовое приложение) всего два файла:
    package.json
    {
    “name”: “hello”,
    “main”: “index.html”,
    }
    и index.html c заголовком “Hello”

    • Владимир пишет:

      А всё разобрался… Я в названии файла index.html, пробел поставил и в упор не видел его). Спустя пару часов допёрло… мда

Оставить комментарий

Ваш email не будет опубликован. Обязательные поля отмечены *

*

* Copy This Password *

* Type Or Paste Password Here *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Узнавай о новых статьях первым!

Спасибо за лайк в FACEBOOK
Подписывайтесь на новости вконтакте
Последние статьи от html5.by