Happy end моей истории со скриптом для комфортного чтения Хабра

в 19:02, , рубрики: javascript, интерфейсы, я учусь, метки: ,

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

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

       var trackerLink = document.querySelector('.userpanel > .top > a.count');
        trackerLink.href = '#tracker_updates';
        var updates = document.createElement('ul');
        updates.className = 'updates';
        updates.style.display = 'none';
        userpanel.appendChild(updates);
        trackerLink.onclick = function (event) {
            event.preventDefault();
            updates.style.display = (updates.style.display != 'none' ? 'none' : 'block');
        }; 

И логически перешел к написанию функции парсинга страниц трекера:

        function getUpdates(url, getUrl) {
            var xmlhttp = new XMLHttpRequest();
            xmlhttp.responseType = 'document';
            xmlhttp.onreadystatechange = function () {
                if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
                    var tracks = xmlhttp.responseXML.querySelectorAll(url);
                    for (i = 0; i < tracks.length; i++) {
                        var post = tracks[i];
                        post.removeChild(post.firstElementChild);
                        updates.appendChild(post);
                        post.outerHTML = post.outerHTML.replace(/td/g, "li");
                    }
                }
            }
            xmlhttp.open("GET", getUrl, true);
            xmlhttp.send();
        }

        getUpdates('tr.new > td.event_type', '/tracker/subscribers/');
        getUpdates('tr.new > td.mention_type', '/tracker/mentions/');

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

        (function () {
            var xmlhttp = new XMLHttpRequest();
            xmlhttp.responseType = 'document';

            xmlhttp.onreadystatechange = function () {
                if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
                    var tracks = xmlhttp.responseXML.querySelectorAll('tr.new > td.post_title');
                    var commentCounts = xmlhttp.responseXML.querySelectorAll('tr.new > td.comment_count');
                    for (i = 0; i < tracks.length; i++) {
                        var track = tracks[i];
                        var commentCount = commentCounts[i].firstChild.nextSibling;
                        commentCount.className = 'count';
                        track.appendChild(commentCount);
                        updates.appendChild(track);
                        track.outerHTML = track.outerHTML.replace(/td/g, "li");

                    }
                
            };
            xmlhttp.open("GET", '/tracker/', true);
            xmlhttp.send();
        })();

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

Вот как это выглядит на скриншоте

image

Все бы хорошо, да наверняка не всем хочется каждый раз кликать кнопку отображения все скрытых картинок или разворачивать дерево ответов к комментариям. Вдруг нужна только пользовательская панель. И тут решено было наконец добавить управление настройками, чтобы комфорт был по-настоящему таковым. Почти сразу пришел к варианту с хранением настроек в local storage и тут процесс встал, так как правильнее было бы использовать родные для каждого из браузеров методы работы с локальным хранилищем. Начал с изучения Chrome Storage API и……им же и закончил, потому что слишком накладно получается строить луна-парк для каждого вместе счастливого будущего для всех и сразу. Появились некотрые идеи, но ввиду недостаточного опыта и просто ради совета бывалого обратился за помощью к spmbt. Он мне как раз подсказал элегантный вариант в виде трех простых функций:

var setLocStor = function(name, hh){
        if(!localStorage) return;
        localStorage['custom_'+ name] = JSON.stringify({h: hh});
    },
    getLocStor = function(name){
        return (JSON.parse(localStorage && localStorage['custom_'+ name] ||'{}')).h;
    }
    ,removeLocStor = function(name){localStorage.removeItem('custom_'+ name);}

Насколько я понимаю, в случае использования Storage API Хрома запросы будут выполняться асинхронно (прошу меня поправить, так как могу ошибаться).

Не буду мучать вас портянками с кодом и вкратце упомяну о том, что создавал именованные чекбоксы со значением по-умолчанию disabled, а для отключения фичи значение менялось на enabled. При клике по каждому чекбоксу выполняется запись пары ключ-значение в local storage. При вызове блока настроек читаются записи local storage и чекбоксам присваиваются соответствующие состояния (при значении ключа disabled чекбокс в состоянии false и если enabled — true). Функции, отвечающие за фичу «слушают» значение своего ключа в local storage и выполняются только если значение в состоянии disabled, то есть галочку «отключить» в настройках не установили.

Скриншот с настройками
image

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

Как это выглядит

image

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

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

Спасибо всем, кто дочитал и, надеюсь, кого-то убедил повторить «путь самурая».
Буду признателен за конструктивную критику и рекомендации.

Ссылка на Chrome webstore
Ссылка на Github

Автор: Glebcha

Источник


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


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