Floating Subs List

Adds a floating list of subscribed magazines to the left of the page

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         Floating Subs List
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  Adds a floating list of subscribed magazines to the left of the page
// @author       raltsm4k
// @match        *://kbin.social/*
// @match        *://fedia.io/*
// @match        *://karab.in/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=kbin.social
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js
// @license      MIT
// ==/UserScript==

let user;
let is_populating = false;
let fetch_tries = 3;

(function() {
    'use strict';

    const a_login = document.querySelector("#header a.login");
    user = a_login.getAttribute("href");

    if (user !== "/login") {
        const div_head = document.querySelector('#header');
        const div_subs = document.createElement('div');
        const div_subs_s = document.createElement('section');
        const div_subs_c = document.createElement('div');
        const button_filter = Object.assign(document.createElement("a"), {
            className: "icon sort-button",
            id: "button-filter",
            title: "Filter by name",
        }).appendChild(Object.assign(document.createElement("i"), {
            className: "fa-solid fa-magnifying-glass"
        })).parentNode;
        const button_refresh = Object.assign(document.createElement("a"), {
            className: "icon sort-button",
            id: "button-refresh",
            title: "Refresh list",
        }).appendChild(Object.assign(document.createElement("i"), {
            className: "fa-solid fa-rotate-right"
        })).parentNode;
        const input_filter = Object.assign(document.createElement("input"), {
            id: "subs-filter",
            name: "subs-filter",
            type: "text",
            title: "Filter by name",
            style: "display:none;"
        });
        const button_collapse = Object.assign(document.createElement("button"), {
            id: "subs-sticky-collapse",
            title: "Collapse/expand",
            textContent: "<"
        });

        const ul_main = Object.assign(document.createElement("ul"), {
            id: "subs-sticky-main"
        });
        const ul_pins = Object.assign(document.createElement("ul"), {
            id: "subs-sticky-pins"
        });

        const preloader = Object.assign(document.createElement("div"), {
            className: "fa-3x",
            id: "subs-sticky-preloader"
        }).appendChild(Object.assign(document.createElement("i"), {
            className: "fa-solid fa-sync fa-spin"
        })).parentNode.appendChild(Object.assign(document.createElement("i"), {
            className: "fa-solid fa-circle-exclamation",
            style: "display:none;"
        })).parentNode;


        button_refresh.addEventListener('click', () => { localStorage.removeItem('cached_subs_' + user); populate(); });
        button_filter.addEventListener('click', () => {
            const search_box = $('#subs-filter');
            search_box.toggle();
            if (search_box.is(':visible')) {
                $('#button-filter').addClass('active');
                search_box.focus();
            } else {
                $('#button-filter').removeClass('active');
                search_box.val('');
                filter();
            }
        });
        input_filter.addEventListener('input', function() { filter(); });
        button_collapse.addEventListener('click', async () => { await localStorage.setItem('subs_collapsed', localStorage.getItem('subs_collapsed') !== 'true'); collapse(); });

        const sub_button = document.querySelector('.magazine__subscribe button[data-action="subs#send"]');
        if (sub_button !== null) sub_button.addEventListener('click', () => { localStorage.removeItem('cached_subs_' + user); });

        $('<style>').text(`#subs-sticky { display: none; }
                 @media only screen and (min-width: 1136px) {
                 #middle > .kbin-container {
                     margin: 0 auto 0 max(calc(200px + 1rem), calc(50vw - 720px + 1rem));
                 }
                 #middle > .kbin-container.collapsed-subs {
                     margin: 0 auto;
                 }
                 #subs-sticky {
                    display: block;
                    position: fixed;
                    top: 3.1625rem;
                    left: max(.5rem, calc(50vw - 680px - 200px - 2rem));
                }
                .fixed-navbar #subs-sticky {
                    position: absolute;
                }
                #subs-sticky.collapsed-subs {
                    left: 0;
                }
                #subs-sticky.collapsed-subs .section {
                    width: 0px;
                    padding: 0;
                    border: none;
                }
                #subs-sticky-collapse {
                    cursor: pointer;
                    height: min(200px, 20%);
                    width: .5rem;
                    position: absolute;
                    top: calc(50% - min(200px, 20%));
                    left: 100%;
                    border: var(--kbin-button-primary-border);
                    border-left: none;
                    background: var(--kbin-button-primary-bg);
                    padding: 0;
                    color: var(--kbin-text-muted-color);
                }
                #subs-sticky-collapse:hover {
                    background: var(--kbin-button-primary-hover-bg);
                }
                a.sort-button {
                    position: relative;
                    float: right;
                    padding-left: .5rem;
                }
                a.sort-button:hover {
                    cursor: pointer;
                }
                a.sort-button:not(.active) {
                    opacity: 50%;
                }
                a#button-filter {
                    margin-right: -.25rem;
                    padding-right: .25rem;
                    border-right: var(--kbin-meta-border);
                }
                #subs-sticky a {
                     white-space: nowrap;
                     text-overflow: ellipsis;
                     max-width: 100%;
                     overflow-x: hidden;
                     overflow-y: hidden;
                     display: inherit;
                }
                #subs-sticky h3 {
                     border-bottom: var(--kbin-sidebar-header-border);
                     color: var(--kbin-sidebar-header-text-color);
                     font-size: .8rem;
                     margin: 0 0 .5rem;
                     text-transform: uppercase;
                }
                #subs-sticky .section {
                     padding: .5rem .5rem 1rem;
                     margin: 0;
                     width: 200px;
                     max-height: calc(100vh - 6.325rem);
                     overflow-y: auto;
                }
                .fixed-navbar #subs-sticky .section {
                     max-height: calc(100vh - 3.5rem);
                }
                #subs-sticky .section a {
                    color: var(--kbin-meta-link-color);
                }
                #subs-sticky .section a:hover {
                    color: var(--kbin-meta-link-hover-color);
                }
                #subs-sticky ul {
                    list-style-type: none;
                    padding: 0;
                    margin: 0;
                }
                #subs-sticky ul li small {
                    display: none;
                }
                #subs-sticky ul div {
                    position: relative;
                }
                #subs-sticky ul div .pin-button {
                    position: absolute;
                    top: -2px;
                    left: -2px;
                    cursor: pointer;
                    opacity: 0%;
                    text-shadow: 2px 2px 5px black;
                    z-index: 20;
                }
                a.icon.pin-button i {
                    font-size: small;
                }
                #subs-sticky ul div:hover .pin-button {
                    opacity: 25%;
                }
                #subs-sticky-pins .pin-button {
                    color: #bf5ded!important;
                    opacity: 100%!important;
                }
                #subs-sticky ul div .pin-button:hover {
                    opacity: 100%;
                }
                #subs-sticky-pins .pin-button:hover {
                    color: #df7dff!important;
                }
                #subs-sticky-pins {
                    display: none;
                    border-bottom: var(--kbin-meta-border);
                    margin-bottom: .25rem!important;
                }
                #subs-sticky figure {
                    display: inline-block;
                    vertical-align: middle;
                    margin: 0 4px 4px 0;
                    height: 24px;
                    width: 24px;
                    float: left;
                }
                #subs-sticky figure, #subs-sticky img {
                    border-radius: 100%;
                }
                #subs-sticky span.subs-comp-name {
                    display: block;
                }
                #subs-sticky span.subs-comp-name-mag {
                    margin-top: -.25rem;
                }
                #subs-sticky span.subs-comp-name-instance {
                    font-size: xx-small;
                    margin-top: -.125rem;
                    opacity: 80%;
                    color: #bf5ded;
                }
                #subs-sticky a:hover span.subs-comp-name-instance {
                    opacity: 100%;
                }
                #subs-filter {
                    display: block;
                    padding: .25rem;
                    margin-bottom: .5rem;
                }
                #subs-sticky .section::-webkit-scrollbar {
                    width: 8px;
                }
                #subs-sticky .section::-webkit-scrollbar-track {
                    background: var(--kbin-bg-nth);
                    border-left: var(--kbin-section-border);
                }
                #subs-sticky .section::-webkit-scrollbar-thumb {
                    background: var(--kbin-section-bg);
                    border-left: var(--kbin-section-border);
                }
                #subs-sticky .section::-webkit-scrollbar-thumb:hover {
                    background: var(--kbin-primary-color);
                }
                #subs-sticky .dummy {
                    display: none;
                }
                #subs-sticky-preloader {
                    height: 100px;
                    display: flex;
                    justify-content: center;
                    align-items: center;
                    opacity: 50%;
                }
                }`).appendTo(document.head);

        div_subs_c.className = 'container';
        div_subs_c.appendChild(ul_pins);
        div_subs_c.appendChild(ul_main);

        div_subs_s.className = 'section';
        div_subs_s.appendChild(button_refresh);
        div_subs_s.appendChild(button_filter);
        div_subs_s.appendChild(Object.assign(document.createElement('h3'), {
            textContent: "Subscribed"
        }));
        div_subs_s.appendChild(input_filter);
        div_subs_s.appendChild(preloader);
        div_subs_s.appendChild(div_subs_c);
        div_subs.appendChild(div_subs_s);
        div_subs.appendChild(button_collapse);

        div_subs.setAttribute('id', 'subs-sticky');
        div_head.appendChild(div_subs);

        // Stop the panel before it collides with the footer when scrolling
        // Not happy with this yet, uncomment if you want to use it
        /*if ($('.fixed-navbar').length == 0) {
            $(window).scroll(function(){
                $('#subs-sticky').css('top', Math.min(rem_to_px(3.1625) + $(window).scrollTop(), $(document).outerHeight() - $('#footer').outerHeight() - $('#subs-sticky').outerHeight() - rem_to_px(3.5)));
            });
        } else {
            $(window).scroll(function(){
                $('#subs-sticky').css('top', Math.min(rem_to_px(3.1625), $(document).outerHeight() - $('#footer').outerHeight() - $('#subs-sticky').outerHeight() - $(window).scrollTop() - rem_to_px(3.5)));
            });
        }*/

        populate();
    }
})();

function populate() {
    if (!is_populating) {
        is_populating = true;
        const cached = JSON.parse(localStorage.getItem('cached_subs_' + user));
        if (cached === null || Date.now() >= cached.expire) { regen(); }
        else { set_preloader(true); $('#subs-sticky-main').html(cached.html); $('#subs-sticky-pins').html(''); console.log("Fetched from cache"); post_populate(); }
    }
}

function post_populate() {
    sort();
    collapse();
    assign_pins();
    set_preloader(false);
    is_populating = false;
}

function regen() {
    set_preloader(true);
    $('#subs-sticky li').remove();
    fetch_next(1);
}

function fetch_next(p) {
    $.ajax({
        url: window.location.origin + user + "/subscriptions?p=" + p,
        success: function(data) {
            const dom = $($.parseHTML(data));
            const ul = dom.find('#content .magazines ul');
            if (!ul.length) {
                if (fetch_tries > 0) {
                    console.log("Failed to get page " + p + " of subs, retrying...");
                    fetch_tries--;
                    fetch_next(p);
                } else {
                    console.log("Failed to get page " + p + " of subs. Out of tries, stopping...");
                }
            } else {
                const els = ul.children('li');
                els.each(function() {
                    const div = $(this).find('div');
                    const a = $(this).find('a');
                    const fig = $(this).find('figure');
                    $(this).find('small').remove();

                    const a_parts = a.text().split('@');
                    if (a_parts.length === 2) {
                        a.html("<span class='subs-comp-name subs-comp-name-mag'>" + a_parts[0] + "</span><span class='subs-comp-name subs-comp-name-instance'>@" + a_parts[1] + "</span>");
                    }

                    if (fig.length > 0) {
                        fig.find('img').css({'height': '24px', 'width': '24px'});
                        a.prepend(fig);
                    } else {
                        a.prepend(`<figure style="background-color: rgba(128, 128, 128, 0.5); text-align: center;">
                              <i class="fa-solid fa-newspaper" style="opacity:50%;"></i></figure>`);
                    }
                    a.removeClass();
                    div.prepend(`<a class="icon pin-button" title="Pin magazine" aria-label="Pin magazine"><i class="fa-solid fa-thumbtack"></i></a>`);
                    $('#subs-sticky-main').append($(this));
                });
                fetch_tries = 3;

                const pg = dom.find('#content .pagination__item--next-page:not(.pagination__item--disabled)');
                if (pg.length) {
                    fetch_next(p+1);
                } else {
                    localStorage.setItem('cached_subs_' + user, JSON.stringify({html: $('#subs-sticky-main').html(), expire: Date.now() + 15 * 60 * 1000}));
                    post_populate();
                }
            }
        },
        error: function() {
            post_populate();
            set_preloader(true, true);
        }
    });
}

function sort() {
    const ul_pins = $('#subs-sticky-pins');
    ul_pins.hide();
    ul_pins.children('li').appendTo($('#subs-sticky-main'));
    ul_pins.children('li').remove();

    const elems = $('#subs-sticky-main').children('li').detach().sort(function (a, b) {
        return ($(a).text().trim().toLowerCase() < $(b).text().trim().toLowerCase() ? -1
                : $(a).text().trim().toLowerCase() > $(b).text().trim().toLowerCase() ? 1 : 0);
    });
    $('#subs-sticky-main').append(elems);

    // Keep pins from versions < 0.8
    const legacy_pins = localStorage.getItem('pinned_subs');
    if (legacy_pins !== null) {
        localStorage.setItem('pinned_subs_' + user, legacy_pins)
        localStorage.removeItem('pinned_subs');
    }

    let pins = localStorage.getItem('pinned_subs_' + user);
    if (pins !== null && (pins = JSON.parse(pins)).length > 0) {
        $('#subs-sticky-main > li').each(function() {
            if (pins.includes($(this).find('a:nth-child(2)').attr('href'))) {
                $(this).appendTo($('#subs-sticky-pins'));
            }
        });
        ul_pins.show();
    }
    filter();
}

function filter() {
    const filter = $('#subs-filter').val();
    const elems = $('#subs-sticky li');
    elems.each(function() {
        $(this).show();
        if ($(this).text().trim().toLowerCase().indexOf(filter.trim().toLowerCase()) == -1) $(this).hide();
    });
}

function assign_pins() {
    $('.pin-button').on('click', function() {
        const href = $(this).parent().children('a').eq(1).attr('href');
        let pins = localStorage.getItem('pinned_subs_' + user);
        if (pins !== null && (pins = JSON.parse(pins)).length > 0) {
            if (pins.includes(href)) {
                pins.splice(pins.indexOf(href), 1);
            } else {
                pins.push(href);
            }
        } else {
            pins = [href];
        }
        localStorage.setItem('pinned_subs_' + user, JSON.stringify(pins));
        sort();
    });
}

function collapse() {
    const is_collapsed = localStorage.getItem('subs_collapsed');
    if (is_collapsed === 'true') {
        $('#middle .kbin-container').addClass('collapsed-subs');
        $('#subs-sticky').addClass('collapsed-subs');
        $('#subs-sticky-collapse').text('>');
    } else {
        $('#middle .kbin-container').removeClass('collapsed-subs');
        $('#subs-sticky').removeClass('collapsed-subs');
        $('#subs-sticky-collapse').text('<');
    }
}

function set_preloader(vis, error = false) {
    const pre = $('#subs-sticky-preloader');
    const cont = $('#subs-sticky .container');
    const i_spin = pre.find(".fa-spin");
    const i_err = pre.find(".fa-circle-exclamation");

    if (vis) {
        pre.show();
        cont.hide();
    } else {
        pre.hide();
        cont.show();
    }

    if (error) {
        i_err.show();
        i_spin.hide();
    } else {
        i_err.hide();
        i_spin.show();
    }
}

function rem_to_px(rem) {
    return parseFloat(rem) * parseFloat(getComputedStyle(document.documentElement).fontSize);
}