Если ты давно хотел сделать Интернет чуточку лучше (для себя), но всё было лень некогда, то сегодня твой счастливый день. За 21 шаг я покажу тебе как достичь этой заветной цели.

Бесплатный бонус! В добавок к интернету, делаем лучше и Google search. Может, Google оценит идею и предложит руку и сердце (если что, я согласная, даже учебу брошу ради него).
</шутка>

В этой статье, я расскажу о то как сделать плагин для браузера Chrome, FireFox и MS Edge на примере собственного опыта спортивного бега по граблям.

Плагин позволят легко внедрять собственный JavaScript/CSS на любые страницы просматриваемые в браузере. Т.е. этакий browser extensions API версия lite.

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

TL;DR

Если кому-то не хочется читать про все 21 шага, то можно обойтись пятью.

Код плагина можно загрузить с GitHub.

И после этого:

  1. Открыть chrome://extensions в Хром браузере
  2. Поставить галочку напротив «Developer mode»
  3. Нажать на «Load unpacked extensions» и выбрать директорию в которую сохранили исходный код
  4. После этого для плагина «CustomActions» выбрать «options»
  5. На форме опций, нажать «Demo config» и «Save»



Вот и все, можно пользоваться, например открыть google.com или Хабр, поправить конфигурацию или поиграться со скриптами.

Дальше идет развернутая версия, с лирическими отступлениями.

Если верить исследованиям британских ученных, то сферический интернет пользователь спасаясь от объема информации которую хотят на него вывалить, создает себе уютный интернет мирок из 5-10 сайтов которые посещает более-менее постоянно, а в «большой» интернет выходит случайно и поскорее бежит обратно.

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

Но еще печальнее было осознать, то что мои 9-ть с половиной сайтов не идеальны. Поэтому в рамках курсовой работы было решено сделать их (ну заодно и остальной интернет) лучше.

В большинстве случаев, для этого требовалось написать всего 3-4 строчки на JavaScript и/или 2-3 на CSS.

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

Определяем наши хотелки:

  1. При открытии сайта внедряется пользовательский Ж-скрипт ассоциированный с этим сайтом
  2. Пользовательский скрипт конфигурируется на странице предпочтений
  3. В тулбар браузера добавляем кнопочку для быстрого вызова конфигурации

Техзадание определено, начинаю кодить.

Шаг 1.

Читаю getstarted до просветления, или посинения, смотря с какой стороны посмотреть.

Шаг 2.

Определяю структуру проекта

options.html
options.js

popup.html
popup.js

background.js

manifest.json

icon.png
images
images\icon128.png
images\icon16.png
images\icon48.png

Шаг 3.

Создаю манифест.

{
  "name": "CustomActions",
  "description": "plugin for CustomActions",
  "version": "1.0",
  "background" : {
    "scripts": ["background.js"]
  },
  "icons":
   {
      "128": "images/icon128.png",
      "16": "images/icon16.png",
      "48": "images/icon48.png"
   },
  "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
  "permissions": [
    "webRequest", "tabs", "activeTab", "http://*/*", "https://*/*", "storage", "unlimitedStorage", "contextMenus", "<all_urls>"
  ],
  "browser_action": {
      "default_title": "Custom Actions Injection plugin",
      "default_icon": "icon.png",
      "default_popup": "popup.html"
  },
        "commands": {
          "cmd-exec-1": {
            "suggested_key": {
              "default": "Ctrl+Q"            
	     },
            "description": "Custom Action #1"
          },
          "cmd-exec-2": {
            "suggested_key": {
              "default": "Ctrl+B"
            },
            "description": "Custom Action #2"
          },
          "cmd-exec-3": {
            "suggested_key": {
              "default": "Ctrl+Y"
            },
            "description": "Custom Action #3"
          }
},
  "options_page": "options.html",
  "manifest_version": 2
}

Шаг 4.

Работаю над формой options.html.

Не зря посещала предыдущую сессию. Пригодились знания по knockout.js из курса «Дизайн Веб-страниц 2.0»

Добавляю файлы knockout-3.4.1.js и knockout.mapping-latest.js в структуру проекта.

Ничего не работает. Копаю. Не помогает. Рою. Нарыла что chrome extensions не любит knockout.
Заставляю полюбить, обновляю манифест.

"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"

Шаг 5.

После боли и страданий в попытке реализовать пользовательский интерфейс приходит озарение.

Тянем всё из chrome://settings

Встали на плечи гиганта и дело пошло:



Шаг 6.

Надо сохранять данные. Читаю про chrome.storage
Дилемма выбора между chrome.storage.sync и chrome.storage.local

chrome.storage.sync привлекательней, но жёсткие лимиты.

Еще одно озарение – у меня же тестовый проект, так что local — это наше всё.
Правлю манифест чтобы не было проблем с лимитами.

И пользую chrome.storage.local.set для сохранения конфигурации.

            chrome.storage.local.set(items, function () {
                self.status('Items saved.');
                setTimeout(function () { self.status(''); }, 750);
            });

Шаг 7.

Понимаю, что сохраненная конфигурация должна быть перезагружена в background.js, но как???

Ага, сладкая парочка chrome.runtime.sendMessage:
            chrome.storage.local.set(items, function () {
                self.status('Items saved.');
                setTimeout(function () { self.status(''); }, 750);

                chrome.runtime.sendMessage({ command: 'refreshConfig' });
            });

и chrome.runtime.onMessage:
            chrome.runtime.onMessage.addListener(
               function (request, sender, sendResponse) {
                   onCommand(request.command);
            });

Шаг 8.

Открываю popup.js. Закрываю. Снова открываю. И так 7 раз. Ничего не понятно.

Это я одна такая или где? Наступает долгожданное утро и тут споткнулась о samples — спасибо вам невидимые труженики гугла.

Разгребаю завалы драгоценных знаний. В конце концов нахожу нужный пример и использую волшебную комбинацию ACV ( Ctrl-A / Ctrl-C / Ctrl-V ). 13 часов спустя — дело сделано.

Шаг 9.

Пора приступать к главной части, перехват и внедрение.

Перехватчик — chrome.tabs.onUpdated:
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
        var url = '';
        if (changeInfo && changeInfo.url)
            url = changeInfo.url.toLowerCase();
        else if (tab && tab.url)
            url = tab.url.toLowerCase();

	// . . .
});

и внедренец — chrome.tabs.executeScript:

if (item.sourceType == 'InjectCSS') 
	chrome.tabs.insertCSS(item.output == 'Owner tab' ? tabId : null, { code: item.data });
else
	chrome.tabs.executeScript(item.output == 'Owner tab' ? tabId : null, { code: item.source });

Все прошло легче чем могло, но всё же дольше чем хотелось.

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

Шаг 10.

Всё готово. Можно идти за честно заработанной пятеркой…

… не так быстро. Вместо пятерки принесла кучу замечаний и улучшений.

Шаг 11.

Начнем с самого легкого, в popup.js добавляю поддержку функции «добавить текущий сайт»

Нет так легко как кажется, надо передать данные из popup.js в options.js.

Потратила 3 часа пытаясь поймать messages, сохранить в extension storage и прочитать обратно, но в конце концов решила, что умный в гору не пойдет, он её обойдет.

Query string – наш ответ этой горе.

Шаг 12.

Следующая функция посложнее, добавить поддержку context menu.
Примеры снова спасают. Всё оказывается проще чем могло бы — chrome.contextMenus.create:
chrome.contextMenus.create({ id: item.id, 
				contexts: ["page", "frame", "selection"], 
				title: item.name, 
				onclick: function (info, tab) { 
							onCommand(info.menuItemId); 
					} 
});

Шаг 13.

Не верю в приметы, но оказался не счастливый шаг.

Надо сделать поддержку вызова через горячие клавиши.

Всё делаю как в примерах, правлю манифест:
"commands": {
          "cmd-exec-1": {
            "suggested_key": {
              "default": "Ctrl+Q"            
	     },
            "description": "Custom Action #1"
          }
}

и использую chrome.commands.onCommand:
chrome.commands.onCommand.addListener(onCommand);

Не работает. После двух дней и ночей и медикаментозной интервенции, промотав вниз страничку настроек обнаруживаю неприметную ссылку на нужные установки.


Вот где собака зарылась. Сама виновата, слишком много примеров наставила, вот ссылочка и сбежала на следующий экран.

Шаг 14.

У нашего завлаба, жена преподаватель английского. Требует перевести интерфейс. Придется учить английский.

Технологии приходят на помощь. Гугл транслейт vs преподаватель английского – счёт 1: 0 в пользу гугла животворящего!

Шаг 15.

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

Возвращаюсь с новым словом рефакторинг (до этого знала только слово ФАКторинг. Особенно часто говорила его в начале проекта).

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

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

Шаг 16.

Рефакторинг закончен. Удивительно как много багов скрывается под покровом первоначального варианта кода, надо бы получше запомнить это слово.

Шаг 17.

Добавляю пример для lorem ipsum. Кто это посоветовал? Учитель латыни?

Требование: при нажатии на комбинацию клавиш заполняем поля на форме предложениями языка lorem.

Сделала, вроде работает.

    var loremDemoData = {
        names: [
            { firstName: "Victoria", lastName: "Veit", email: "Victoria.Veit@noreply.ru" },
            { firstName: "Gisele", lastName: "Gillard", email: "Gisele.Gillard@noreply.ru" },
            { firstName: "Edmund", lastName: "Edelson", email: "Edmund.Edelson@noreply.ru" },
            { firstName: "Joey", lastName: "Janelle", email: "Joey.Janelle@noreply.ru" }
        ],
        lorem: [
            "Orem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet purus condimentum, porta nulla sed, consequat felis. Phasellus quis condimentum odio. Maecenas scelerisque vehicula leo, sit amet tristique tellus molestie sed. Aenean lacus lorem, feugiat semper imperdiet a, vehicula ac orci. Pellentesque ac nisi commodo, pellentesque lorem quis, fringilla tellus. Fusce bibendum erat sit amet libero maximus rutrum. Integer dictum nibh sodales efficitur congue. Mauris nulla libero, hendrerit eget dictum nec, aliquam eu mi. Donec ipsum nisi, bibendum et consequat eu, imperdiet eget nisl. Duis tincidunt nibh et nibh tempor, quis mattis mi vulputate.",
            "Suspendisse quis eleifend lectus. Sed nec vehicula elit. Praesent ac sollicitudin diam. Nam at venenatis lectus. Fusce condimentum tortor nec augue vestibulum tempus. Nullam faucibus vehicula lorem, et mollis justo dapibus a. Proin sagittis velit in lectus vehicula, id eleifend urna hendrerit. Integer rhoncus dui sed enim sollicitudin, a finibus magna fermentum.",
            "Fusce at urna vitae magna semper scelerisque id volutpat tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed ut elit nisl. Duis sit amet ante accumsan nibh ultricies pharetra at vitae purus. Donec a felis eget ipsum euismod tempus. Donec elementum vel tortor vel efficitur. Nunc tristique, magna hendrerit sagittis placerat, odio sem commodo ligula, eu aliquam arcu elit sit amet diam. Etiam ultrices vehicula auctor."
        ],
        loremShort: [
            "Morbi nec sollicitudin augue.",
            "Suspendisse sagittis fringilla aliquam.",
            "Curabitur malesuada dolor.",
            "Praesent quis lacus neque. Duis vitae vehicula felis"
        ]
    };

        function getRandomInt(min, max) {
            return Math.floor(Math.random() * (max - min)) + min;
        }

        var name = data.names[getRandomInt(0, data.names.length)];
        var hadEmail = false;

        var t = document.querySelectorAll('input[type=text], textarea');
        for (var i = 0, l = t.length; i < l; i++) {
            var e = t[i];
            var ro = e.getAttribute('readonly');

            if (e.disabled || ro === '' || ro === 'true' || ro == '1')
                continue;

            var loremTxt = data.lorem[getRandomInt(0, data.lorem.length)];
            var loremShort = data.loremShort[getRandomInt(0, data.loremShort.length)];

            var na = ('' + e.name).toLowerCase();
            var ia = ('' + e.id).toLowerCase();

            if (na == 'firstname' || ia == 'firstname' || na == 'fname' || ia == 'fname')
                e.value = name.firstName;
            else if (na == 'lastname' || ia == 'lastname' || na == 'lname' || ia == 'lname')
                e.value = name.lastName;
            else if (!hadEmail && (na.indexOf('email') >= 0 || ia.indexOf('email') >= 0)) {
                e.value = name.email;
                hadEmail = true;
            } else {
                e.value = (e.tagName == 'TEXTAREA' ? loremTxt : loremShort);
            }
        }

Шаг 18.

Следующий пример для сайта habrahabr.ru

Требование: при открытии сайта, скрывать статьи для хабов и компаний перечисленных в конфигурации. Для статей, написанных от лица компании, сделать их видимо отличными от статей написанными независимыми авторами.

В процессе узнала о существовании Хабра и Гиктаймса. Знала бы о них раньше, все было бы по крайней мере в два раза быстрее. Провела три дня читая без остановки. Осилила статьи только за последний год, впереди еще много интересного!

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

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

Оказалось, что статью написать легче, чем код. Может чукча не читатель, а писатель?

{
    "showCompanies": [
        "yandex",
        "mosigra"
    ],
    "hideCompanies": [
        "hashflare"
    ],
    "hideHubs": [
        "lib"
    ]
}

        function hideParent(el) {
            if (el.classList && el.classList.contains('post_teaser'))
                el.style.display = 'none';
            else if (el.parentElement)
                hideParent(el.parentElement);
        }

        function sanitizeParent(el) {
            if (el.classList && el.classList.contains('post_teaser')) {
                el.querySelectorAll('img').forEach(function (img) { img.style.display = 'none'; });
                el.querySelectorAll('.post__body_crop').forEach(function (chld) {
                    chld.style.maxHeight = '4em';
                    chld.style.overflow = 'hidden';

                    el.addEventListener('mouseover', function () {
                        chld.style.maxHeight = "inherit"; chl
                        d.querySelectorAll('img').forEach(function (img) {
                            img.style.display = 'block';
                        });
                    }, false);

                    el.addEventListener('mouseout', function () {
                        chld.style.maxHeight = "4em";
                        chld.querySelectorAll('img').forEach(function (img) {
                            img.style.display = 'none';
                        });
                    }, false);

                });

                el.querySelectorAll('.post__title a').forEach(function (titl) { titl.style.color = '#707040'; });

            } else if (el.parentElement)
                sanitizeParent(el.parentElement);
        }

        document.querySelectorAll('a[href*="https://geektimes.ru/hub/"]').forEach(function (el) {
            var hub = el.getAttribute('href').replace(/^.*\.ru\/hub\//, '').replace(/\/.*$/, '');
            if (data && data.hideHubs && data.hideHubs.indexOf(hub) >= 0)
                hideParent(el);
        });

        document.querySelectorAll('a[href*="https://geektimes.ru/company/"], a[href*="https://habrahabr.ru/company/"]').forEach(function (el) {
            var company = el.getAttribute('href').replace(/^.*\.ru\/company\//, '').replace(/\/.*$/, '');
            if (data) {
                if (data.hideCompanies && data.hideCompanies.indexOf(company) >= 0) {
                    hideParent(el);
                    return;
                } else if (data.showCompanies && data.showCompanies.indexOf(company) >= 0)
                    return;
            }

            sanitizeParent(el);
        });

Шаг 19.

Бродя по просторам интернета, увидела интересную идею и решила реализовать её в качестве примера для google search.

Может завлаб оценит инициативу и перестанет придираться по пустякам.

Функционал: при открытии гугла, показываем подсказку со списком наиболее часто используемых слов и сайтов для быстрого поиска.

После примера для Харба, это пара пустяков.

    var googleDemoData = [
        { "keywords": "python", "title": "python" },
        { "keywords": "javascript", "title": "javascript" },
        { "keywords": "php", "title": "php" },
        { "keywords": "mysql", "title": "mysql" },
        { "keywords": "site:stackoverflow.com", "title": "at stackoverflow.com" },
        { "keywords": "site:developer.mozilla.org", "title": "at developer.mozilla.org" },
        { "keywords": "site:developer.chrome.com", "title": "at developer.chrome.com" },
        { "keywords": "site:habrahabr.ru", "title": "at habrahabr.ru" }
    ];

        function ggSetTimeRange() {
            var elemId = this.getAttribute('data-range');
            var timeLimit = document.querySelector('#' + elemId + ' a');
            if (timeLimit)
                timeLimit.click();
        }

        function ggReplaceAndSearch() {
            var kw = this.getAttribute('data-search');

            if (document.location.href.indexOf('chrome-search://') == 0 || document.location.href.indexOf('https://www.google.com/_/chrome/newtab?') == 0) {
                document.location.href = "https://www.google.com/search?q=" + encodeURIComponent(kw);
                return;
            }

            var inputText = document.querySelector('input[name="q"]');
            if (inputText) {
                setTimeout(function () {

                    var keyword = '' + inputText.value;
                    if (kw.indexOf('site:') >= 0 && keyword.indexOf('site:') >= 0) {
                        keyword = keyword.replace(/ *site:[^ ]+/, '');
                    }
                    else if (keyword.indexOf(kw) >= 0)
                        return;

                    kw = ' ' + kw;

                    if (kw.indexOf('site:') >= 0) {
                        inputText.value = keyword + ' ' + kw;

                        setTimeout(function () {
                            var btn = document.querySelector('form[action="/search"]');
                            if (btn) {
                                btn.submit();
                            } else {
                                btn = document.querySelector('button[name="btnK"]');
                                if (btn) {
                                    btn.click();
                                }
                            }
                        }, 100);
                    }
                    else {
                        inputText.value = kw + ' ' + keyword;

                        var strLength = ('' + inputText.value).length;
                        inputText.setSelectionRange(strLength, strLength);
                    }
                }, 200);

                setTimeout(function () {
                    inputText.focus();
                }, 100);

            };
        };

        var ggHelper = document.getElementById('ggHelper');
        if (!ggHelper) {
            var helperHtml = '<div id="ggHelper" style="position: fixed; ' +
					                'box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); ' +
					                'background-color: #f0f0f0; border-radius: 2px; flex: 1; ' +
					                'padding: 0.7em 1em 0.3em 1em; right: 1em; top: 12em; width: 13em; height: ' + (data.length + 4) + 'em; ' +
					                'font-size: 13px;">' +
		            '<ul style="list-style-type: none; margin: 0; padding: 0;">';

            data.forEach(function(dataItem ) {
                helperHtml += '<li style="text-align: left; cursor: pointer;"><a href="javascript: return false;" data-search="' + dataItem.keywords + '" class="gg-keyword">' + dataItem.title + '</a></li>';
            });

            helperHtml += '<li style="margin: 0.5em;"><hr size="1" style="height: 1px; border-color: #e0e0e0;"></li>';

            helperHtml += '<li style="cursor: pointer;">' +
                '<a href="javascript: return false;" data-range="qdr_w" class="gg-range">week</a> :: ' +
                '<a href="javascript: return false;" data-range="qdr_m" class="gg-range">month</a> :: ' +
                '<a href="javascript: return false;" data-range="qdr_y" class="gg-range">year</a> :: ' +
                '<a href="javascript: return false;" data-range="qdr_" class="gg-range">any</a></li>';
            helperHtml += '</ul></div>';

            var bodyTag = document.querySelector('body');
            if (bodyTag) {
                var e = document.createElement('div');
                e.innerHTML = helperHtml;
                bodyTag.appendChild(e.firstChild);

                document.querySelectorAll('#ggHelper .gg-keyword').forEach(function (el) {
                    el.addEventListener('click', ggReplaceAndSearch);
                });

                document.querySelectorAll('#ggHelper .gg-range').forEach(function (el) {
                    el.addEventListener('click', ggSetTimeRange);
                });
            }
        }

Шаг 20.

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

После переписки, нашла еще не менее 6-ти багов. Ревью – это сила! Надо запомнить и это слово.
Стало приятно смотреть на код, начала даже понимать что и зачем там написано.

Шаг 21.

Все готово.

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

Пожелайте удачи!

P.S.:. В конце хотелось бы выразить сердечную благодарность создателям и пользователям сайта stackoverflow.com

Не представляю как выглядела работа до его создания. А до появления веба (слава ТНБ, я родилась уже после его создания веба), профессия программиста наверное была одной из самых депрессивных и с самым высоким уровнем суицидов на единицу площади.

Даже сейчас, иногда всё еще хочется вырвать всю растительность на голове, а иногда и того похуже.

P.P.S.:. Критика приветствуется. Любая.

Update 1. Выставили оценки, мне 4++.

Один бал завлаб вычел с формулировкой: «слишком много плагиата с StackOverflow», но плюс добавил за статью на Хабре. На вопросы по поводу второго плюса, загадочно ухмыляется в усы.

Update 2. Две радостные новости.

Первая. Преподаватель по сетевым технологиям обещал поставить экзамен автоматом если добавлю функцию «поделиться с другом». Группа экспретов говорила незнакомые слова.

Сижу, курю node.js, походу придется начать не только курить, но и пить. Без бутылки не разобраться.

Голова в облаках. На Хероку оно мне надо? Может лучше сдать как обычно — шпаргалки и зубрило. А то здоровье дороже.

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

Так что если есть идея как улучшить функциональность — предлагайте, голосуйте. Можно новую демо функцию, шаблон или UI хитрый или еще что.

Озвучьте ваши самые смелые фантазии.

Третья. Если так пойдет, может и диплом на чём-нибудь прокатит?

Update 3. Ура! Первая зарплата в долларах. По программе work & travel изучаю английский на кухне местного макдональдса.

Первая выученная идиома: «маньяна на траваху». Чувствую, что к концу лета буду флюент.

Умножаю доллары на 60, количество нулей конкурирует с лучшими предложениями для программистов в нашем областном центре. А у нас в райцентре, надо вводить коэффициент N.

Нет N мало, надо брать M.

Группа экспертов, молча завидует издалека, но намекает что по приезду будем гудеть. Требуют привезти дудку. Но тут тем кому нету 21 года, музыкальные инструменты не продают, не знаю что и делать.

Много думаю.

Может моё призвание это кулинария? Выпишу бабушку, откроем чебуречную, будем диетическими пирожками торговать. А то на диете бесплатного макдональдса, я точно останусь очччень сферическим пользователем.

Update 4. Вместо законного отдыха, провела две бессонные ночи программируя (и как разультат за эти два дня спустила всю первую зарплату на starbucks), но всё таки сконвертировала плагин в FireFox и MS Edge.

FireFox почти не сопротивлялся. Можно загрузить код или готовый плагин с GitHub.

Чтобы разрешить загрузить из исходного кода нужно воспользоваться web-ext или через настройки:



Для загрузки готового плагина:



А MS Edge упирался всеми конечностями, еле заборола. Хотела бросить, но заветный курсовой автоматом всё таки не позволил слабостям взять верх над сильностями.

Слова из документации: Microsoft Edge extensions currently only support Default Policy Restrictions: script-src 'self'; object-src 'self', поставили крест на использовании knockout на форме опций. Пришлось переводить на angular. Тоже не без приключений.

Но чудо произошло и в конце концов всё случилось. Код можно забрать с GitHub.

Надо разрешить разработку в about:flags



и после этого можно загрузить для локального использования:



Я уже благодарила StackOverflow? В который раз снимаю шляпу перед этой сокровищницей знаний.
Поделиться с друзьями
-->

Комментарии (41)


  1. frees2
    20.07.2017 15:12

    Если свой сервер на этом же компе можно PHP подгружать, просто удобнее будет. Конечно, многие не согласятся, но это личное мнение. У каждого свои извращения.


  1. rinat_crone
    20.07.2017 15:27
    -8

    О. У. ЩИТ.
    В соседних тредах — машин лернинг, биг дата, хайлоады… Чему может научить студент? Зачем его статья на Хабре? Кто-нибудь объяснит человеку разницу между профессиональными ресурсами и персональными блогами?

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


    1. rinat_crone
      20.07.2017 15:28
      -2

      И да, 80% вашей статьи – сомнительного рода листинги, которые под силу написать (и вы сами это доказываете) даже самому ленивому студенту. Ладно бы ещё написать, а не надергать со Stack Overflow…


    1. Analitik_Telecom
      20.07.2017 15:33
      +11

      … сказал человек без единой публикации.

      А по мне так, девчонка нормально написала! Учитывая, что на Хабре есть и студенты, и новички, и сомневающиеся, именно такие вот посты могут вдохновить и копать чужой код, и писать на Хабр. Обратите внимание, сколько здравой самоиронии в статье, как бережно написано про ревью кода. А ошибки — «оно, чай, не филолух», обтешется. Кстати, на Хабре принято про ошибки писать в личку. ЩИТ и МАТ вам :-)


      1. rinat_crone
        20.07.2017 15:42
        -2

        Хм, Вы факт несогласия с литературным критиками тоже будете выражать в подобном ключе?
        Я на Хабр писал в 2009 году подобное. В момент когда понял, что публикации написаны в таком же ключе как и эта («по-детски») я их скрыл в черновики.

        В любом случае, Ваше мнение имеет право на жизнь. Хотя на мой взгляд, если ЭТА статья показалась Вам полезной и увлекательной, то Вы что-то не то читаете в интернете…


    1. ntpetrova
      20.07.2017 16:10

      Я опубликовала статью в песочницу, думала что её увидят только те, кто специально на это подписан. Так что сорян, что-то проглядела.

      Ну а по поводу надергать куски — этому есть синоним: интеграция компонентов. Это с какой стороны посмотреть, иногда это плюс, а не минус.


      1. kuzminvv
        20.07.2017 18:41

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


  1. piranuy
    20.07.2017 15:47
    +3

    Хром-плагин это хорошо, но разве всякие Tampermonkey не специально для таких задач существуют? А в качестве бонуса все работало бы в любом браузере для которого есть подобный плагин.


    1. ntpetrova
      20.07.2017 16:01

      Я не настоящий сварщик, только учусь.

      Спасибо за подсказку, посмотрю подробнее в эту сторону.


      1. powerman
        20.07.2017 17:12

        Для FF это плагины Spidermonkey и Stylish. Когда-то для IE это был Trixie, а для Safari NinjaKit, возможно сейчас уже другие. Плюс есть сайты вроде https://greasyfork.org/ для обмена модами между юзерами.


        1. sumanai
          20.07.2017 17:48
          +1

          Для FF это плагины Spidermonkey

          Скорее Greasemonkey.


  1. neit_kas
    20.07.2017 16:24
    +1

    Озвучьте ваши самые смелые фантазии.

    Пользуюсь лисом. Намёк думаю ясен.


    1. ntpetrova
      20.07.2017 17:45
      +1

      Быстро просмотрев на лису, вижу что API очень похожи.

      Осенью возьмусь, сейчас смены по 12-ть часов. Боюсь что не осилю при таком графике.


      1. Londeren
        21.07.2017 13:12

        Подкину вам ссылку.


        1. ntpetrova
          22.07.2017 07:29

          Сконвертировала код под FireFox https://github.com/ntpetrova/CustomActionsFireFox

          Почти что 1-в-1, небольшие изменения.


  1. frees2
    20.07.2017 16:25

    Риторически, сам с собою разговариваю.

    Как известно, при работе в Chrome на общем устройстве каждый пользователь может сохранять свои настройки, закладки и темы.
    Возможна ли надстройка над сайтом «для своих», обмен информацией между ними.
    C:\Users\YOURUSER\AppData\Local\Google\Chrome\Application\chrome.exe --user-data-dir=«D:\PROFILEDIR»
    http://www.davidledgerwood.net/2011/10/managing-multiple-google-chrome.html?m=1


    1. ntpetrova
      20.07.2017 17:39
      -1

      Как говорит наш завлаб, ничего невозможного нету, вопрос времени и ресурсов.

      Идея нравиться, примерно как я это вижу:

      При навигации на сайт, по url для корневого домена или по иерархии фолдеров проверяется на наличие комментариев.

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

      Показываем иконку уведомления в тулбар или всплывающее окно со списком комментариев, если есть новые записи со времени последнего визита.

      Только нужен ресурс, центральный сервер или дисковери сервис в случае р2р.


      1. frees2
        21.07.2017 17:36

        В общем потестировал, поместил в одну общую директорию с php.
        Гораздо проще получается, редактором меняем ссылки, редактируем php-редактором саму страничку popup.html и всё без всяких подгрузок, аяксов. Редактируем сохраняем, нажимаем на иконку браузера и смотрим изменения.


  1. hamperok
    20.07.2017 16:30
    -4

    тут срочно нужен коммент с лайками


    1. nick758
      21.07.2017 13:58
      +2

      image


  1. gimntut
    20.07.2017 16:33
    +1

    В целом статья понравилась.
    Листинги не читал, но пару фраз на русском утащил к себе в копилку.
    И на хероку мне это надо?


  1. bano-notit
    21.07.2017 01:34

    Хорошая статья) Весёлая. Правдо не несёт информационной нагрузки для большей часть здешней аудитории, но почему бы не развеяться на ит шную тему в четверг, ну для меня уже пятница правда?)


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


    Но вот мне не понятно зачем задержка там вообще существует задержка исполнения кода на 1 секунду и на 5? И ещё, обычно всякие там приложухи не имеют доступа к "системным" вкладкам типа chrome://ext..., //hist… и потобным, ибо их туда обычно не пускает для инжекта система безопасности. У вас же на таких страницах нету ограничений в интерфейсе, соответственно некоторое чувство вседозволенности присутствует, а такого быть не должно, ибо браузер всё же ставит в рамки.


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


    1. ntpetrova
      21.07.2017 05:00
      -1

      Согласна, трудно придумать что-то новое. Всё уже было сделано много раз и чем дальше тем труднее и труднее быть оригинальной.

      Задержка на 1 или 5 секунд была сделана для случаев когда подгружается дополнительный контент по AJAX.

      Можно конечно в пользовательский скрипт добавлять проверки, но в большинстве случаев использование задержки будет достаточно и позволит сделать скрипт сильно проще.

      Пользовательский интерфейс, да, это больной вопрос. Чувство прекрасного у меня не из того места растёт.


      1. bano-notit
        21.07.2017 05:06

        Не согласен с задержкой. Причём от слова совсем.
        У Вас есть возможность запускать этот код когда угодно, так почему бы не сделать проверку на весь этот дополнительный контент? Да и кстати, от чего считается эта 1/5 секунд? От начала жизни страницы или от контентлоада?


        1. ntpetrova
          21.07.2017 05:22

          Согласно документации:

          «runAt: The soonest that the JavaScript or CSS will be injected into the tab. Defaults to „document_idle“.»

          дополниетельные опции: «document_start» и «document_end».

          Вопрос по поводу согласия с задержкой должен в первую очередь исходить из позиционирования по целевой аудитории.

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

          Никто не мешает их просто игнорировать, если хочется иметь полный контроль.


          1. bano-notit
            21.07.2017 05:33

            Игнорировать… Ну тогда можно игнорировать пол мира. В любом случае сами по себе эти задержки проблему не решают, потому что у меня вот хабр сейчас с моим интернетом загружался секунд 10. Это нельзя подсовывать пользователю как решение. Его нужно хотя бы предупреждением обособлять, что "такой вот хак сработает невсегда".


            1. ntpetrova
              21.07.2017 05:51

              Спасибо за совет.

              Предупреждение пойдет по графе улучшения UI, но довольно незначительное.
              Завлаба скорее не зачтёт как отдельный пункт, придется что-то еще придумывать.


              1. bano-notit
                21.07.2017 06:03

                На счёт ui можно придумать что-то более глобальное, чем есть сейчас, например плиточки. Вот только они уже используются в material версии chrome://ext… Но сама их реализация с кнокаутом и анимациями думаю сойдёт, если попробуете, ибо когда виден результат невооружённым глазом, то эффект значительно выше.


    1. sumanai
      21.07.2017 16:15

      и большей частью завсегдатаев интернета юзаются.

      Сомневаюсь, что хотя бы процент наберётся.


  1. Daniil1979
    21.07.2017 13:07
    -1

    Автор, пиши ещё.


  1. Anton23
    21.07.2017 13:58
    -1

    Эта статья — мой идеал. Честно. Мне очень понравилось. Даже похоже на мини-рассказ. И даже юморком приправлено. Может поподробнее напишите про работу, доллары и США?(или не США, просто в рифму)


  1. theTilliWilli
    21.07.2017 13:58

    курсовая удалась ;)


  1. panhead
    21.07.2017 15:40

    Автор, Вы молодец. Но создавать подобное — Сизифов труд. **** Google не хочет развивать идею расширений и их возможностей, поэтому приходиться постоянно стрелять себе в ноги и играться с велосипедами. У них даже баги висят на dev уже 5 лет и никто их фиксить не собирается. Мне тоже очень интересная тема улучшения интернета под себя, но адекватных инструметов пока нет. Буквально в прошлом семестре писал курсовую по теме "работа с закладками". Расширение должно было брать информацию через нейронные сети о странице и автоматически добавлять теги для упрощённого поиска в будущем(прокачанный покет). Теперь буду усложнять это на дипломную работу. Поверьте, Google будет обновлять все, кроме функционала и удобства работы с расширениями.


    1. ntpetrova
      21.07.2017 15:57

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

      Корпорация добра стимулирует использование расширений ТОЛЬКО из их магазина.

      Если установить что-то в режиме разработки, то через несколько дней, под предлогом безопасности, при запуске браузера начнут предлагать удалить этот плагин и нету возможности запретить это. Очень не многие пользователи будут готовы нажимать cancel каждый раз.

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

      Так что поверьте, нету завышенных ожиданий.


      1. panhead
        21.07.2017 17:13

        Сообщения с предлогом "отключить пользовательские расширения" не появляются в chrome canary. Мне очень обидно что google не видит потенциала в этом направлении в 2017 году, к сожалению сейчас век нативных приложений.


  1. id_potassium_chloride
    22.07.2017 01:38

    Здорово! А у вас есть такой же, только для Огнелиса?


    1. ntpetrova
      22.07.2017 07:27

      Сконвертировала код под FireFox https://github.com/ntpetrova/CustomActionsFireFox

      Завтра подписанную версию залью.


  1. ntpetrova
    23.07.2017 07:01

    Перевыполнила капиталистические обязательства, добавила поддержку не только FireFox, но и MS Edge.

    Детали смотрите под update 4.


  1. ntpetrova
    23.07.2017 09:35
    +1

    Прочитала статью «И ещё примерно 3,3 тыс новых способов читать «Хабрахабр» и «Гиктаймс»» и последний комментарий как раз типичный случай когда такой плагин мог бы быть использован, если что-то не устраивает в UI/UX сайта.

    Текст комментария (надеюсь автор не возражает):

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

    1. Свернуть ветвь комментариев
    2. Перейти к следующему топ-левел комментарию
    3. Сортировать комментарии по рейтингу»

    Попробовала реализовать в меру своего понимания как было бы удобно.

    Как попробовать:

    Открываем «options», добавляем новый элемент, вводим любое имя.

    В url: ^https?://[^/]*(habrahabr|geektimes)\.ru

    В Source копируем код
    if(!document.querySelector('a[data-cid]'))
    {
       document.querySelectorAll('.comments_list ul > li.comment_item').forEach(li => {
    		if(li.querySelector('ul > li')) {
    			var liNext = li.nextElementSibling;
    			li.parentNode.querySelectorAll('#' + li.id + ' > .comment_body .comment-item__controls').forEach(dv => {
    				var a = document.createElement('a');
    				a.innerText = 'hide responses';
    				a.setAttribute('href', '#');
    				a.setAttribute('data-cid', li.id);
    				dv.appendChild(a);
    
    				if(liNext) {
    					var a = document.createElement('a');
    					a.style.marginLeft = '1em';
    					a.innerText = 'next in the level';
    					a.setAttribute('href', '#' + liNext.id);					
    					dv.appendChild(a);
    				}
    			}); 
    		}
            });
    
            var rates = [];
            document.querySelectorAll('.comments_list ul > li.comment_item').forEach(li => {
    		var rate = parseInt(li.parentNode.querySelector('#' + li.id + ' > .comment_body .js-voting span').innerText.replace('–', '-'));
    		rates.push({ r: rate, id: parseInt(li.id.substring(8)) });
            });
    
            rates.sort(function(a, b) {
                if(a.r < b.r)
                    return 1;
                else if(a.r > b.r)
                    return -1;
                else {
                    if(a.id < b.id)
                        return -1;
                    else if(a.id > b.id)
                        return 1;
                    else
                        return 0;
                }
            });
    
    	var currentRate = 0;
    	var a = document.createElement('a');					
    	a.innerText = '++';
    	a.setAttribute('style', 'margin-top: 2em; font-weight: bold; font-size: 20px; color: #808020; background-color: white; text-decoration: none; cursor: pointer;');
            document.querySelector('#xpanel').appendChild(a);
    	a.addEventListener('click', (event) => {
    		if(currentRate < rates.length)
    		{
    			var url = location.href;
    			location.href = "#comment_" + rates[currentRate].id;
    			history.replaceState(null,null,url);
    			currentRate++;
    		}
    	});
    
       document.querySelector('.comments_list').addEventListener('click', (event) => {
    	var t = event.target;
    	if(!t.getAttribute('data-cid'))
    		return;
    
    	event.preventDefault();
            event.stopPropagation();
    	t.innerText = (t.innerText == 'hide responses' ? 'show responses' : 'hide responses');
            document.querySelectorAll('li#' + t.getAttribute('data-cid') + ' > ul').forEach(ul => { 
    		ul.style.display = (ul.style.display == 'none' ? 'block' : 'none'); 
    	});	
       });
    }
    


    1. mmatrosov
      23.07.2017 13:22

      Спасибо! Для мобильных браузеров и, соответственно, мобильной версии сайта работать будет?


      1. bano-notit
        23.07.2017 17:48

        А разве можно ставить дополнения под хром в больной версии хрома?