Pull to refresh

Comments 19

Чтобы вызвать функцию сразу после ее объявления можно делать так:
(function () {
/*код */
})();

И разве не короче было бы сделать
document.querySelectorAll('.content img');

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

Поправьте меня пожалуйста если я сделал неправильные выводы.
По-моему все возможные частные случаи перечислять более затратно, чем отключить все, а потом включить то что действительно необходимо ( белые vs черные списки). В вашем случае это 9 проходов по DOM против 2х.
Из большинства объявленных функций повторно у вас используется если я не ошибаюсь только одна. Остальное — линейное программирование. Для семантики и отладки на данном уровне комментариев вполне достаточно.
Да, насчет изображений с вами соглашусь и позволю себе добавить, что в случае с перечислением не нужно будет исправлять скрипт если появится пресловутая кнопка редактирования поста с изображением. Но все же в данном случае не так сложно будет сделать быстрое исправление в угоду оптимизации.
Комментарии я стараюсь не плодить, так как лучше сделать код семантичным. Иногда имя функции отражает суть длинного комментария, а зачем лишний текст в таком случае.

Спасибо за пояснения и правильное направление для оптимизации скрипта.
Комментарии очень даже нужны. Например без комментариев в статье кусок кода
            function declOfNum(number, titles) {
                cases = [2, 0, 1, 1, 1, 2];
                return titles[(number % 100 > 4 && number % 100 < 20) ? 2 : cases[(number % 10 < 5) ? number % 10 : 5]];
            }

тяжело воспринять при первом прочтении — глаза мозолят «магические числа». Опять же о какой семантике может идти речь если в наличии функции hideReplies() и hideR()?
В случае со склонением пожалуй да.
Спасибо, буду исправляться :)
После того как получил инвайт на Хабр, для комментариев появилась ссылка «ответить» и кнопка раскрытия дерева ответов оказалась под этой ссылкой съедая свободное пространство.
Сделал хотфикс для корректного отображения и
теперь все выглядит так
image
Я бы хранил селекторы так:

var selectors = [
    '.content > img',
    '.content > div > img',
    '.content > div > div > img',
];

var imgs = document.querySelectorAll(selectors.join(', '));
Немного поправил код, послал пул реквест. Давно не писал на vanilla.js, местами тупил, но вроде осилил.

Скрытый текст
/* Add css rules to specified selector */
function addCSSRule(selector, rules, sheet) {
    sheet = sheet || document.styleSheets[0];
    if (sheet.insertRule) {
        sheet.insertRule(selector + '{' + rules + '}');
    } else {
        sheet.addRule(selector, rules);
    }
}
/* Return human-friendly declension of provided numbers */
function declOfNum(number, titles) {
    cases = [2, 0, 1, 1, 1, 2];
    return titles[(number % 100 > 4 && number % 100 < 20) ? 2 : cases[(number % 10 < 5) ? number % 10 : 5]];
}
/* Return chidren nodes of an element with a specified class name */
function getChildrenByClassName(element, className) {
    var children = [];
    for (var i = 0; i < element.childNodes.length; i++) {
        if (element.childNodes[i].className == className) {
            children.push(element.childNodes[i]);
        }
    }
    return children;
}
/* Hide single nodeList */
function hideNode (nodes) {
    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        node.style.display = 'none';
    }
}
/* Hide nodeLists provided as array */
function hideNodes (nodes) {
    if( Object.prototype.toString.call( nodes ) === '[object Array]' ) {
        for (var i = 0; i < nodes.length; i++) {
            hideNode(nodes[i]);
        }
    }
    else {
        hideNode(nodes);
    }
}
/* Toggle visibility of provided elements */
function toggleElements (elements) {
    for (var i = 0; i < elements.length; i++) {
        var element = elements[i];
        element.style.display = (element.style.display != 'none' ? 'none' : 'block');
    }
}
/* Create a button ( span.buttons > a.button ) */
function createBtn (className, value, onclick) {
    className = className || 'button';
    value = value || 'Button';
    onclick = onclick || function (event) {};

    var newContainer = document.createElement('span');
        newContainer.className = 'buttons';
    var newBtn = document.createElement('a');
        newBtn.className = className + ' button';
        newBtn.appendChild(document.createTextNode(value));
        newBtn.href="#";
        newBtn.onclick = onclick;
        newContainer.appendChild(newBtn);
    return newContainer;
}
/* Replies button event handler */
function commentsBtnClick (event) {
    event.preventDefault();
    var btn = event.currentTarget;
    var comments = btn.parentNode.parentNode.parentNode.parentNode.querySelectorAll('.reply_comments');
    toggleElements(comments);
}

/* C-style Main() =) */
(function(){
    /* Sets of {NodeList} elements to operate with */
    var allReplies = document.querySelectorAll('.reply_comments');
    var sidebarImgs = document.querySelectorAll('.sidebar_right > .banner_300x500, .sidebar_right > #htmlblock_placeholder');
    var contentImgs = document.querySelectorAll('.content img, .message img');

    /* Hide all images and nested replies by default */
    hideNodes([allReplies, sidebarImgs, contentImgs]);

    /* Add button to toggle images visibility */
    var newImgBtn = createBtn('habraimage', '◄ Показать изображения', function (event) {event.preventDefault(); toggleElements(contentImgs);});
    document.querySelectorAll('.main_menu')[0].appendChild(newImgBtn);
    /* Make buton fixed */
    addCSSRule('.habraimage', 'position:fixed; right: 6%; z-index: 1;');

    /* Add buttons to toggle comments visibility */
    var comments = document.querySelectorAll('.comments_list > .comment_item'),
        comment, combody;
    for (var i = 0; i < comments.length; i++) {
        comment = comments[i];
        var replies = comment.querySelectorAll('.reply_comments .comment_body');
        if (replies.length > 0) {
            combody = getChildrenByClassName(comment, 'comment_body')[0];
            replylink = getChildrenByClassName(combody, 'reply')[0];
            if (combody) {
                var newBtn = createBtn('hidereplies', replies.length + declOfNum(replies.length, [' ответ', ' ответа', ' ответов']), commentsBtnClick);
                replylink.appendChild(newBtn);
            }
        }
    }
})();

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

addCSSRule('.reply', 'margin-bottom: 1em;');

Иначе блок с классом reply наезжал на следующий блок комментария.
Как доберусь до дома обязательно попробую исключить класс spoiler_text из функции скрытия изображений
Ну это уже лирика.

Главное что
а) функции и процедуры теперь используются по назначению — не для разметки кода, а для повторного использования,
б) мы не делаем много лишних проходов по DOM,
в) у нас нет множества циклов, которым нужно подбирать новые имена переменных для итерации
г) не нужно возиться с тоннами статичного css,
д) ну и худо-бедно код документирован.

Удачи в дальнейшей разработке!
Спасибо за поддержку!
Буду допиливать до желаемого и ожидать в своей голове/от пользователей идей для нового функционала.
Надеюсь на вашу поддержку и дальнейшие пулл реквесты :)
Критикуйте, так как это сильно помогает не останавливаться на том что есть.
Имея достаточно долгий опыт (года 2) с юзерскриптами и расширениями, сразу по-другому представил решение этой задачи. Прототипы решения у меня уже есть — и генерация кнопки, и скрывание некоторых блоков (пример). Не буду повторять велосипед, а опишу идею:
1) рисунки скрываются-открываются каскадными стилями;

2) прописываются правила CSS типа
.hideimg img{display: none!important;}
и правила незакрывания служебных картинок типа аватаров:
.hideimg img.avatar, ...{display: block!important;}

3) рисуется кнопка (например, функцией $e() в указанном скрипте HabrAjax) и на неё вешается обработчик, переключающий класс hideimg во всех нужных нам блоках. Всё, задача решена.

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

Расширения браузера для этой задачи — тоже трата времени, оправданная разве что опытом знакомства с API расширений. Быстрее написать единый юзерскрипт, одинаково работающий в 4 или 5 основных браузерах — самое сложное будет — поддержать querySelector в IE (например, взять решение из Sizzle / jQuery).
Когда появится больше одного селектора, то буду использовать массив для исключений как предложил truezemez. Нисколечко не оспариваю тот факт, что тут мне помогли и показали как сделать еще лучше и «по-взрослому». В тексте поста я хотел сказать о том, что на большинство ищут решение типовых задач из примеров обучающих курсов на профильных форумах (это я понял после гугления по совпадающему с контекстом типовой задачи вопросу).

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

Если вы хотите поделиться со мной знаниями и указать на иные возможности реализации функционала, то я был бы весьма признателен.
Так я уже всё показал по пунктам. Нажмите ссылку «пример», посмотрите код, найдите использования $e (их там сотня) — делаются разные кнопки, панель настроек, обработчики событий, деревья. Масса вариантов очень похожего на ваши построения. И это как раз не первая итерация, а вторая, когда по коду стало видно, что много общего надо свести в одну функцию. Много полезных техник. Правда, учиться всему этому проще на обычных скриптах — их трассировать можно.
Да я уже смотрел ваш скрипт и во многих местах откровенно запутался. Посмотрел дифы совсем старых/относительно свежих версий, где-то докопался до истины. Пока сложно переваривать, так как хотелось бы все непонятные конструкции видеть с комментариями, но се ля ви, опыта моего маловато будет.
Там, действительно, очень много поводов запутаться, всё писалось кусочками, на результат. Худшее место — основной цикл подгрузки статей, самое раннее ядро по сути. Но теперь хотя бы хрошо понимаю, чего ни в каком случае не надо делать (давать слишком короткие имена, переусложнять структуру). И даёт много размышлений на будущее, как правильно надо подобное делать. Например, нужна модульность, автосборка, автотесты. И вообще, как-то отлаживать на обычных скриптах, чтобы потом переносить в юзер-, потому что отсутствие дебаггинга раза в 3 удлиняет отладку.
Sign up to leave a comment.

Articles