Greasy Fork is available in English.

NoName Club expand replies

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

Skript installieren?
Vom Ersteller vorgeschlagenes Skript

Ihnen könnte auch Youtube subtitles under video frame gefallen.

Skript installieren
/* 
    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);
        }
    }
})();