Greasy Fork is available in English.

NoName Club expand replies

Добавляет в элементы ленты кнопку *развернуть* рядом с колличеством ответов. Кнопка дает развернуть/свернуть ответы к теме. Теперь не нужно переходить на страницу темы, чтобы прочитать комментарии пользователей.

Instalar este script¿?
Script recomendado por el autor

Puede que también te guste Youtube subtitles under video frame.

Instalar este script
/* 
    NoName Club expand replies - Adds a button which opens topic's replies.
    Copyright (C) 2019  T1mL3arn

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <https://www.gnu.org/licenses/>. 
*/

// ==UserScript==
// @name NoName Club expand replies
// @namespace https://github.com/T1mL3arn
// @description Добавляет в элементы ленты кнопку *развернуть* рядом с колличеством ответов. Кнопка дает развернуть/свернуть ответы к теме. Теперь не нужно переходить на страницу темы, чтобы прочитать комментарии пользователей.
// @author T1mL3arn
// @version 1.2
// @icon https://nnm-club.me/favicon.ico
// @match *://nnm-club.me/*
// @match *://nnmclub.to/*
// @match *://ipv6.nnmclub.to/*
// @match *://nnmclub.tv/*
// @match *://ipv6.nnm-club.me/*
// @match *://ipv6.nnm-club.lib/*
// @match *://nnm-club.lib/*
// @match *://nnmclub5toro7u65.onion/*
// @match https://[2a01:d0:e451:0:6e6e:6d2d:636c:7562]/*
// @match http://[2001:470:1f15:f1::1113]/*
// @match nnm-club.i2p
// @run-at document-end
// @noframes
// @grant none
// @license GPLv3 
// @homepageURL https://github.com/t1ml3arn-userscript-js/NoName-Club-expand-replies
// @supportURL https://github.com/t1ml3arn-userscript-js/NoName-Club-expand-replies/issues
// ==/UserScript==

(()=>{
    let log = function(){
        console.log(`[ ${GM_info.script.name} ] : `, ...arguments);
    }
    
    let error = function(){
        console.error(`[ ${GM_info.script.name} ] Error : `, ...arguments);
    }

    /**
        TODO test matching ALL mirrors including ipv6, onion, i2p
       https://nnm-club.me/forum/viewtopic.php?t=1191445
       https://nnm-club.me/forum/viewtopic.php?t=1000470&start=1470#post_9390225
        
        nnm-club.me
        nnmclub.to
        
        ipv6.nnm-club.me
        ipv6.nnmclub.to
        ipv6.nnm-club.lib
        nnmclub.tv
        nnm-club.lib
        nnmclub5toro7u65.onion
        https://[2a01:d0:e451:0:6e6e:6d2d:636c:7562]
        nnm-club.i2p
     */

    ///TODO some guards to test if markup is changed ?

    let css = `
    .nnm-show-replies__a-disabled {
        text-decoration: none !important;
        pointer-events: none !important;
        color: #777 !important;
    }
    `;
    addStyle(css);

    // parse current page to find if there are any cards with ANSWERS icon there 
    let cards = $('.pline').has('a.pcomm[href^="viewtopic.php"]');

    if(cards.length == 0)   return;

    // add new button 
    cards.each((ind, elt)=>{
        if(getRepliesCount(elt) == 0) return;

        let goToForumBtn = $(elt).find('a.pcomm[href^="viewtopic.php"]');
        let href = goToForumBtn[0].href;
        
        let loadAnswersBtn = $('<span>')
            .text(' развернуть')
            .css('white-space', 'pre')
            .attr('data-href', href);
        
        goToForumBtn
        .after(loadAnswersBtn)
        .after($('<span>').text(' | ').addClass('vbot'));
        
        loadAnswersBtn.click(e => loadAnswers(elt, loadAnswersBtn));
    });

    async function loadAnswers(cardElt, btn){
        
        btn.text(' загрузка ');
        btn.unbind('click');

        let href = btn.attr('data-href');

        let text = await fetchPageText(href);
        if(!text){
            btn.text(' развернуть');
            return;
        }
        
        let forumElement = new DOMParser().parseFromString(text, 'text/html');
        let replies = parseRepliesAndGetElement(forumElement);

        let container = $('<div>').append(replies).css({'max-height': '600px', 'overflow-y': 'auto'}); 
        
        $(cardElt).after(container);

        btn.text(' свернуть');
        btn.click(e => hideAnswers(container, btn));
        
        let nav = new Nav(forumElement, href, container);
        container.prepend(nav.getElement());
    }
    
    function blobToText(blob){
        // response.text() returns string in UTF-8
        // but nnm club uses windows-1251 charset
        // so here is a trick to get a string in that charset using a blob and File API
        
        return new Promise((resolve, reject) => {
            let reader = new FileReader();
            reader.onload = event => resolve(event.target.result);
            reader.onabort = event => reject(event);
            reader.onerror = event => reject(event);
            reader.readAsText(blob, 'windows-1251');
        });
    }

    function getRepliesCount(topicElt) {
        let raw = $(topicElt).find('a.pcomm.tit-b.bold').text();
        return parseInt(raw);
    }

    function hideAnswers(answers, btn) {
        btn.unbind('click');
        answers.hide();
        btn.text(' развернуть');
        btn.click(e => showAnswers(answers, btn));
    }

    function showAnswers(answers, btn) {
        btn.unbind('click');
        answers.show();
        btn.text(' свернуть');
        btn.click(e => hideAnswers(answers, btn));
    }

    class Nav {
        constructor(documentElement, href, container){
            this.elt = $('<div>');
            // parse pages
            let pageNav = $(documentElement).find('span.nav:contains(Страницы:)');
            if(!pageNav) return;

            // first element in set should contain page links
            let anchors = $(pageNav[0]).find('a');
            let pages = [href].concat(anchors.map((i, elt) => elt.href).get());
            // remove link to "next" page
            pages.pop();
            if(pages.length < 2) return;

            let html = pages.reduce((acc, curr, ind) => acc + `<a href="${curr}">${ind}</a> `, 'Страницы: ');
            this.elt.append(html).css({"font-weight": "bold", "padding": "10px", "padding-left": "0"});
            
            anchors = this.elt.find('a');
            anchors.first().addClass("nnm-show-replies__a-disabled");
            anchors.click(e => $(e.target).addClass("nnm-show-replies__a-disabled"));
            anchors.click(async e => {
                e.preventDefault();
                ///TODO cache results somehow
                let replies = await getReplies(e.target.href);
                anchors.each((i,elt) => $(elt).removeClass("nnm-show-replies__a-disabled"))
                if(!replies) return;

                $(e.target).addClass("nnm-show-replies__a-disabled");

                container.find('.forumline')
                            .before(replies)
                            .remove();
            });
        }

        /** Returns a jquery object */
        getElement(){
            return this.elt;
        }
    }

    async function getReplies(href) {
        let pageText = await fetchPageText(href);
        return !pageText ? null : parseRepliesAndGetElement(pageText);
    }

    async function fetchPageText(href) {
        let response = await fetch(href);
        if(!response.ok) {
            error(`Cannot fetch page "${href}"`);
            return null;
        }

        let blob = await response.blob();
        let pageText = await blobToText(blob);

        return pageText;
    }

    function parseRepliesAndGetElement(data){
        let documentElement;
        if(typeof data == 'string')
            documentElement = new DOMParser().parseFromString(data, 'text/html');
        else
            documentElement = data;

        const rem = 'td:nth-child(1) span.nav:contains(Вернуться к началу)';

        let replies = $(documentElement).find('.forumline').eq(0);

        let repliesPostElts = replies.find('tr').filter((i,elt) => $(elt).find(rem).length);
        repliesPostElts.next().remove();
        repliesPostElts.remove();

        // remove first post
        replies.find('tbody > tr.row1:nth-child(2)').remove();
        // remove sorting form
        replies.find('form > span.gensmall').remove();    
        replies.find('tr *:contains(Форма быстрого ответа)').remove();
        replies.find('tr form[action^="posting.php"]').remove();
        replies.find('#post_opt').remove();

        return replies;
    }

    function addStyle(css){
        let head = document.getElementsByTagName('head')[0];
        if (head) {
            let style = document.createElement('style');
            style.setAttribute('type', 'text/css');
            style.textContent = css;
            head.appendChild(style);
        }
    }
})();