// ==UserScript== // @name Kbin Subscriptions Panel // @namespace https://perry.dev // @license MIT // @version 0.8 // @description Adds a side panel with all magazine subscriptions. // @author Daniel Pervan // @match https://kbin.social/* // @icon https://www.google.com/s2/favicons?sz=64&domain=kbin.social // @grant GM_xmlhttpRequest // @grant GM_addStyle // @downloadURL none // ==/UserScript== (function () { 'use strict'; function addSubscriptionsSidePanel() { const loginElement = document.querySelector(".login"); if (loginElement.href.endsWith("/login")) { return; } GM_addStyle(` body.extend-width .kbin-container { max-width: 1620px; } #subscription-panel-settings-button { position: absolute; top: 0; right: 0; margin: 0.5rem; padding: 0.5rem; color: var(--kbin-section-text-color); font-size: 0.8em; cursor: pointer; } #subscription-panel-settings-button:hover { color: var(--kbin-sidebar-header-text-color); } .subscription-panel-settings-modal { position: fixed; z-index: 100; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4); backdrop-filter: blur(2px); } .subscription-panel-settings-modal-content { background-color: var(--kbin-section-bg); margin: 15% auto; padding: 20px; width: 80%; max-width: 600px; min-width: 300px; animation: modalopen 0.25s ease-in-out; box-shadow: 0 0 1rem rgba(0,0,0,0.2); } .subscription-panel-settings-modal-content { border: var(--kbin-options-border); border-radius: 0.5rem; } .subscription-panel-settings-modal-content .close { color: #aaa; float: right; font-size: 28px; cursor: pointer; } .subscription-panel-settings-modal-content ul { list-style: none; padding-inline: 0; } .subscription-panel-settings-modal-content ul li { margin-bottom: 1rem; } .subscription-panel-settings-modal-content ul li label { display: block; margin-bottom: 0.5rem; } .subscription-panel-settings-modal-content ul li .description { font-size: 0.8em; font-weight: 100; font-style: italic; opacity: 0.8; } .subscription-panel-settings-modal-content ul li input[type="checkbox"] { margin-right: 0.5rem; } @keyframes modalopen { from { opacity: 0; transform: scale(0.9); } to { opacity: 1 transform: scale(1); } } .subscription-panel-settings-modal-content h2 { margin-top: 0; } #subscription-panel-collapse-button { position: relative; display: inline-block; right: 0; margin: 0.5rem; padding: 0.5rem; color: var(--kbin-section-text-color); font-size: 0.8em; cursor: pointer; } #middle > .kbin-container { grid-template-areas: "subscription-panel main sidebar"; grid-template-columns: minmax(200px, 1fr) 3fr 1fr; } body.subscription-panel-collapsed #middle > .kbin-container { grid-template-columns: minmax(100px, 120px) 3fr 1fr; } body.subscription-panel-collapsed #middle > .kbin-container #subscription-panel li.no-image { padding-left: 1.8; } body.subscription-panel-collapsed #middle > .kbin-container #subscription-panel input { display: none; } body.subscription-panel-collapsed #middle > .kbin-container #subscription-panel li { font-size: 0.8em; } body.subscription-panel-collapsed.subscription-panel-hide-on-collapse #middle > .kbin-container #subscription-panel ul { display: none; } .sidebar-left #middle > .kbin-container { grid-template-areas: "sidebar main subscription-panel"; grid-template-columns: 1fr 3fr minmax(200px, 1fr); } body.subscription-panel-collapsed .sidebar-left #middle > .kbin-container { grid-template-columns: 1fr 3fr minmax(100px, 120px); } #subscription-panel { background-color: var(--kbin-section-bg); border: var(--kbin-options-border); color: var(--kbin-section-text-color); margin-bottom: .5rem; padding: 2rem 1rem; border-radius: 0 0 .5rem .5rem !important; height: fit-content; font-size: 0.8em; margin-right: 0.5rem; position: relative; } #subscription-panel h3 { border-bottom: var(--kbin-sidebar-header-border); color: var(--kbin-sidebar-header-text-color); font-size: .8rem; margin: 0 0 1rem; text-transform: uppercase; width: 100%; } #subscription-panel ul { list-style: none; line-height: 2.5em; padding-inline: 0; } #subscription-panel ul li { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; animation: showItem 0.5s ease-in-out; } #subscription-panel ul li.hideItem { animation: hideItem 0.25s ease-in-out; animation-fill-mode: forwards; } #subscription-panel ul li a img { height: 1.4em; margin-right: .5em; border-radius: 50%; vertical-align: middle; } #subscription-panel ul li.no-image { padding-left: 1.9em; } #subscription-panel .instance-name { opacity: 0.8; font-weight: 100; } #subscription-panel-spinner { text-align: center; font-size: 2em; } @keyframes showItem { 0% { opacity: 0; } 100% { opacity: 1; } } @keyframes hideItem { 0% { opacity: 1; max-height: 2.5em; } 100% { opacity: 0; max-height: 0; } } `); /** Create the subscription panel */ const kbinContainer = document.querySelector("#middle > .kbin-container"); const magazinePanel = document.createElement("aside"); const magazinePanelUl = document.createElement("ul"); const title = document.createElement("h3"); magazinePanel.id = "subscription-panel"; title.innerHTML = 'Subscriptions'; magazinePanel.appendChild(title); kbinContainer.appendChild(magazinePanel); /** Add search box */ const searchBox = document.createElement("input"); searchBox.type = "text"; searchBox.placeholder = "Filter"; searchBox.addEventListener("input", (e) => { let filter = e.target.value.toLowerCase(); magazinePanelUl.querySelectorAll("li").forEach((li) => { let a = li.querySelector("a"); if (a.innerText.toLowerCase().indexOf(filter) > -1) { li.classList.remove("hideItem"); } else { li.classList.add("hideItem"); } }); }); magazinePanel.appendChild(searchBox); /** Add settings button */ const settingsButton = document.createElement("div"); settingsButton.id = "subscription-panel-settings-button"; settingsButton.innerHTML = ''; settingsButton.addEventListener("click", () => { showSettingsModal(); }); magazinePanel.appendChild(settingsButton); /** Add collapse button */ const collapseButton = document.createElement("span"); collapseButton.id = "subscription-panel-collapse-button"; collapseButton.innerHTML = ''; title.addEventListener("click", () => { toggleCollapsePanel(); }); title.appendChild(collapseButton); /** Add subscription list */ magazinePanel.appendChild(magazinePanelUl); /** Add spinner */ const spinner = document.createElement("div"); spinner.id = "subscription-panel-spinner"; spinner.innerHTML = ''; magazinePanel.appendChild(spinner); /** Fetch subscription page */ const xhr = new XMLHttpRequest(); xhr.open("GET", "/settings/subscriptions/magazines", true); xhr.onreadystatechange = function () { if (this.readyState === 4) { spinner.remove(); if (this.status === 200) { /** Parse the page */ let dom = new DOMParser().parseFromString(this.responseText, 'text/html'); let magazinesElements = dom.querySelectorAll(".section.magazines.magazines-columns ul>li"); let magazines = [] /** Find subscriptions */ magazinesElements.forEach((el) => { let magA = el.querySelector("a") let mag = {}; mag.fullName = magA.innerText; const instanceName = mag.fullName.match(/@(.*)/); mag.instanceName = instanceName ? instanceName[1] : undefined; mag.name = mag.fullName.replace(/@(.*)/, ""); mag.url = magA.href; mag.img = el.querySelector("figure img")?.src; magazines.push(mag); }); magazines.sort((a, b) => { return a.name.localeCompare(b.name); }) magazines.forEach(mag => { /** Create the item dom element */ let li = document.createElement("li"); let a = document.createElement("a"); a.href = mag.url; a.title = mag.fullName; if (mag.img) { let img = document.createElement("img"); img.src = mag.img; a.appendChild(img); } else { /** Add some padding when there is no magazine image */ li.classList.add("no-image"); } const span = document.createElement("span"); span.className = "name"; span.appendChild(document.createTextNode(mag.name)); a.appendChild(span); if (mag.instanceName) { const span = document.createElement("span"); span.className = "instance-name"; span.appendChild(document.createTextNode("@" + mag.instanceName)); a.appendChild(span); } li.appendChild(a); magazinePanelUl.appendChild(li); }); } else { magazinePanel.appendChild(document.createTextNode("Failed to load subscriptions")); } } }; xhr.send(); } function showSettingsModal() { const settings = getSettings(); const modal = document.createElement("div"); modal.className = "subscription-panel-settings-modal"; modal.innerHTML = `

Subscription Panel Settings

`; document.body.appendChild(modal); const extendWidthEl = modal.querySelector("#subscription-panel-extend-width"); if (settings?.extendWidth) { extendWidthEl.checked = true; } const hideOnCollapseEl = modal.querySelector("#subscription-panel-hide-on-collapse"); if (settings?.hideOnCollapse) { hideOnCollapseEl.checked = true; } modal.querySelector(".subscription-panel-settings-modal .close").addEventListener("click", () => { modal.remove(); }); modal.addEventListener("click", (e) => { if (e.target === modal) { modal.remove(); } }); modal.querySelector("#subscription-panel-extend-width").addEventListener("change", (e) => { const settings = getSettings(); if (e.target.checked) { settings.extendWidth = true; } else { settings.extendWidth = false; } saveSettings(settings); applySettings(); }); modal.querySelector("#subscription-panel-hide-on-collapse").addEventListener("change", (e) => { const settings = getSettings(); if (e.target.checked) { settings.hideOnCollapse = true; } else { settings.hideOnCollapse = false; } saveSettings(settings); applySettings(); }); } function applySettings() { const settings = getSettings(); if (settings?.extendWidth) { document.body.classList.add("extend-width"); } else { document.body.classList.remove("extend-width"); } if (settings?.collapsed) { collapsePanel(); } if (settings?.hideOnCollapse) { document.body.classList.add("subscription-panel-hide-on-collapse"); } else { document.body.classList.remove("subscription-panel-hide-on-collapse"); } } function getSettings() { let settings = localStorage.getItem("subscription-panel-settings"); if (!settings) { settings = {}; } else { settings = JSON.parse(settings); } return settings; } function saveSettings(settings) { localStorage.setItem("subscription-panel-settings", JSON.stringify(settings)); } function toggleCollapsePanel() { const settings = getSettings(); if (document.body.classList.contains("subscription-panel-collapsed")) { uncollapsePanel(); settings.collapsed = false; } else { collapsePanel(); settings.collapsed = true; } saveSettings(settings); } function collapsePanel() { document.querySelector("#subscription-panel-collapse-button i").className = "fa-solid fa-chevron-right"; document.querySelector("#subscription-panel-collapse-button").title = "Show subscriptions"; document.querySelector(".subscription-panel-header").innerText = "Subs"; document.body.classList.add("subscription-panel-collapsed"); } function uncollapsePanel() { document.querySelector("#subscription-panel-collapse-button i").className = "fa-solid fa-chevron-left"; document.querySelector("#subscription-panel-collapse-button").title = "Hide subscriptions"; document.querySelector(".subscription-panel-header").innerText = "Subscriptions"; document.body.classList.remove("subscription-panel-collapsed"); } /** Do the stuff */ addSubscriptionsSidePanel(); applySettings(); })();