Pikabu UI++

Улучшение интерфейса

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         Pikabu UI++
// @namespace    pikabuUIPlusPlus
// @version      1.60
// @description  Улучшение интерфейса
// @author       Array
// @license      CC-BY-SA-4.0
// @match        *://pikabu.ru/*
// @grant        unsafeWindow
// @grant        window.close
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at document-body
// ==/UserScript==

// simbols source http://myhomeinet.ru/smilegenerator/page-274.html
// colors source https://www.w3schools.com/colors/colors_names.asp
// special thanks:
// @nazarpunk
// @moretraher2020
(function() {
    `use strict`;

    const DATA_LIFETIME = 604800000;
    const DATABASE = `db`;
    const DATABASE_VERSION = `1`;

    const EXP_VALUES = [3, 5, 8];
    const EXP_ICO = [`❊`, `•`, `︿`, `︽`, `⁂`, `★`];

    const NOTE_VALUES = [`#добро`, `#интересно`, `#осторожно`, `#неприемлемо`],
          NOTE_ICO = [`❤`, `♞`, `⚠`, `✖`],
          NOTE_COLORS = [`LimeGreen`, `DodgerBlue`, `OrangeRed`, `Violet`];

    const SUBS_VALUES = [40, 900, 4000, 9000],
          SUBS_ICO = [``, `◔`, `◑`, `◕`, `◉`];

    const BAN_ICO = [``, `ϟ`, `†`];
    const GENDER_ICO = [``, `♂`, `♀`];


    // ************************ ENTRY POINT *************************

    let MAKE_OLD_HEADER = GM_getValue(`MAKE_OLD_HEADER`, 1);
    let MAKE_ANSWER_TOP = GM_getValue(`MAKE_ANSWER_TOP`, 0);
    let MUTE_WATERMARK = GM_getValue(`MUTE_WATERMARK`, 1);
    let MINIFY_AUTHOR_PANEL = GM_getValue(`MINIFY_AUTHOR_PANEL`, 0);

    let SHOW_AUTHOR_LIKE = GM_getValue(`SHOW_AUTHOR_LIKE`, 0);
    let SHOW_SHORT_POST_DESIGN = GM_getValue(`SHOW_SHORT_POST_DESIGN`, 0);
    let SHOW_EMOTIONS = GM_getValue(`SHOW_EMOTIONS`, 1);
    let SHOW_COMMUNITY_DESCRIPTION = GM_getValue(`SHOW_COMMUNITY_DESCRIPTION`, 1);

    let SHOW_HIDDEN_LIKES = GM_getValue(`SHOW_HIDDEN_LIKES`, 1);
    let SHOW_HIDDEN_COMMENT_LIKES = GM_getValue(`SHOW_HIDDEN_COMMENT_LIKES`, 1);

    let SHOW_EXP = GM_getValue(`SHOW_EXP`, 1);
    let SHOW_NOTE = GM_getValue(`SHOW_NOTE`, 1);
    let SHOW_RATE = GM_getValue(`SHOW_RATE`, 1);
    let SHOW_SUBS = GM_getValue(`SHOW_SUBS`, 1);
    let SHOW_BAN = GM_getValue(`SHOW_BAN`, 1);
    let SHOW_GENDER = GM_getValue(`SHOW_GENDER`, 0);

    const ENABLED_QUICK_NOTES = () => SHOW_EXP || SHOW_NOTE || SHOW_RATE || SHOW_SUBS || SHOW_BAN || SHOW_GENDER;

    let SHOW_QUICK_NOTE_0 = GM_getValue(`SHOW_QUICK_NOTE_0`, 1);
    let SHOW_QUICK_NOTE_1 = GM_getValue(`SHOW_QUICK_NOTE_1`, 1);
    let SHOW_QUICK_NOTE_2 = GM_getValue(`SHOW_QUICK_NOTE_2`, 1);
    let SHOW_QUICK_NOTE_3 = GM_getValue(`SHOW_QUICK_NOTE_3`, 1);

    const SHOW_QUICK_NOTE_VALUES = [SHOW_QUICK_NOTE_0, SHOW_QUICK_NOTE_1, SHOW_QUICK_NOTE_2, SHOW_QUICK_NOTE_3];

    let AUTOBAN = GM_getValue(`AUTOBAN`, 0);


    let database = new Map();

    let loadingComments = new Map();
    let loadingOrder = [];
    let updateOrder = [];
    let intersectionItems = [];
    let loadingComment = null;


    let userpageRendered = false;


    // ************************ INIT *************************

    addCustomCSS();

    loadDatabase();


    document.addEventListener("DOMContentLoaded", () => {

        checkUserPage();

        checkSettingsPage();

        renderAnswerTop();

    }, {once: true});


    // ************************ EVENTS *************************

    // Intersections
    const intersector = new IntersectionObserver(entries =>
    {
        for (const entry of entries) {
            if (entry.isIntersecting)
            {
                intersectionItems.push(entry.target);


                if(answer != undefined && entry.target.classList.contains(`story-comments__top-trigger`))
                {
                    sectionTopTrigger.classList.toggle(`answer-trigger__place-holder`, false);

                    commentsRoot.prepend(answer);

                    sectionDownTrigger.classList.toggle(`answer-trigger__place-holder`, true);
                }

                if(answer != undefined && entry.target.classList.contains(`story-comments__down-trigger`))
                {
                    sectionDownTrigger.classList.toggle(`answer-trigger__place-holder`, false);

                    comments.appendChild(answer);

                    sectionTopTrigger.classList.toggle(`answer-trigger__place-holder`, true);

                }
            }
            else
            {
                const index = intersectionItems.indexOf(entry.target);
                if (index > -1) {
                    intersectionItems.splice(index, 1);
                }
            }
        }

    }, { threshold: 0 });


    // Ajax listener
    !function(send) {
        XMLHttpRequest.prototype.send = function(body) {
            send.call(this, body);

            if(body?.includes(`note`))
            {
                checkUserPage();
            }
        };
    }(XMLHttpRequest.prototype.send);


    // Mutation listener
    new MutationObserver(mutations => {
        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                if (!(node instanceof HTMLElement)) continue;

                // watermark
                if (MUTE_WATERMARK && node.dataset?.watermarked == 1)
                {
                    node.remove();
                }

                // header
                if (node.classList.contains(`header__main`))
                {
                    renderHeader(node);
                    setTimeout(() => { renderHeader(node); }, 50);
                }

                // posts
                if (node.classList.contains(`story`))
                {
                    renderPost(node);

                    checkPostReady(node);
                }

                // comments
                if (node.classList.contains(`comment__header`))
                {
                    renderComment(node.parentElement.parentElement);

                    checkCommentReady(node);
                }

            }
        }
    }).observe(document.body, {childList: true, subtree: true});


    // Visbility listener
    document.addEventListener("visibilitychange", function() {
        if(!document.hidden)
        {
            loadDatabase();

            nextLoading();
        }
    });


    // ************************ DATABASE *************************

    function loadDatabase()
    {
        let jobject = JSON.parse(GM_getValue(DATABASE, `{}`));

        database = new Map(Object.entries(jobject));

        let ver = GM_getValue(`version`, ``);
        if(ver != DATABASE_VERSION)
        {
            GM_setValue(`version`, DATABASE_VERSION);
            database = new Map();
        }
    }

    function saveDatabase()
    {
        let json = JSON.stringify(Object.fromEntries(database.entries()));
        GM_setValue(DATABASE, json);
    }

    function resetDatabase()
    {
        GM_setValue(DATABASE, `{}`);
    }


    // ************************ BASEMENT *************************

    function checkUserPage()
    {
        let url = window.location.href;
        let key = url.split(`@`)[1];

        if(url.includes(`@`) && !userpageRendered)
        {
            userpageRendered = true;
            renderUserpageTools();
        }

        if(database.has(key))
        {
            database.get(key).timestamp = 0;

            saveDatabase();

            updateUser(key);
        }
    }

    function checkSettingsPage()
    {
        let url = window.location.href;

        if(url.includes(`settings`)) renderSettings();
    }

    function checkPostReady(node)
    {
        if(!ENABLED_QUICK_NOTES()) return;

        let post = node?.querySelector(`.story__user-link`);

        if(post == null)
        {
            setTimeout(() => { checkPostReady(node) }, 50);
            return;
        }

        let userName = post.getAttribute(`data-name`);

        // check author-panel
        let panel = node?.querySelector(`.story__author-panel`);

        if(MINIFY_AUTHOR_PANEL && panel != null)
        {
            renderPostAuthor(node, post, userName);
            panel.remove();
        }

        checkUsername(userName, post);
    }

    function renderPostAuthor(node, userLink, userName)
    {
        let container = node.querySelector(`.story__community`);

        // move data
        let creationTime = container.querySelector(`.story__creation-date`);

        container.prepend(creationTime);

        // resize avatar
        let avatar = userLink.querySelector(`.avatar`);
        avatar.classList.toggle(`avatar_medium`, false);
        avatar.classList.toggle(`avatar_small`, true);

        // add text
        userLink.append(userName);

        // move elements
        let subContainer = document.createElement(`div`);
        subContainer.classList.add(`story__user`);
        subContainer.classList.add(`user`);
        node.querySelector(`.story__header`).prepend(subContainer);

        subContainer.prepend(container);

        container.prepend(userLink);

        container.classList.toggle(`story__community`, false);
        container.classList.toggle(`story__community_after-author-panel`, false);
        container.classList.toggle(`story__user-info`, true);
    }

    function checkCommentReady(node)
    {
        if(!ENABLED_QUICK_NOTES()) return;

        let commentBase = node.querySelector(`.comment__user`);
        let comment = commentBase?.querySelector(`.user__nick`);

        if(comment == null)
        {
            setTimeout(() => { checkCommentReady(node) }, 50);
            return;
        }

        let userName = commentBase.getAttribute(`data-name`);

        checkUsername(userName, comment);
    }

    function checkUsername(userName, holder)
    {
        if(userName == `DELETED` || userName == `Аноним`) return;

        // check is raw
        if(holder?.textContent.trim() == userName)
        {
            holder.dataset.name = userName;

            intersector.observe(holder);

            addComment(holder, userName);
        }
    }


    function updateUser(requestedUser)
    {
        if(!ENABLED_QUICK_NOTES()) return;

        // comemet usernames
        document.querySelectorAll(`.comment__user`).forEach((commentBase) => {

            let comment = commentBase.querySelector(`.user__nick`);

            let userName = commentBase.getAttribute(`data-name`);


            if(requestedUser == userName)
            {
                addComment(comment, userName);
            }

        });

        //post usernames
        document.querySelectorAll(`.story__user-link`).forEach((post) => {

            let userName = post.getAttribute(`data-name`);

            if(requestedUser == userName)
            {
                addComment(post, userName);
            }
        });

        if(loadingComment == null)
        {
            nextLoading();
        }
    }


    function addComment(comment, userName)
    {
        if(database.has(userName))
        {
            let date = new Date();
            let data = database.get(userName);

            renderUsername(userName, comment, data);

            // update data
            if(date.getTime() > (data.timestamp + DATA_LIFETIME))
            {
                if(loadingComments.has(userName))
                {
                    loadingComments.get(userName).push(comment);
                }
                else
                {
                    loadingComments.set(userName, [comment]);
                    updateOrder.push(userName);
                }
            }
        }
        else
        {
            //request data
            if(loadingComments.has(userName))
            {
                loadingComments.get(userName).push(comment);
            }
            else
            {
                loadingComments.set(userName, [comment]);

                loadingOrder.push(userName);

                nextLoading();
            }
        }

    }


    // ************************ LOADING  *************************

    function nextLoading()
    {
        //find intersection
        let intersectionUsername = null;
        for(const intersected of intersectionItems)
        {
            if(intersected?.dataset?.name == null) continue;

            if(loadingOrder.includes(intersected.dataset.name))
            {
                intersectionUsername = intersected.dataset.name;

                const index = loadingOrder.indexOf(intersectionUsername);
                if (index > -1) {
                    loadingOrder.splice(index, 1);
                }
                break;
            }

            if(updateOrder.includes(intersected.dataset.name))
            {
                intersectionUsername = intersected.dataset.name;

                const index = updateOrder.indexOf(intersectionUsername);
                if (index > -1) {
                    updateOrder.splice(index, 1);
                }
                break;
            }
        }


        if(loadingComment == null && intersectionUsername != null)
        {
            loadingComment = intersectionUsername;

            loadData(loadingComment);
        }
        else if(loadingComment == null && loadingOrder.length > 0)
        {
            loadingComment = loadingOrder.shift();

            loadData(loadingComment);
        }
        else if(loadingComment == null && updateOrder.length > 0)
        {
            // update expired data
            loadingComment = updateOrder.shift();

            loadData(loadingComment);
        }

    }

    function loadData(userName)
    {
        const body = new FormData();
        body.set(`action`, `get_short_profile`);
        body.set(`user_name`, userName);

        fetch(`/ajax/user_info.php`, {
            method: `post`,
            body  : body
        })
            .then(r => r.json())
            .then(result => {
            let data = parseData(result.data.html);

            if(data == null)
            {
                nextLoading();
                return;
            }

            if(document.hidden)
            {
                loadingOrder.push(loadingComment);
                loadingComment = null;

                return;
            }

            database.set(userName, data);

            saveDatabase();

            let comments = loadingComments.get(userName);
            loadingComments.delete(userName);

            if(comments != null)
            {
                for (let comment of comments)
                {
                    renderUsername(userName, comment, data);
                    intersector.unobserve(comment);
                }
            }


            loadingComment = null;

            setTimeout(() => {nextLoading();}, 100 + 400 * Math.random());
        });
    }


    // ************************ CLICKS  *************************

    let clickTimeout = false;
    function clickUserNote(e)
    {
        if(clickTimeout) return;

        clickTimeout = true;

        let note = e.target.getAttribute(`data-note`);
        let uid = e.target.getAttribute(`data-uid`);

        let url = window.location.href;
        let userName = url.split(`@`)[1];

        let data = database.get(userName);

        let message = ``;
        let mtype = `-`;
        if(data.note != (parseInt(note)+1))
        {
            message = NOTE_VALUES[note];
            mtype = `+`;
        }

        let textarea = document.querySelector(`.page-profile`).querySelector(`.profile-note__textarea`).value = message;
        let textdiv = document.querySelector(`.page-profile`).querySelector(`.profile-note__text`).textContent = message;

        const body = new FormData();
        body.set(`action`, `note`+mtype);
        body.set(`id`, uid);
        body.set(`message`, message);

        fetch(`/ajax/users_actions.php`, {
            method: `post`,
            body  : body
        })
            .then(result => {

            data.timestamp = 0;

            updateUser(userName);

            clickTimeout = false;

            applyAutoban(uid, (parseInt(note) == 3));
        });
    }


    function clickQuicknote(e)
    {
        if(clickTimeout) return;

        let note = e.target.getAttribute(`data-note`);
        let uid = e.target.getAttribute(`data-uid`);

        let comment = e.target?.parentElement?.parentElement?.querySelector(`.comment__user`);
        let userName = comment?.getAttribute(`data-name`);

        if(!database.has(userName)) return;

        clickTimeout = true;

        let refTool = e.target.parentElement?.querySelector(`a`);
        let ref = refTool?.getAttribute(`href`);

        let data = database.get(userName);

        let message = ``;
        let mtype = `-`;
        if(data.note != (parseInt(note)+1))
        {
            message = NOTE_VALUES[note]+` `+ref;
            mtype = `+`;
        }

        const body = new FormData();
        body.set(`action`, `note`+mtype);
        body.set(`id`, uid);
        body.set(`message`, message);

        fetch(`/ajax/users_actions.php`, {
            method: `post`,
            body  : body
        })
            .then(result => {

            data.timestamp = 0;

            updateUser(userName);

            clickTimeout = false;

            applyAutoban(uid, (parseInt(note) == 3));
        });
    }

    function applyAutoban(uid, isBan)
    {
        if(!AUTOBAN) return;

        const body = new FormData();
        body.set(`action`, isBan ? `ignore_in_comments` : `unignore_in_comments`);
        body.set(`id`, uid);

        fetch(`/ajax/ignore_actions.php`, {
            method: `post`,
            body  : body
        });
    }

    // ************************ PARSING  *************************

    function smartSlice(text, start, end)
    {
        let strIndex = text.indexOf(start);
        let endIndex = text.indexOf(end, strIndex + start.length + 1);

        return text.slice(strIndex+start.length, endIndex);
    }

    function parseData(html)
    {
        if(html == undefined || html == null) return null;

        let data = new Object();

        let regions = html.split(`information`);

        // ban
        let banStat = 0;
        if(regions[0].includes(`ban-status`))
        {
            banStat = 1;

            if(regions[0].includes(`навсегда`)) banStat = 2;
        }

        // note
        let noteStat = 0;
        if(regions[1].includes(`profile__note`))
        {
            let subRegions = regions[1].split(`profile__note-inner`);
            regions[1] = subRegions[0];

            for (let i = 0; i < NOTE_VALUES.length; i++)
            {
                if(subRegions[1].includes(NOTE_VALUES[i]))
                {
                    noteStat = i + 1;
                }
            }
        }

        // gender
        let genderStat = 0;
        if(regions[1].includes(`шник`)) genderStat = 1;

        if(regions[1].includes(`шница`)) genderStat = 2;


        //exp
        let expStat = 0;
        let expSource =	smartSlice(regions[1], `<span>`, `</div>`);

        let y = expSource.includes(`лет`) || expSource.includes(`год`);
        let m = expSource.includes(`месяц`);

        if(y || m)
        {
            if(y)
            {
                let years = parseInt(expSource);

                expStat = 2 + EXP_VALUES.length;
                for (let i = EXP_VALUES.length - 1; i >= 0; i--)
                {
                    if(years < EXP_VALUES[i])
                    {
                        expStat = 2 + i;
                    }
                }
            }
            else
            {
                expStat = 1;
            }
        }

        let digitalRegions = regions[1].split(`profile__digital`);

        //rate
        let rateStat = 0;

        let rateRaw = smartSlice(digitalRegions[1], `<b>`, `</b>`);

        if(rateRaw.includes(`К`))
        {
            rateStat = rateRaw.replace(`-`, `▽`);
        }
        else
        {
            rateStat = (Math.round(rateRaw/1000) + `K`).replace(`-`, `▽`);
        }

        if(rateStat.includes(`NaN`))
        {
            rateStat = `--`;
        }

        // subs
        let subsStat = 0;
        let rawSubs = smartSlice(digitalRegions[3], `<b>`, `</b>`);

        if(rawSubs.includes(`К`))
        {
            subsStat = 4;
        }
        else if(rawSubs > SUBS_VALUES[0])
        {
            subsStat = SUBS_VALUES.length;
            for (let i = SUBS_VALUES.length - 1; i > 0 ; i--)
            {
                if(rawSubs < SUBS_VALUES[i])
                {
                    subsStat = i;
                }
            }
        }

        // timestamp
        let date = new Date();
        data.timestamp = date.getTime();

        data.exp = expStat;
        data.note = noteStat;
        data.rate = rateStat;
        data.subs = subsStat;
        data.ban = banStat;
        data.gender = genderStat;

        return data;
    }

    // ************************ CSS  *************************

    function addCustomCSS()
    {
        let styles = ``;
        for (let i = 0; i < NOTE_COLORS.length; i++)
        {
            styles += `.`+NOTE_COLORS[i]+`_note {background-color: `+NOTE_COLORS[i]+`;}`;
        }

        styles += `.userpage__forced-on {opacity: 1 !important; visibility: visible !important;}`;
        styles += `.userpage-note-tools {margin-left: 15px !important; margin-top: 4px !important;}`;
        styles += `.userpage-note-tool {font-size: 18px !important; padding: 12px !important; padding-left: 12px !important; padding-right: 12px !important;}`;

        styles += `.header-old {background-color: var(--color--header__bg) !important; --color--header__bg: var(--color-primary-700) !important; color: #f9f9fb !important; --color-primary-100: var(--color-primary-500); --color-primary-800:#f9f9fb; --color-primary-200: var(--color-primary-400)}`;
        styles += `.header-black-old {--color-primary-800:var(--color-primary-900)}`;
        styles += `.header-item-old {--color-primary-700:var(--color-primary-900) !important;}`;
        styles += `.header-item-button-old {--color-primary-700:var(--color-primary-900) !important;}`;

        styles += `.settings__forced-off {opacity: 0 !important; visibility: hidden !important; max-height: 1px !important;}`;
        styles += `.answer-trigger {opacity: 0 !important; visibility: hidden !important; padding: 0px; border: 0px;  max-height: 0px; transition: none !important;}`;
        styles += `.answer-trigger__place-holder {opacity: 0 !important; visibility: hidden !important; padding: 0px; border: 0px;  height: 113px !important; max-height: 113px !important;}`;

        styles += `.nsfw-blur {filter: blur(0px); object-position: 200% 200%; object-fit: none;}`;

        let styleSheet = document.createElement(`style`);
        styleSheet.type = `text/css`;
        styleSheet.innerText = styles;
        document.head.appendChild(styleSheet);
    }

    // ************************ RENDERING  *************************

    function renderUsername(userName, comment, data)
    {
        if(comment == null) return;

        let exp = ``;
        if(SHOW_EXP)
        {
            exp = EXP_ICO[data.exp] + ` `;
        }

        let note = ``;
        if(SHOW_NOTE && data.note > 0)
        {
            note = NOTE_ICO[data.note - 1] + ` `;

            let noteEl = comment?.parentElement?.parentElement?.querySelector(`.avatar__note`);

            if(noteEl == null || noteEl == undefined)
            {
                let avatar = comment?.parentElement?.parentElement?.querySelector(`.avatar`);

                noteEl = document.createElement(`span`);
                noteEl.classList.add(`avatar__note`);

                avatar.appendChild(noteEl);
            }

            // remove prev
            for (let i = 0; i < NOTE_COLORS.length; i++)
            {
                noteEl.classList.toggle(NOTE_COLORS[i]+`_note`, false);
            }

            noteEl.classList.add(NOTE_COLORS[data.note - 1]+`_note`);

        }
        if(SHOW_NOTE && data.note == 0)
        {
            let noteEl = comment?.parentElement?.parentElement?.querySelector(`.avatar__note`);

            if(noteEl != null && noteEl != undefined && noteEl.classList.length > 1)
            {
                noteEl.remove();
            }
        }

        let rate = ``;
        if(SHOW_RATE)
        {
            rate = data.rate + ` `;
        }

        let subs = ``;
        if(SHOW_SUBS)
        {
            subs = SUBS_ICO[data.subs] + ` `;
        }

        let ban = ``;
        if(SHOW_BAN && data.ban > 0)
        {
            ban = ` `+BAN_ICO[data.ban];
        }

        let gender = ``;
        if(SHOW_GENDER && data.gender > 0)
        {
            gender = ` `+GENDER_ICO[data.gender];
        }

        if(comment.childNodes.length > 1)
        {
            for (let i = 0; i < comment.childNodes.length; i++)
            {
                if(comment.childNodes[i].textContent.trim() == userName)
                {
                    comment.childNodes[i].textContent = exp + note + rate + subs + userName + ban + gender;
                }
            }
        }
        else
        {
            comment.textContent = exp + note + rate + subs + userName + ban + gender;
        }

    }

    function renderComment(comment)
    {
        let tools = comment.querySelector(`.comment__tools`);
        let authorLike = comment.querySelector(`.comment__author-like`);
        let rating = comment.querySelector(`.comment__rating-count`);
        let ratingUp = comment.querySelector(`.comment__rating-up`);
        let ratingDown = comment.querySelector(`.comment__rating-down`);

        renderAuthorLikes(authorLike);

        renderCommunityDescription();

        if(tools == null || rating == null || ratingUp == null || ratingDown == null)
        {
            setTimeout(() => { renderComment(comment); }, 50);
            return;
        }


        let meta = comment.getAttribute(`data-meta`);

        if(!comment.hasAttribute(`data-quicknote`) && (meta != null) && !meta.includes(`ua`))
        {
            comment.setAttribute(`data-quicknote`, 1);

            let uid = smartSlice(meta, `aid=`, `;`);

            renderCommentTools(tools, uid);
        }


        if(meta != null && meta.includes(`avh`))
        {
           let rawRating;

           let metaSplit = meta.split(`avh=`);

           if(metaSplit[1].includes(`;`))
           {
               rawRating = smartSlice(meta, `avh=`, `;`);
           }
           else
           {
               rawRating = metaSplit[1];
           }

           renderCommentLikes(rating, ratingUp, ratingDown, rawRating, comment.dataset.id, comment.dataset.authorId);
        }

    }

    function renderCommentTools(tools, uid)
    {
        if(!ENABLED_QUICK_NOTES()) return;

        for (let i = 0; i < NOTE_VALUES.length; i++)
        {
            if(!SHOW_QUICK_NOTE_VALUES[i]) continue;

            let tool = document.createElement(`div`);
            tool.classList.add(`comment__tool`);
            tool.classList.add(`hint`);

            tool.setAttribute(`aria-label`, `Отметить как `+NOTE_VALUES[i]);
            tool.setAttribute(`data-note`, i);
            tool.setAttribute(`data-uid`, uid);

            tool.textContent = NOTE_ICO[i];

            tool.addEventListener(`click`, clickQuicknote);

            tools.appendChild(tool);
        }
    }

    function renderCommunityDescription()
    {
        if(SHOW_COMMUNITY_DESCRIPTION) return;

        let page = document.querySelector(`.page-story`);

        page?.querySelectorAll(`.section-group`).forEach((section) => {

            let item = section.querySelector(`.community-comments`);

            if(item != null)
            {
                section.remove();
            }
        });
    }

    function renderAuthorLikes(like)
    {
        if(SHOW_AUTHOR_LIKE) return;

        like?.remove();
    }

    function renderCommentLikes(rating, ratingUp, ratingDown, raw, id, aid)
    {
        if(!SHOW_HIDDEN_COMMENT_LIKES) return;

        let dataMetaRating = raw.split(`:`);

        let storyId = id;
        let autorId = aid;
        let key = 253537024;
        let storySeed = (storyId % 99) + 1;
        let autorSeed = (autorId % 98);

        function restore(x)
        {
             return -x/storySeed - key - autorSeed / storySeed;
        }

        let plus = restore(dataMetaRating[0]);
        let minus = restore(dataMetaRating[1]);
        let resultRating = Math.round(plus - minus);

        if(resultRating > 100000 || resultRating < -100000) resultRating = 0;

        if(resultRating > 0) resultRating = `+`+resultRating;

        rating?.childNodes[0]?.remove();
        rating.textContent = resultRating;
        rating.setAttribute(`aria-label`, Math.round(plus)+` плюсов, `+Math.round(minus)+` минусов`);
        rating.dataset.originRating = resultRating;

        ratingUp.addEventListener(`click`, onChangeCommentRating);
        ratingDown.addEventListener(`click`, onChangeCommentRating);

    }

    function onChangeCommentRating(e)
    {
        let comment = e.target.parentNode;

        let rating = comment.querySelector(`.comment__rating-count`);
        let ratingUp = comment.querySelector(`.comment__rating-up`);
        let ratingDown = comment.querySelector(`.comment__rating-down`);

        setTimeout(() =>
        {
            let plus = ratingUp.classList.contains(`comment__rating-up_active`);
            let minus = ratingDown.classList.contains(`comment__rating-down_active`);

            let result = parseInt(rating.dataset.originRating) + (plus - minus);

            if(result > 0) result = `+`+result;

            rating.textContent = result;
        }, 50);

    }

    function renderUserpageTools()
    {
        if(!ENABLED_QUICK_NOTES()) return;

        let isUser = document.querySelector(`.page-profile`)?.querySelector(`.background`)?.hasAttribute(`data-editable`);

        if(isUser == true) return;

        let uid = document.querySelector(`.page-profile`).querySelector(`.section-group`).getAttribute(`data-user-id`);

        let root = document.querySelector(`.main__inner`);
        let panel = document.querySelector(`.feed-panel`);

        let tools = document.createElement(`div`);
        tools.classList.add(`comment__tools`);
        tools.classList.add(`userpage__forced-on`);
        tools.classList.add(`userpage-note-tools`);
        root.insertBefore(tools, panel);

        for (let i = 0; i < NOTE_VALUES.length; i++)
        {
            if(!SHOW_QUICK_NOTE_VALUES[i]) continue;

            let tool = document.createElement(`div`);
            tool.classList.add(`comment__tool`);
            tool.classList.add(`hint`);
            tool.classList.add(`userpage-note-tool`);

            tool.setAttribute(`aria-label`, `Отметить как `+NOTE_VALUES[i]);
            tool.setAttribute(`data-note`, i);
            tool.setAttribute(`data-uid`, uid);

            tool.textContent = NOTE_ICO[i];

            tool.addEventListener(`click`, clickUserNote);

            tools.appendChild(tool);
        }
    }

    function renderPost(article)
    {
        let rating = article.querySelector(`.story__rating-count`);
        let emotions = article.querySelector(`.story__emotions`);

        if(rating == null || emotions == null)
        {
            setTimeout(() => { renderPost(article); }, 50);
            return;
        }

        //

        if (SHOW_HIDDEN_LIKES && rating?.firstChild?.tagName == `SPAN`) {

            rating.removeChild(rating?.firstChild);

            let dataMetaRating = article.getAttribute(`data-meta-rating`).split(`:`);

            let storyId = article.getAttribute(`data-story-id`);
            let autorId = article.getAttribute(`data-author-id`);
            let key = 253537024;
            let storySeed = (storyId % 99) + 1;
            let autorSeed = (autorId % 98);

            function restore(x)
            {
                 return -x/storySeed - key - autorSeed / storySeed;
            }

            let resultRating = Math.round(restore(dataMetaRating[0]) - restore(dataMetaRating[1]));

            if(resultRating > 100000 || resultRating < -100000) resultRating = 0;

            rating.textContent = resultRating;
        }


        if(!SHOW_EMOTIONS)
        {
            emotions?.remove();
        }


        if(!SHOW_SHORT_POST_DESIGN)
        {
            article.classList.toggle(`story_short`, false);

            let bg = article.querySelector(`.icon--ui__bg-story-short`);

            bg?.remove();
        }
    }

    function renderHeader(header)
    {
        if(header == undefined) header = document.querySelector(`.header__main`);

        header.classList.toggle(`header-old`, MAKE_OLD_HEADER);

        header.querySelector(`.icon--ui__logo-text`).childNodes[0].classList.toggle(`header-old`, MAKE_OLD_HEADER);

        header.querySelector(`.header-menu__subs-counter`).classList.toggle(`header-black-old`, MAKE_OLD_HEADER);

        header.querySelectorAll(`.header-menu__item`).forEach(item => {

            item.classList.toggle(`header-item-button-old`, MAKE_OLD_HEADER);
        });

        header.querySelectorAll(`.header-right-menu__item`).forEach(item => {

            item.classList.toggle(`header-item-old`, MAKE_OLD_HEADER);
        });

    }

    let commentsRoot;
    let comments;
    let answer;
    let sectionTopTrigger;
    let sectionDownTrigger;
    function renderAnswerTop()
    {
        if(!MAKE_ANSWER_TOP) return;


        setTimeout(() =>
        {
            commentsRoot = document.querySelector(`.story-comments`);
            comments = commentsRoot?.querySelector(`.story-comments__all`);

            if(comments == null || comments.scrollHeight < window.screen.height) return;


            comments.querySelectorAll(`section`).forEach(item => {
                if(item.dataset?.role == `answer`)
                {
                    answer = item;
                }
            });



            sectionTopTrigger = document.createElement(`section`);
            sectionTopTrigger.classList.add(`story-comments__top-trigger`);
            sectionTopTrigger.classList.add(`answer-trigger`);
            comments.prepend(sectionTopTrigger);
            intersector.observe(sectionTopTrigger);

            sectionDownTrigger = document.createElement(`section`);
            sectionDownTrigger.classList.add(`story-comments__down-trigger`);
            sectionDownTrigger.classList.add(`answer-trigger`);
            comments.append(sectionDownTrigger);
            intersector.observe(sectionDownTrigger);

        }, 50);
    }

    // ************************ SETTINGS  *************************

    function renderSettings()
    {

        let enableUISection = false;

        let header = document.querySelector(`.feed-panel`).querySelector(`.menu`);

        let items = [];
        header.querySelectorAll(`.menu__item`).forEach(item => {
            items.push(item);
            item.addEventListener(`click`, (e) => {
                if(enableUISection)
                {
                    settingItem.classList.toggle(`menu__item_current`, false);

                    settings?.remove();

                    if(settingsOld != null)
                    {
                        settingsContainer.appendChild(settingsOld);
                        setTimeout(() => e.target.classList.toggle(`menu__item_current`, true), 100);
                    }

                    enableUISection= false;
                }
            });
        });

        let settingsContainer = document.querySelector(`.settings`);
        let settings;
        let settingsOld;

        let settingItem = document.createElement(`a`);
        settingItem.classList.add(`menu__item`);
        settingItem.classList.add(`menu__item_route`);
        settingItem.textContent = `UI++`;
        header.appendChild(settingItem);

        settingItem.addEventListener(`click`, () => {

            enableUISection = true;

            items.forEach(item => {item.classList.toggle(`menu__item_current`, false);});

            settingItem.classList.toggle(`menu__item_current`, true);

            settingsOld = settingsContainer.querySelector(`.settings-main`)
                        || settingsContainer.querySelector(`.settings-security`)
                        || settingsContainer.querySelector(`.settings-save`)
                        || settingsContainer.querySelector(`.settings-notifications`);

            settingsOld?.remove();

            settings = document.createElement(`div`);
            settings.classList.add(`settings-main`);
            settingsContainer.appendChild(settings);

            let sectionGroup = document.createElement(`div`);
            sectionGroup.classList.add(`section-group`);
            settings.appendChild(sectionGroup);

            let sectionHeader = document.createElement(`section`);
            sectionHeader.classList.add(`section_gray`);
            sectionGroup.appendChild(sectionHeader);

            let h = document.createElement(`h4`);
            h.textContent = `Настройки сторонего скрипта Pikabu UI++`;
            sectionHeader.appendChild(h);

            let sectionBody = document.createElement(`section`);
            //sectionBody.classList.add(`section`);
            sectionBody.classList.add(`section_padding_none`);
            sectionGroup.appendChild(sectionBody);

            let options = document.createElement(`div`);
            options.classList.add(`settings-main__options`);
            sectionBody.appendChild(options);

            const addLine = (tittle, desc) => {
                let line = document.createElement(`h4`);
                line.classList.add(`settings-main__sub-header`);
                line.textContent = tittle;
                options.appendChild(line);

                if(desc != undefined) line.insertAdjacentHTML(`beforeend`,`<i class="fa fa-question-circle hint" aria-label="`+desc+`"></i>`);

                return line;
            };

            let quickNoteElements = [];

            const addCheckbox = (tittle, current, name, desc) => {
                let op = document.createElement(`div`);
                op.classList.add(`settings-main__option`);
                options.appendChild(op);

                let l = document.createElement(`label`);
                op.appendChild(l);

                let checkbox = document.createElement(`span`);
                checkbox.classList.add(`checkbox`);
                checkbox.classList.add(`checkbox_switch`);
                checkbox.classList.toggle(`checkbox_checked`, current);
                checkbox.setAttribute(`tabindex`, `0`);
                checkbox.setAttribute(`unselectable`, `on`);
                l.appendChild(checkbox);

                checkbox.addEventListener(`click`, () => {
                    let value = !GM_getValue(name, 1);
                    GM_setValue(name, value);
                    eval(name+` = value;`);
                    checkbox.classList.toggle(`checkbox_checked`, value);

                    let hideQuickNote = !ENABLED_QUICK_NOTES();
                    for(const el of quickNoteElements)
                    {
                        el.classList.toggle(`settings__forced-off`, hideQuickNote);
                    }

                    if(name == `MAKE_OLD_HEADER`)
                    {
                        MAKE_OLD_HEADER = value;

                        renderHeader();
                    }
                });

                l.insertAdjacentHTML(`beforeend`, tittle);

                if(desc != undefined) l.insertAdjacentHTML(`beforeend`,`<i class="fa fa-question-circle hint" aria-label="`+desc+`"></i>`);

                return op;
            };

            // design

            addLine(`Внешний вид`);
            addCheckbox(`Вернуть старый дизайн шапки `, MAKE_OLD_HEADER, `MAKE_OLD_HEADER`, `Часть цветов может не совпадать, а некоторые анимации не работать. Получилось что получилось.`);
            addCheckbox(`Дублировать блок написания комментария в начало поста `, MAKE_ANSWER_TOP, `MAKE_ANSWER_TOP`);

            addLine(`Отображение стандартной информации`);
            addCheckbox(`Показывать лайки автора в его посте `, SHOW_AUTHOR_LIKE, `SHOW_AUTHOR_LIKE`);
            addCheckbox(`Показывать дизайн коротких постов `, SHOW_SHORT_POST_DESIGN, `SHOW_SHORT_POST_DESIGN`);
            addCheckbox(`Показывать эмоции у постов `, SHOW_EMOTIONS, `SHOW_EMOTIONS`);
            addCheckbox(`Показывать описание сообщества при открытии поста `, SHOW_COMMUNITY_DESCRIPTION, `SHOW_COMMUNITY_DESCRIPTION`);
            addCheckbox(`Уменьшить заголовок постов избранных авторов `, MINIFY_AUTHOR_PANEL, `MINIFY_AUTHOR_PANEL`);

            addLine(`Отображение скрытой информации`);
            addCheckbox(`Показывать оценку свежих постов `, SHOW_HIDDEN_LIKES, `SHOW_HIDDEN_LIKES`);
            addCheckbox(`Показывать оценку свежих комментариев `, SHOW_HIDDEN_COMMENT_LIKES, `SHOW_HIDDEN_COMMENT_LIKES`, `Оценка обновляется с небольшой задержкой.`);

            addLine(`Прочий функционал`);
            addCheckbox(`Убрать вотермарку pikabu.ru при копировании изображений `, MUTE_WATERMARK, `MUTE_WATERMARK`);

            addLine(`Дополнительная информация рядом с никнеймом `, `Данная функция отправляет множество запросов к серверу пикабу (на каждого пользователя по запросу), так что потенциально может замедлить работу сайта. Для отключения необходимо снять все пункты (блок умных заметок исчезнет).`);


            let noteDesc = ``;
            for(let i = 0; i < 4; i++) noteDesc += `<br>  `+NOTE_ICO[i]+` - `+NOTE_VALUES[i];

            addCheckbox(`Показывать дату регистрации `, SHOW_EXP, `SHOW_EXP`, EXP_ICO[0]+` - Менее месяца<br>`+EXP_ICO[1]+` - 1 год<br>`+EXP_ICO[2]+` - Более 1 года<br>`+EXP_ICO[3]+` - Более `+EXP_VALUES[0]+` лет<br>`+EXP_ICO[4]+` - Более `+EXP_VALUES[1]+` лет<br>`+EXP_ICO[5]+` - Более `+EXP_VALUES[2]+` лет`);
            addCheckbox(`Показывать значок умной заметки `, SHOW_NOTE, `SHOW_NOTE`, `Оставьте в заметке пользователя один из хэштегов чтобы показывать специальный маркер, а также изменить цвет заметки: `+noteDesc);
            addCheckbox(`Показывать рейтинг `, SHOW_RATE, `SHOW_RATE`);
            addCheckbox(`Показывать подписчиков `, SHOW_SUBS, `SHOW_SUBS`, SUBS_ICO[1]+` - Более `+SUBS_VALUES[0]+` подписчиков<br>`+SUBS_ICO[2]+` - Более `+SUBS_VALUES[1]+` подписчиков<br>`+SUBS_ICO[3]+` - Более `+SUBS_VALUES[2]+` подписчиков<br>`+SUBS_ICO[4]+` - Более `+SUBS_VALUES[3]+` подписчиков`);
            addCheckbox(`Показывать бан `, SHOW_BAN, `SHOW_BAN`, BAN_ICO[1]+` - Временныый бан<br>`+BAN_ICO[2]+` - Вечный бан` );
            addCheckbox(`Показывать пол `, SHOW_GENDER, `SHOW_GENDER`, GENDER_ICO[1]+` - Пикабушник<br>`+GENDER_ICO[2]+` - Пикабушница` );

            quickNoteElements.push(addLine(`Умные заметки `, `Рядом с комментариями и на странице пользователя будут кнопки для быстрого добавления заметки с хэштегом. Кнопки рядом с комментариями также сохраняют ссылку на комментарий. Нажатие на кнопку срабатывает сразу, однако иконка обновится с небольшой задержкой из-за очереди загрузки. <br>ОСТОРОЖНО: При добавлении хэтега через кнопку, предыдущая заметка полностью удаляется.`));

            quickNoteElements.push(addCheckbox(`Показывать кнопку #добро `, SHOW_QUICK_NOTE_0, `SHOW_QUICK_NOTE_0`));
            quickNoteElements.push(addCheckbox(`Показывать кнопку #интересно `, SHOW_QUICK_NOTE_1, `SHOW_QUICK_NOTE_1`));
            quickNoteElements.push(addCheckbox(`Показывать кнопку #осторожно `, SHOW_QUICK_NOTE_2, `SHOW_QUICK_NOTE_2`));
            quickNoteElements.push(addCheckbox(`Показывать кнопку #неприемлемый `, SHOW_QUICK_NOTE_3, `SHOW_QUICK_NOTE_3`));
            quickNoteElements.push(addCheckbox(`Автоматический бан #неприемлемого `, AUTOBAN, `AUTOBAN`, `При добавлении хэштега #неприемлемый автоматически скрываются комментарии пользователя. Если убрать хэштег, то комментарии снова будут показываться. Работает только при добавлении хэштегов через кнопки быстрого добавления.` ));

            let sectionBottom = document.createElement(`section`);
            sectionBottom.classList.add(`section_gray`);
            sectionBottom.classList.add(`section_center`);
            sectionGroup.appendChild(sectionBottom);

            let buttonClear = document.createElement(`button`);
            buttonClear.classList.add(`button_danger`);
            buttonClear.textContent = `УДАЛИТЬ КЭШИРОВАННЫЕ ДАННЫЕ`;
            sectionBottom.appendChild(buttonClear);


            buttonClear.addEventListener(`click`, () => {
                resetDatabase();
            });

            let hideQuickNote = !ENABLED_QUICK_NOTES();
            for(const el of quickNoteElements)
            {
                el.classList.toggle(`settings__forced-off`, hideQuickNote);
            }

        });

    }

})();