Pikabu Enhancement Suite

Новый новый дизайн пикабу. Не забудь установить юзерстиль.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Pikabu Enhancement Suite
// @version      1.4.8
// @license      CC-BY-SA-4.0
// @description  Новый новый дизайн пикабу. Не забудь установить юзерстиль.
// @author       NeverLoved
// @match        https://pikabu.ru/*
// @grant        none
// @namespace https://greasyfork.org/users/175168
// ==/UserScript==

(function() {
    'use strict';

    // Source: https://github.com/jserz/js_piece/blob/master/DOM/ParentNode/prepend()/prepend().md
    (function (arr) {
        arr.forEach(function (item) {
            if (item.hasOwnProperty('prepend')) {
                return;
            }
            Object.defineProperty(item, 'prepend', {
                configurable: true,
                enumerable: true,
                writable: true,
                value: function prepend() {
                    var argArr = Array.prototype.slice.call(arguments),
                        docFrag = document.createDocumentFragment();

                    argArr.forEach(function (argItem) {
                        var isNode = argItem instanceof Node;
                        docFrag.appendChild(isNode ? argItem : document.createTextNode(String(argItem)));
                    });

                    this.insertBefore(docFrag, this.firstChild);
                }
            });
        });
    })([Element.prototype, Document.prototype, DocumentFragment.prototype]);

    let _c;
    var defconfig = {
        "top_big_searchbar":false,
        "top_no_avatar":true,
        "porn_stealth":true,
        "restore_f":true,
        "great_comment_links":true,
        "post_header_collapse":true,
        "sidebar_icons":true,
        "sidebar_feed_link":false,
        "enable_nano_nav":true,
        "oldschool_favicon": false,

    };
    try {
        _c = JSON.parse(window.localStorage.getItem('enhancements'));
        if(!Object.keys(_c).length) {
            throw new Error('Empty');
        }
    } catch(e) {
        console.log('Config is empty, using defconfig');
        _c = defconfig;
    }

    var config = {
        values:_c,
        set:function(param, value) {
            this.values[param] = value;
            window.localStorage.setItem('enhancements', JSON.stringify(this.values));
        },
        get:function(param) {
            return this.values[param];
        }
    };

    function findParentByTag(el, tag) {
        while (el.parentNode) {
            el = el.parentNode;
            if (el.tagName === tag)
                return el;
        }
        return null;
    }

    function nodeIndex(node) {
        var children = node.parentNode.children;
        var num = 0;
        for (var i=0; i<children.length; i++) {
            if (children[i]==node) return num;
            if (children[i].nodeType==1) num++;
        }
        return -1;
    }

    var scrollTo = function(elem) {
        if(elem) {
            try {
                elem.scrollIntoView({behavior:'smooth', 'block':'start'});
            } catch(e) {
                console.log('Не удалось проскроллить к элементу', elem, e);
            }
        }
    };

    var scrollInstant = function(elem) {
        if(elem) {
            try {
                elem.scrollIntoView({behavior:'instant', 'block':'start'});
            } catch(e) {
                console.log('Не удалось проскроллить к элементу', elem, e);
            }
        }
    };


    if(window.location.pathname == '/settings') {
        var onchange = function(e) {
            let checkbox = e.target;
            config.set(checkbox.id, checkbox.checked);
        };

        var create_config_item = function(caption, id, value) {
            let checkbox = document.createElement('input');
            checkbox.setAttribute('id', id);
            checkbox.setAttribute('type', 'checkbox');
            if(value) {
                checkbox.checked = true;
            }
            checkbox.onchange = onchange;
            let label = document.createElement('label');
            label.innerText = caption;
            label.setAttribute('for', id);

            let tr = document.createElement('tr');
            tr.append(document.createElement('td'));
            tr.append(document.createElement('td'));
            tr.children[0].append(label);
            tr.children[1].append(checkbox);
            return tr;
        };

        var sett_table = document.createElement('table');
        sett_table.className = "enhanced_settings";
        var pew = document.createElement('style');
        pew.innerText = ".enhanced_settings [type=checkbox] { visibility: visible!important; }";
        document.body.append(pew);
        console.log(pew);
        [
            create_config_item("Иконки в меню сайдбара", 'sidebar_icons', config.get('sidebar_icons')),
            create_config_item('"Моя Лента" в правом меню', 'sidebar_feed_link', config.get('sidebar_feed_link')),
            create_config_item("Скрывать пост нажатием на пустое место в заголовке", 'post_header_collapse', config.get('post_header_collapse')),
            create_config_item("Скрытие просмотренных постов по F", 'restore_f', config.get('restore_f')),
            create_config_item("Включить плавающую навигацию справа", 'enable_nano_nav', config.get('enable_nano_nav')),
            create_config_item("Добавить нормальные ссылки на комментарии [#]", 'great_comment_links', config.get('great_comment_links')),
            create_config_item("Строка поиска всегда развёрнута", 'top_big_searchbar', config.get('top_big_searchbar')),
            create_config_item("Убрать быстрое меню в шапке", 'top_no_avatar', config.get('top_no_avatar')),
            create_config_item("Скрытная иконка клубнички", 'porn_stealth', config.get('porn_stealth')),
            create_config_item("Старая иконка сайта", 'oldschool_favicon', config.get('oldschool_favicon')),
        ].forEach(function(tr) { sett_table.append(tr); });
        document.querySelector('form[name="general"] section').append(sett_table);
        //document.querySelector('form[name="general"] .page-settings__table tbody').append();
    }

    var sidebar = document.querySelector('.sidebar aside');
    var top_menu = document.querySelector('header.header');
    var profile_info = sidebar.querySelector('.profile-info');
    var answers_link = sidebar.querySelector('.menu a.menu__item[href="/answers"]');
    var main_menu = answers_link.parentNode;
    var rating_container = profile_info.children[0];
    var subscribers_container = profile_info.children[1];

    var rating_val = 0;
    try {
        rating_val = JSON.parse(document.querySelector('[data-entry=initParams]').innerText).userKarma;
    } catch(e) {
        if((rating_val = rating_container.getAttribute('aria-label')) == null) {
            rating_val = rating_container.children[0].innerText;
            rating_val = parseFloat(rating_val.replace(/[^0-9.]/g, ''));
        }
    }

    var subscribers_val = 0;
    if((subscribers_val = subscribers_container.getAttribute('aria-label')) == null) {
        subscribers_val = subscribers_container.children[0].innerText;
    }
    subscribers_val = parseInt(subscribers_val.replace(/[^0-9]/g, ''));

    var rating = document.createElement('span');
    rating.innerText = "Рейтинг: "+rating_val;
    rating.style['padding-left'] = getComputedStyle(answers_link)['padding-left'];
    rating.className = "rating";

    var subscribers = document.createElement('span');
    subscribers.innerText = "Подписчиков: "+subscribers_val;
    subscribers.style['padding-left'] = getComputedStyle(answers_link)['padding-left'];
    subscribers.className = "subscribers";

    if(config.get('sidebar_feed_link')) {
        var feed_link_top = top_menu.querySelector('a[href="/subs"]');
        var bell = feed_link_top.querySelector('.bell');
        if(bell) {
            bell.className = "bell bell_profile-menu";
        }
        var clss = "menu__item";
        if(feed_link_top.parentNode.className.indexOf("header-menu__item_current")+1) {
            clss += " menu__item_current";
        }
        feed_link_top.className += clss;
        main_menu.prepend(feed_link_top);
        feed_link_top.style.display = 'block';
    }

    if(config.get('oldschool_favicon')) {
        document.getElementById("favicon").setAttribute("href", "https://cs.pikabu.ru/favicon2x.ico");
    }

    main_menu.prepend(subscribers);
    main_menu.prepend(rating);

    var edits = sidebar.querySelector('.menu a.menu__item[href="/edits"]');
    if(edits) {
        var unneeded_menu = edits.parentNode;
        main_menu.append(edits);
        unneeded_menu.remove();
    }
    profile_info.remove();

    try {
        var styluses = [].slice.call(document.querySelectorAll('.stylus'));
        var stylus;
        for(var i in styluses) {
            stylus = styluses[i];
            stylus.innerHTML = stylus.innerHTML.replace('.sidebar-block__header {', '.'+sidebar.querySelector(".sidebar-block__content").previousElementSibling.className+' {');
        }
    } catch(e) {
        console.log("Не установлен юзерстиль или нет доступа к блоку контента в сайдбаре.");
    }


    if(config.get('sidebar_icons')) {
        var menu_items = [].slice.call(main_menu.children); //Конвертируем HTMLCollection в массив
        menu_items.forEach(function(item) {
            let icon_container = document.createElement('span');

            icon_container.className = "icon_container";
            let i = document.createElement('i');
            let space = document.createTextNode(' ');
            let icon = 'circle-o';
            switch(item.getAttribute('href')) {
                case '/answers':
                    if(item.children.length) {
                        icon = "envelope-o";
                    } else {
                        icon = "envelope-open-o";
                    }
                    break;
                case '/comments':
                    icon = "comments-o";
                    break;
                case '/liked':
                    icon = "thumbs-o-up";
                    break;
                case '/saved-stories':
                    icon = 'floppy-o';
                    break;
                case '/subs-list':
                    icon = 'list';
                    break;
                case '/ignore-list':
                    icon = 'times';
                    break;
                case '/notes':
                    icon = 'sticky-note-o';
                    break;
                case '/edits':
                    icon = 'pencil';
                    break;
                case '/subs':
                    icon = "file-text-o";
                    break;
                default:
                    if(item.className == "rating") {
                        icon = "bar-chart";
                    } else if(item.className == "subscribers") {
                        icon = "users";
                    }
                    break;
            }
            i.className = 'fa fa-'+icon;
            icon_container.prepend(i);
            item.prepend(space);
            item.prepend(icon_container);
        });
    }
    var right_menu = top_menu.querySelector('.header-right-menu');
    var search_bar = right_menu.querySelector('.header-right-menu__search');

    if(config.get('top_big_searchbar')) {
        search_bar.style.width = "250px";
        search_bar.style['background-color'] = "#fff";
        search_bar.style['border-radius'] = "2px";
        search_bar.querySelector('button').style.color = "#8ac858";
    }
    if(config.get('top_no_avatar')) {
        right_menu.querySelector('.avatar').remove();
    }
    var notify_btn = right_menu.querySelector('.header-right-menu__notification');
    notify_btn.innerHTML = '<i class="fa fa-bell fa-bigger" style="font-size:1.3em"></i>';
    notify_btn.style['line-height'] = '1px';
    notify_btn.style['margin-right'] = '0px';


    var user_info = sidebar.querySelector('.user');
    var settings = user_info.querySelector('a[href="/settings"]');
    var exit = user_info.querySelector('.user__exit');
    var nick_container = user_info.querySelector('.user__nick_big').parentNode;

    try {
        var straw = sidebar.querySelector('[data-role="switch-adult"]');
        if(config.get('porn_stealth')) {
            straw.children[0].innerHTML = '<i class="fa fa-bigger fa-user-secret"></i>';
        } else {
            straw.children[0].innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon--ui__strawberry"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon--ui__strawberry"></use></svg>';
        }
        var qs = straw.parentNode.remove();
    } catch(err) {
        console.log('Настройка клубнички выключена.');
        var straw = document.createTextNode('');
    }

    try {
        var displaymode = sidebar.querySelector('[data-role="display-mode"]');
        displaymode.previousElementSibling.remove();
        var quickset = displaymode.parentNode.parentNode.parentNode;
        displaymode.style.float = "none";
        displaymode.style.display = "inline-block";
        var dtxt = displaymode.children[0];
        if(dtxt.innerText.indexOf('скрыто')+1) {
            dtxt.innerText += ' просмотренных постов';
        } else {
            dtxt.innerText += ' просмотренные посты';
        }
        var panel = document.querySelector('main section:first-child');
        //panel.insertBefore(displaymode, panel.lastChild);
        panel.insertBefore(displaymode, panel.lastElementChild);
        quickset.remove();

    } catch(err) {
        //var straw = document.createTextNode('');
    }

    settings.innerHTML = '<i class="fa fa-cog fa-bigger"></i>';
    settings.style.color = '#757575';

    exit.style.display = "inline-block";
    exit.innerHTML = '<i class="fa fa-sign-out fa-2x"></i>';


    var btn_container = exit.parentNode;
    btn_container.className += " btn_container";

    btn_container.append(settings);
    btn_container.append(document.createTextNode(' '));

    btn_container.append(notify_btn);
    btn_container.append(document.createTextNode(' '));
    btn_container.append(straw);

    user_info.append(exit);
    if(config.get('post_header_collapse')) {
        document.querySelector('main').addEventListener('click', function(e) {
            if(e.target.nodeName == "HEADER") {
                e.target.parentNode.parentNode.querySelector('.collapse-button').click();
            }
        }, false);
    }

    var remove_visited = function(shortcut) {
        document.querySelectorAll('article.story a.story__title-link_visited').forEach(function(item) {
            var story = item.parentNode.parentNode.parentNode.parentNode;
            story.setAttribute('data-visited', "true");
        });
        var curr = current_story();
        var scroll_next = true;
        var is_video = false;
        [].slice.call(document.querySelectorAll('article.story[data-visited="true"]')).forEach(function(item) {
            if(item == curr) {
                var yt = item.querySelector('iframe');
                if(yt) {
                    console.log('Нельзя прятать текущий пост если в нём есть iframe плеера. TODO: получать статус воспроизведения через Youtube Api. Или не TODO, ну его нафиг.');
                    scroll_next = false;
                    if(shortcut) { //Если remove_visited вызвано нажатием клавиши то пытаемся развернуть плеер в фуллскрин
                        var requestFullscreen = yt.requestFullScreen || yt.mozRequestFullScreen || yt.webkitRequestFullScreen || yt.msRequestFullscreen;
                        if(requestFullscreen) {
                            scrollInstant(item); //Скроллим к посту т.к скролл уезжает при удалении предыдущих постов
                            requestFullscreen.call(yt);
                        }
                    }
                    return;
                }
            }
            item.style.display = "none"; //для обратной совместимости с теми, кто не обновит юзерцсс
            item.setAttribute('data-hidden', "true");
        });
        if(scroll_next) {
            var next_story = document.querySelector('article.story[data-visited="false"]');
            scrollInstant(next_story);
        }
    };

    var current_story = function(){
        /* Множитель зума нужен только для хрома, в фаерфоксе там будет ~1 */
        var zoom = parseFloat((window.outerWidth/parseInt(getComputedStyle(document.documentElement,null).width)).toFixed(2));
        var el = document.elementFromPoint((window.outerWidth/zoom)/2, (window.outerHeight/zoom)/4);
        if(el.className.indexOf('stories-feed__container')+1) {
            console.log('Попали в margin.');
            window.scroll(0, window.scrollY-15); //margin-top поста 15px, сдвинув скролл на 15 пикселей мы куда-нибудь да попадём :)
            el = document.elementFromPoint((window.outerWidth/zoom)/2, (window.outerHeight/zoom)/4);
        }
        var story = findParentByTag(el, 'ARTICLE');
        return story;
    };
    var next_story = function() {
        try {
            var cs = current_story();
            if(!cs) {
                console.log('Текущей истории нет, переходим к первой видимой.');
                return document.querySelector('article.story:not([data-hidden="true"])');
            }
            var ni = nodeIndex(cs)+1;
            var ns = [].slice.call(document.querySelectorAll('article.story:nth-child(n+'+ni+'):not(:nth-child('+ni+')):not([data-hidden="true"])'));
            var next = ns.shift();

            var prev = prev_story(true);
            if(prev && prev.getClientRects()[0].top > 0) {
                console.log('Ох уж эти короткие посты.');
                next = cs; //следующий пост на самом деле тот, что мы уже считаем текущим
            }

            if(!next) {
                throw('');
            }
            return next;
        } catch(e) {
            console.log('Нет следующей истории.');
        }
    };
    var prev_story = function(simple) {
        try {
            var cs = current_story();
            var ni = nodeIndex(cs)+1;
            var ps = [].slice.call(document.querySelectorAll('article.story:nth-child(-n+'+ni+'):not(:nth-child('+ni+')):not([data-hidden="true"])'));
            var prev = ps.pop();
            if(!simple) {
                if(prev.getClientRects()[0].top > 0) { //пост слишком маленький, и это он на самом деле является current_story
                    console.log('Ох уж эти короткие посты.');
                    prev = ps.pop(); //достаём предыдущий пост
                }
            }
            if(!prev) {
                throw('');
            }
            return prev;
        } catch(e) {
            if(!simple) {
                console.log('Нет предыдущей стории.');
            }
            //return null;
        }
    };

    if(config.get('restore_f')) {
        if(document.querySelectorAll('article.story').length > 1) {
            /**
             * UPD: mutationObserver больше не нужен для целей скрытия,
             * в последнем обновлении посты сами помечаются прочитанными.
             * Используем это:
            */

            window.addEventListener('keyup', function(e) {
                if(e.key == "f" || e.key == "F" || e.key == "а" || e.key == "А") {
                    remove_visited(true);
                }
            });

            try {
                var fkey = document.querySelector('.hotkeys').parentNode.querySelector('[aria-label="Показать полностью"]');
                fkey.setAttribute('aria-label', 'Скрыть прочитанное');
                fkey.children[1].innerHTML = "<i class='fa fa-eye-slash'></i>";
            } catch(e) {
                //console.log('хоткеи не найдены');
            }
        }
    }

    if(config.get('enable_nano_nav')) {
        if(document.querySelectorAll('article.story').length > 1) {
            var nano_nav = document.createElement('div');
            nano_nav.id = "nano_nav";
            nano_nav.className = "sidebar-block sidebar-block_border";

            var separator = document.createElement('span');
            separator.className = "separator";

            var separator_empty = document.createElement('span');
            separator_empty.className = "separator empty";

            var prev = document.createElement('span');
            var next = document.createElement('span');
            var hide = document.createElement('span');

            prev.className = "link";
            next.className = "link";
            hide.className = "link";
            prev.innerHTML = '<i class="fa fa-chevron-left fa-2x hint" aria-label="Назад (или нажмите кнопку A)"></i>';
            next.innerHTML = '<i class="fa fa-chevron-right fa-2x hint" aria-label="Вперед (или нажмите кнопку D)"></i>';
            hide.innerHTML = '<i class="fa fa-eye-slash fa-2x hint" aria-label="Скрыть просмотренные (или нажмите кнопку F)"></i>';
            prev.onclick = function() { scrollTo(prev_story()); };
            next.onclick = function() { scrollTo(next_story()); };
            hide.onclick = function() { remove_visited(); };
            nano_nav.append(prev);
            nano_nav.append(document.createTextNode(' '));
            nano_nav.append(separator);
            nano_nav.append(document.createTextNode(' '));
            nano_nav.append(next);
            nano_nav.append(document.createTextNode(' '));
            nano_nav.append(separator_empty);
            nano_nav.append(document.createTextNode(' '));
            nano_nav.append(hide);
            document.body.append(nano_nav);
        }
    }

    if(config.get('great_comment_links')) {

        var makeCommentGreatAgain = function(element) {
            let comment_tools = element.querySelector('.comment__tools');
            let branch_link = comment_tools.querySelector('[data-role="link"]');
            let branch_link_href = branch_link.getAttribute('href');
            branch_link.setAttribute('aria-label', 'Ссылка на ветку комментариев');

            let comment_link = document.createElement('a');
            comment_link.className = 'comment__tool hint';
            comment_link.setAttribute('data-role', 'comment_link');
            comment_link.setAttribute('aria-label', 'Ссылка на комментарий');
            comment_link.setAttribute('href', branch_link_href.replace('?cid=', '#comment_'));
            comment_link.innerText = '#';
            comment_tools.insertBefore(comment_link, branch_link);
            element.setAttribute('data-great', "true");
        };
        var makeCommentsGreatAgain = function() {
            document.querySelectorAll('.comment:not([data-great])').forEach(function(item) {
                makeCommentGreatAgain(item);
            });
        };
        makeCommentsGreatAgain();
        var jumpto = function() {
            if(window.location.hash.indexOf('comment_')+1) {
                var comment = document.querySelector(window.location.hash);
                if(comment) {
                    comment.scrollIntoView({behavior:'smooth', 'block':'start'});
                } else { //В списке комментариев нет загруженного комментария
                    var more_comments = document.querySelector('button.comments__more-button');
                    if(more_comments) {
                        more_comments.scrollIntoView({behavior:'smooth', 'block':'start'});
                        more_comments.className = more_comments.className += " button_yellow";
                        (function() { setTimeout(function() { console.log('release');more_comments.className = more_comments.className.replace(' button_yellow', ''); }, 2500); })();
                    }
                }
            }
        };


        if(window.location.href.indexOf('/story/')+1) {
            jumpto();
            var is_comment_already = null;
            if(window.location.hash.indexOf('comment_')+1) {
                 is_comment_already = document.querySelector(window.location.hash);
            }
            var more_comments_btn = document.querySelector('button.comments__more-button');
            if(more_comments_btn) {
                more_comments_btn.addEventListener('click', function() {
                    var emitted = false;
                    var e = document.createEvent('Event');
                    e.initEvent('comments.loaded', true, true);
                    var comment_observer = new MutationObserver(function(records) {
                        records.some(function(record) {
                            if(record.target.className.indexOf('comment')+1) {
                                document.dispatchEvent(e);
                                comment_observer.disconnect();
                                return true;
                            } else {
                                return false;
                            }
                        });
                    });
                    comment_observer.observe(document.querySelector('section.comments'),  {'childList':true, 'subtree':true});

                });
            }



            document.addEventListener('comments.loaded', function() {
                console.log('comments loaded emitted');
                makeCommentsGreatAgain();
                if(!is_comment_already) {
                    jumpto();
                }
            });
        }
    }

})();