您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improved experience for reddit.com
当前为
// ==UserScript== // @name Reddit++ // @name:ru Reddit++ // @namespace RedditPlusPlus // @license CC-BY-SA-4.0 // @version 0.1.8 // @description Improved experience for reddit.com // @description:ru Улучшение интерфейса reddit.com // @author lnm95 // @match *://www.reddit.com/* // @icon data:image/gif;base64,AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEf/NABE//QARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//QBE/+8ARP/MAEP/ngBF/1wAIv8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARf9OAEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//gAR/+gAEj/JQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARf9/AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf/7AFP/kABI/woAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARf9/AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX/1wBi/yQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQv+YAEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX/6wA9/zQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACL/JABF/+sARf//AEX//wBF//8ARf//AEX//wBF//8BRf//E1L9/yVe/P8xZ/v/N2r6/zdq+v8wZvr/Jl77/xNR/f8BRv7/AEX//wBF//8ARf//AEX//wBF//8ARf//AEX/6wAj/yIAAAAAAAAAAAAAAAAAAAAAAAAAAABI/woARf/XAEX//wBF//8ARf//AEX//wBF//8GSf3/K2H3/0l28v9Re+//UXru/1B67f9Qee3/UHnt/1B67f9Qeu7/UXrv/0p28f8pYPf/Bkn9/wBF//8ARf//AEX//wBF//8ARf//AEX/2ABG/wgAAAAAAAAAAAAAAAAAAAAAAFP/jwBF//8ARf//AEX//wBF//8ARf//HFf4/0p17f9Qeez/UHns/1B56/9Qeez/UHrs/1J87/9SfO//UHrt/1B57P9Qeez/UHnr/1B57P9Lde3/HVj4/wBF//8ARf//AEX//wBF//8ARf//AEX/kQAAAAAAAAAAAAAAAABH/yUARf/7AEX//wBF//8ARf//AEX//ydf9f9Qeev/UHrs/1F77v9TfO//U37w/0Zx5f8iTcD/Ejyt/xE7rP8hTL//RXDk/1R+8f9TffD/Unzv/1F77f9Qeez/KF/1/wBF//8ARf//AEX//wBF//8ARf/5AEX/LAAAAAAAAAAAAEP/oABF//8ARf//AEX//wBF//8dWPj/UXvt/1N98P9Uf/P/VoH0/1aB9f9CbuL/CTSo/wgzpv8JNKf/CTSn/wczpv8JNKj/QW3i/1aC9v9WgfT/VYDz/1R/8f9Sfe//H1n4/wBF//8ARf//AEX//wBF//8ARf+gAAAAAAAi/xAARf/4AEX//wBF//8ARf//BEj+/0168v9VgPP/VoL1/1eC9v9Yg/j/WIP5/xA8sv8DLqT/BC+k/wUwpf8FMKX/BC+k/wMupP8PO7D/WIT5/1iD+P9Xg/f/V4L2/1aB9P9Oe/P/BEj+/wBF//8ARf//AEX//wBF//cARf8QAEX/XABF//8ARf//AEX//wBF//8fW/v/VoL1/1eD9/9Yg/j/x83h/+fi2P/Fyt3/VH/0/0x37P9Ic+j/RnHm/0Vx5v9Ic+j/THjt/1R/9P9ahvv/yM7h/+fi2P/Fytz/WIP3/1eC9v8hXPv/AEX//wBF//8ARf//AEX//wBF/10ARf+eAEX//wBF//8ARf//AEX//zRp+v9Yg/j/WYT6/1mF+//x7un/7+vk/+Tf1P9Le/3/W4f+/1uH/v9ch/7/XIf+/1uH/v9bh/7/TX3+/x5e/v/x7un/7+vk/+Tf1P9Zhfr/WIT5/zZr+v8ARf//AEX//wBF//8ARf//AEX/nQBF/8wARf//AEX//wBF//8XVPr/T3z2/1mF+v9ahvv/W4b8//Xy7v/y8Or/5d/U/xZV/v9ciP//XIj//1yI//9ciP//XIj//1yI//8YVv3/AE7///Xy7v/y8Or/5d/U/1qG/P9Zhfv/T3z3/xZU+f8ARf//AEX//wBF//8ARf/QAET97wBF//8ARf//EVD7/1B77/9hifT/nbHp/6G06/+lufL/+Pbz//f18f/r597/eJXn/56w5f+esOX/g6Dw/1yI//9mjvz/nrLr/3yY6v94mvL/+Pbz//f18f/r597/n7Lo/52w5P+breH/eZbk/xBQ+/8ARf//AEX//wBE/u0ARf/9AEX//wBF//84bPj/WIP4/6O49P/y7+n/8/Dr//f28v/8/Pr//Pv5//b07//s6OH/5uHX/+Xf1f/j39f/XIj//6S69v/y7+n/8/Dr//f28v/8/Pr//Pv5//b07//s6OH/5uHX/+Xf1f/j3tf/OW34/wBF//8ARf//AEX//gBF//4ARf//AEX//0d4+/9bh/3/qL/8//z8+v/8/Pr//f38//7+/f/+/vz/+/r3//b07//z8Ov/8/Hr//Hw7/9ciP//qL/9//z8+v/8/Pr//f38//7+/f/+/vz/+/r3//b07//z8Ov/8/Hr//Hw7/9IePv/AEX//wBF//8ARf/9AEX97QBF//8ARf//PXH9/1yI/v9nkP//qcD+/63D/v+tw/7//v79//z7+f/18u7/p7v0/6e89P+qv/n/i6r9/1yI//9nkP//qcD+/63D/v+tw/7//v79//z7+f/18u7/p7v0/6e89P+qv/n/i6r9/z5y/f8ARf//AEX//wBF/u4ARf/QAEX//wBF//8WVf7/W4f//1yI//9ciP//XIj//1yI///+/vz/+Pbz/+rl3P9ciP//XIj//1yI//9ciP//XIj//1yI//9ciP//XIj//1yI///+/vz/+Pbz/+rl3P9ciP//XIj//1yI//9bh///FlX+/wBF//8ARf//AET/zQBG/50ARf//AEX//wBF//8iXv//VoT//1yI//9ciP//U4H///39/P/18+7/5eDV/1yI//9ciP//XIj//1yI//9ciP//XIj//1yI//9ciP//XIj///39/P/18+7/5eDV/1yI//9ciP//V4T//yFd//8ARf//AEX//wBF//8ARf+eAET/XQBF//8ARf//AEX//wBF//8ESP//FlX//xRT//8CR///9/n8//b08P/j39r/UoH//1yI//9ciP//XIj//1yI//9ciP//XIj//1KB//82bP//9/n8//b08P/i39r/FVT//xZV//8ESP//AEX//wBF//8ARf//AEX//wBF/1wARf8QAEX/9wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//9LfP7/fZ76/0d29/8ARf//Ckz//xdW//8UR87/G03T/xdW//8KTP//AEX//wBF//9LfP7/fZ76/0d29/8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf/4ACL/EAAAAAAARv+gAEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wU+2f8OPr3/AEX//wBF//8ARf//D0/7/yxi9v8nXvb/B0n9/wBF//8ARf//AEX//wBF//8ARf//AEX//wBH/58AAAAAAAAAAABG/ywARf/5AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AUT3/wo3rv8MRNv/AEX//xZT+v9RfPD/VX/y/1R/8v9JdvH/B0n9/wBF//8ARf//AEX//wBF//8ARf/7AEf/JQAAAAAAAAAAAAAAAABF/5EARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//A0Hn/wo3rf8OPr//JlTQ/1J+8/9ahvz/Wob8/1mF+f8lX/v/AEX//wBF//8ARf//AEX//wBT/5AAAAAAAAAAAAAAAAAAAAAAAEj/CABF/9gARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AUT4/wU+1/8dS8j/Un70/1yI//9ciP7/XIj+/ypj/f8ARf//AEX//wBF//8ARf/XAEj/CgAAAAAAAAAAAAAAAAAAAAAAAAAAAKL/IgBF/+sARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//yJe/v9ciP//XIj//12J//9fiv//DE7//wBF//8ARf//AEX/6wBi/yQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD3/NABF/+sARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//x9c//8/c///PnL//xFS//8ARf//AEX//wBF/+sAPf80AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGL/JABF/9cARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf/YACP/IgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEP/CgBT/48ARf/7AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf/5AEX/kQBI/wgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABI/yUAR/+gAEX/+ABF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf//AEX//wBF//8ARf/3AEX/oABG/ywAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo/8QAEX/XABF/54ARf/MAET/7gBF//0ARf/+AEX/7QBF/88ARv+dAEX/XQBF/xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAD/4AAAP/AAAA/4AAAH/AAAA/gAAAHwAAAA8AAAAOAAAABgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAGAAAABwAAAA8AAAAPgAAAH8AAAD/gAAB/8AAA//wAA///AA/8= // @run-at document-end // @grant unsafeWindow // @grant GM_getValue // @grant GM_setValue // ==/UserScript== (function() { 'use strict'; const REVISION = 4; const pp_app = document.body.querySelector(`shreddit-app`); if(pp_app == null || pp_app.getAttribute(`devicetype`) != `desktop`) { console.log(`Reddit++ was stopped for a non compatible page`); return; } let pp_meta = document.head.querySelector(`meta[name="reddit-plus-plus"]`); if(pp_meta != null) { console.log(`Reddit++ already runned`); return; } pp_meta = document.createElement(`meta`); pp_meta.setAttribute(`name`, `reddit-plus-plus`); pp_meta.setAttribute(`rev`, REVISION); document.head.append(pp_meta); // *********************************************************************************************************************** // ************************************************** CLASSES ************************************************************ // *********************************************************************************************************************** class Database { constructor(name, isCleanupable) { this.databaseKey = name+`_DATABASE`; this.refreshKey = name+`_REFRESHED`; this.cleanupKey = name+`_CLEANUP`; this.isCleanupable = (isCleanupable != undefined && isCleanupable == true); this.refresh(); // cleanup database if(this.isCleanupable && GM_getValue(this.cleanupKey, 0) < Date.now()) { // limit data storage for 1 day const timestampLimit = Date.now() - (1000 * 60 * 60 * 24); this.data = Object.fromEntries(Object.entries(this.data).filter(([key, value]) => value.timestamp > timestampLimit) ); this.refreshed = Date.now(); GM_setValue(this.databaseKey, this.data); GM_setValue(this.refreshKey, this.refreshed); GM_setValue(this.cleanupKey, Date.now() + (1000 * 60 * 10)); } } refresh() { const lastRefreshed = GM_getValue(this.refreshKey, 0); if(this.data == undefined || this.refreshed < lastRefreshed) { this.refreshed = lastRefreshed; this.data = GM_getValue(this.databaseKey, {}); } } get(id) { this.refresh(); const data = this.data[id]; return (data == undefined) ? {} : data; } isDisabled(id) { return this.get(id) == false; } isEnabled(id) { return !this.isDisabled(id); } set(id, value) { this.refresh(); if(this.isCleanupable) { value.timestamp = Date.now(); } this.data[id] = value; this.refreshed = Date.now(); GM_setValue(this.databaseKey, this.data); GM_setValue(this.refreshKey, this.refreshed); } } class CustomCSS { constructor() { this.stylesheet = new CSSStyleSheet(); this.registry(document); } registry(source) { source.adoptedStyleSheets.push(this.stylesheet); } addRule(rule) { this.stylesheet.insertRule(rule, 0); } addVar(name, lightValue, darkValue) { this.addRule(`:root.theme-light { ${name}: ${lightValue} !important;}`); this.addRule(`:root { ${name}: ${darkValue ?? lightValue};}`); } } class Window { constructor(tittle, render, onClose) { this.tittle = tittle; this.render = render; this.onClose = onClose; this.container = null; this.content = null; this.closeButton = null; } build() { this.container = document.createElement(`div`); this.container.classList.add(`pp_WindowContainer`); this.container.addEventListener('click', e => { if(e.target == this.container) { this.close(); }}); const win = AppendNew(this.container, `div`, `pp_Window`); const tittleContainer = AppendNew(win, `div`, `pp_WindowTittleContainer`); let tittle = AppendNew(tittleContainer, `div`, [`pp_WindowTittle`, `flex`, `flex-row`]); tittle = AppendNew(tittle, `span`, [`text-24`, `font-semibold`]); tittle.textContent = this.tittle; this.closeButton = AppendNew(tittleContainer, `div`, [`pp_WindowCloseButton`, `flex`, `items-center`]); this.closeButton = AppendNew(this.closeButton, `button`, [`button`, `icon`, `inline-flex`, `items-center`, `justify-center`, `button-small`, `button-secondary`, `px-[var(--rem6)]`]); this.closeButton.setAttribute(`tittle`, `Close ${this.tittle}`); this.closeButton.addEventListener('click', e => { this.close(); }); this.closeButton = AppendNew(this.closeButton, `span`, [`flex`, `items-center`, `justify-center`]); this.closeButton = AppendNew(this.closeButton, `span`, [`flex`]); const svg = buildSvg(20, 20, closeWindowGraphic, {c:'none'}); svg.setAttribute(`height`, `16`); svg.setAttribute(`width`, `16`); this.closeButton.append(svg); AppendNew(win, `hr`, `border-b-neutral-border-weak`); //`border-0`, `border-b-sm`, `border-solid`, `border-b-neutral-border-weak` this.content = AppendNew(win, `div`, `pp_WindowContent`); AppendNew(win, `div`, `pp_WindowFooter`).textContent = ` `; } open(context) { if(this.container == null) { this.build(); } while (this.content.firstChild) { this.content.removeChild(this.content.lastChild); } this.render(this, context); document.body.appendChild(this.container); document.body.style.overflow = 'hidden'; } close() { this.container.remove(); document.body.style.overflow = 'visible'; if(this.onClose != undefined) { this.onClose(); } } } class ImageViewer { constructor() { this.openned = false; this.viewer = null; this.container = null; this.image = null; this.mouse = {x:0, y:0}; this.drag = { enabled:false, start: {x:0, y:0}, current: {x:0, y:0, scale:1} }; this.scrollImage = this.scrollImage.bind(this); this.startDrag = this.startDrag.bind(this); this.mouseMove = this.mouseMove.bind(this); this.endDrag = this.endDrag.bind(this); } open(src) { if(this.openned) return; this.openned = true; if(this.viewer == null) { this.build(); } this.image.src = src; window.addEventListener('wheel', this.scrollImage, { passive: false }); this.image.addEventListener('mousedown', this.startDrag); document.addEventListener('mousemove', this.mouseMove ); this.image.addEventListener('mouseup', this.endDrag); this.image.addEventListener('mouseleave', this.endDrag); // reset pos this.drag.current = {x:0, y:0, scale:1}; this.updateTransform(); document.body.appendChild(this.viewer); } close() { this.viewer.remove(); this.drag.enabled = false; this.container.classList.toggle(`pp_imageViewer_drag`, false); window.removeEventListener('wheel', this.scrollImage, { passive: false }); this.image.removeEventListener('mousedown', this.startDrag); document.removeEventListener('mousemove', this.mouseMove); this.image.removeEventListener('mouseup', this.endDrag); this.image.removeEventListener('mouseleave', this.endDrag); this.openned = false; } build() { this.viewer = document.createElement(`div`); this.viewer.classList.add(`pp_imageViewer`); this.viewer.dataset.open = false; const closeButton = AppendNew(this.viewer, `div`, `pp_imageViewer_closeButton`); const closeSvg = buildSvg(40, 40, closeImageGraphic, {w:1}); closeButton.appendChild(closeSvg); this.container = AppendNew(this.viewer, `div`, `pp_imageViewer_imageContainer`); this.image = AppendNew(this.container, `img`, `pp_imageViewer_image`); this.image.alt = `Comment image`; this.image.ondragstart = function() { return false; }; // close this.viewer.addEventListener('click', (e) => { if(e.target != this.image) { this.close(); } }); closeButton.addEventListener('click', () => { this.close(); }); } updateTransform() { this.container.style.transform = `translate(${this.drag.current.x}px, ${this.drag.current.y}px) scale(${this.drag.current.scale}, ${this.drag.current.scale})`; } startDrag(event) { this.drag.start.x = event.screenX - this.drag.current.x; this.drag.start.y = event.screenY - this.drag.current.y; this.drag.enabled = true; this.container.classList.toggle(`pp_imageViewer_drag`, true); } mouseMove(event) { this.mouse.x = event.clientX; this.mouse.y = event.clientY; if(this.drag.enabled) { this.drag.current.x = event.screenX - this.drag.start.x; this.drag.current.y = event.screenY - this.drag.start.y; this.updateTransform(); } } endDrag() { this.fit(1); this.drag.enabled = false; this.container.classList.toggle(`pp_imageViewer_drag`, false); } scrollImage(e) { const m = Math.max(1.0, 1.0 + Math.log2(this.drag.current.scale * this.drag.current.scale)); const prevScale = this.drag.current.scale; this.drag.current.scale = Math.max(0.5, this.drag.current.scale + (-e.deltaY / 1000) * m ); console.log(`scale mouse [${this.mouse.x}, ${this.mouse.y}]`); const rect = this.image.getBoundingClientRect(); const hh = rect.height / 2; const hw = rect.width / 2; const dy = rect.y + hh; const dx = rect.x + hw; const os = ( this.drag.current.scale / prevScale - 1); this.drag.current.y -= Math.min(Math.max(this.mouse.y - dy, -hh), hh) * os; this.drag.current.x -= Math.min(Math.max(this.mouse.x - dx, -hw), hw) * os; if(e.deltaY > 0) { this.drag.current.y /= 1.1; this.drag.current.x /= 1.1; } this.fit(0.33); e.preventDefault(); } fit(force) { const offset = 0; const rect = this.image.getBoundingClientRect(); const left = offset - rect.left; const right = rect.right - window.innerWidth + offset; if(left > 0 && right < 0) { this.drag.current.x += ((rect.width > window.innerWidth) ? -right : left) * force; } else if(left < 0 && right > 0) { this.drag.current.x += ((rect.width > window.innerWidth) ? left : -right) * force; } const top = offset - rect.top; const bottom = rect.bottom - window.innerHeight + offset; if(top > 0 && bottom < 0) { this.drag.current.y += ((rect.height > window.innerHeight) ? -bottom : top) * force; } else if(top < 0 && bottom > 0) { this.drag.current.y += ((rect.height > window.innerHeight) ? top : -bottom) * force; } this.updateTransform(); } } // *********************************************************************************************************************** // **************************************************** VARS ************************************************************* // *********************************************************************************************************************** const HOUR_SECONDS = 60 * 60; const DAY_SECONDS = HOUR_SECONDS * 24; const settingsButtonGraphic = 'M15.07,2.25a.33.33,0,0,1,.33.33V6.72h1.25a2.11,2.11,0,0,1,0,4.21H15.4v4.14a.33.33,0,0,1-.33.33H10.93v1.25a2.11,2.11,0,0,1-4.21,0V15.4H2.58a.33.33,0,0,1-.33-.33v-3A3.51,3.51,0,0,0,4.49,8.82,3.48,3.48,0,0,0,2.25,5.57v-3a.33.33,0,0,1,.33-.33H5.34a3.49,3.49,0,0,0,7,0h2.76m0-1.25H10.75A2.24,2.24,0,1,1,6.9,1H2.58A1.58,1.58,0,0,0,1,2.58v4a2.24,2.24,0,1,1,0,4.47v4a1.58,1.58,0,0,0,1.58,1.58H5.47a3.36,3.36,0,0,0,6.71,0h2.89a1.58,1.58,0,0,0,1.58-1.58V12.18a3.36,3.36,0,0,0,0-6.71V2.58A1.58,1.58,0,0,0,15.07,1Z'; const settingsGraphic = [{d:'M10 20c-.401 0-.802-.027-1.2-.079a1.145 1.145 0 0 1-.992-1.137v-1.073a.97.97 0 0 0-.627-.878A.98.98 0 0 0 6.1 17l-.755.753a1.149 1.149 0 0 1-1.521.1 10.16 10.16 0 0 1-1.671-1.671 1.149 1.149 0 0 1 .1-1.523L3 13.906a.97.97 0 0 0 .176-1.069.98.98 0 0 0-.887-.649H1.216A1.145 1.145 0 0 1 .079 11.2a9.1 9.1 0 0 1 0-2.393 1.145 1.145 0 0 1 1.137-.992h1.073a.97.97 0 0 0 .878-.627A.979.979 0 0 0 3 6.1l-.754-.754a1.15 1.15 0 0 1-.1-1.522 10.16 10.16 0 0 1 1.673-1.676 1.155 1.155 0 0 1 1.522.1L6.1 3a.966.966 0 0 0 1.068.176.98.98 0 0 0 .649-.887V1.216A1.145 1.145 0 0 1 8.8.079a9.129 9.129 0 0 1 2.393 0 1.144 1.144 0 0 1 .991 1.137v1.073a.972.972 0 0 0 .628.878A.977.977 0 0 0 13.905 3l.754-.754a1.152 1.152 0 0 1 1.522-.1c.62.49 1.18 1.05 1.671 1.671a1.15 1.15 0 0 1-.1 1.522L17 6.1a.967.967 0 0 0-.176 1.068.98.98 0 0 0 .887.649h1.073a1.145 1.145 0 0 1 1.137.991 9.096 9.096 0 0 1 0 2.392 1.145 1.145 0 0 1-1.137.992h-1.073A1.041 1.041 0 0 0 17 13.905l.753.755a1.149 1.149 0 0 1 .1 1.521c-.49.62-1.05 1.18-1.671 1.671a1.149 1.149 0 0 1-1.522-.1L13.906 17a.97.97 0 0 0-1.069-.176.981.981 0 0 0-.65.887v1.073a1.144 1.144 0 0 1-.99 1.137A9.431 9.431 0 0 1 10 20Zm-.938-1.307a7.638 7.638 0 0 0 1.875 0v-.982a2.292 2.292 0 0 1 3.853-1.6l.693.694a8.796 8.796 0 0 0 1.326-1.326l-.694-.694a2.29 2.29 0 0 1 1.6-3.851h.982a7.746 7.746 0 0 0 0-1.876h-.982a2.213 2.213 0 0 1-2.034-1.4 2.223 2.223 0 0 1 .438-2.451l.694-.693a8.76 8.76 0 0 0-1.327-1.326l-.692.694a2.22 2.22 0 0 1-2.434.445 2.221 2.221 0 0 1-1.419-2.041v-.979a7.638 7.638 0 0 0-1.875 0v.982a2.213 2.213 0 0 1-1.4 2.034 2.23 2.23 0 0 1-2.456-.438l-.693-.694a8.757 8.757 0 0 0-1.326 1.327l.694.692a2.216 2.216 0 0 1 .445 2.434 2.22 2.22 0 0 1-2.041 1.418h-.982a7.746 7.746 0 0 0 0 1.876h.982a2.213 2.213 0 0 1 2.034 1.4 2.223 2.223 0 0 1-.438 2.451l-.694.693c.394.488.838.933 1.326 1.326l.694-.694a2.218 2.218 0 0 1 2.433-.445 2.22 2.22 0 0 1 1.418 2.041v.983ZM10 13.229a3.23 3.23 0 1 1 0-6.458 3.23 3.23 0 0 1 0 6.458Zm0-5.208a1.979 1.979 0 1 0 0 3.958 1.979 1.979 0 0 0 0-3.958Z'}]; const closeWindowGraphic = [{d:'m18.442 2.442-.884-.884L10 9.116 2.442 1.558l-.884.884L9.116 10l-7.558 7.558.884.884L10 10.884l7.558 7.558.884-.884L10.884 10l7.558-7.558Z'}]; const linkGraphics = [{d:'M14.111 12.5a3.701 3.701 0 0 1-1.09 2.41c-.479.47-.928.922-1.378 1.373-.45.45-.894.9-1.368 1.366a3.852 3.852 0 0 1-2.698 1.099 3.852 3.852 0 0 1-2.698-1.099 3.738 3.738 0 0 1-1.116-2.659c0-.997.402-1.953 1.116-2.658.479-.472.928-.923 1.378-1.375.45-.45.893-.9 1.368-1.365A3.936 3.936 0 0 1 9.638 8.59a3.968 3.968 0 0 1 2.24.258c.27-.269.546-.54.812-.806l.131-.13a5.086 5.086 0 0 0-3.182-.624A5.052 5.052 0 0 0 6.732 8.71c-.48.471-.929.922-1.377 1.373-.449.451-.894.9-1.37 1.366A4.982 4.982 0 0 0 2.5 14.992c0 1.328.534 2.602 1.486 3.543A5.13 5.13 0 0 0 7.58 20a5.13 5.13 0 0 0 3.595-1.465c.478-.471.927-.923 1.377-1.374.451-.451.894-.9 1.368-1.366a4.993 4.993 0 0 0 1.263-2.071c.243-.781.288-1.61.132-2.412L14.11 12.5Z'}, {d:'M16.017 1.467A5.123 5.123 0 0 0 12.422 0a5.123 5.123 0 0 0-3.595 1.467c-.478.471-.926.923-1.377 1.374-.45.451-.894.9-1.367 1.366a4.966 4.966 0 0 0-1.106 1.624 4.907 4.907 0 0 0-.291 2.86l1.2-1.19a3.699 3.699 0 0 1 1.092-2.41c.478-.472.928-.923 1.377-1.374.45-.45.894-.9 1.368-1.366a3.844 3.844 0 0 1 2.698-1.101c1.012 0 1.982.396 2.698 1.101a3.736 3.736 0 0 1 1.116 2.66c0 .996-.401 1.953-1.116 2.658-.478.471-.927.922-1.377 1.373-.45.451-.893.9-1.368 1.367a3.933 3.933 0 0 1-2.014 1.003 3.966 3.966 0 0 1-2.24-.26c-.273.274-.551.549-.818.818l-.123.12a5.087 5.087 0 0 0 3.183.624 5.053 5.053 0 0 0 2.906-1.423c.477-.472.926-.923 1.376-1.375.45-.452.894-.9 1.368-1.365A4.977 4.977 0 0 0 17.5 5.008a4.977 4.977 0 0 0-1.488-3.543l.005.002Z'}]; const newUserGraphic = [{w:'2', f:'none', d:'M12.5,11.5a3.39,3.39,0,0,1,2,2,3.16,3.16,0,0,1,0,2'}, {w:'0', d:'M1.46,1.5S3.49,4.89,7,5.07c1.49.07,3.35.25,4.06.79,1.41,1.09,2.3,2.08,1.74,4.37a4.91,4.91,0,0,1-4.36,3.49C5.08,14,2.89,10.29,2.33,9.35.41,6.12,1.46,1.5,1.46,1.5Z'}]; const closeImageGraphic = [{d:'m33.16001,9.52439l-2.37671,-2.37671l-10.691,10.691l-10.691,-10.691l-2.37671,2.37671l10.691,10.691l-10.691,10.691l2.37671,2.37671l10.691,-10.691l10.691,10.691l2.37671,-2.37671l-10.691,-10.691l10.691,-10.691z'}]; const feedGraphics = { Best:[{c:'none', d:'M14.51,6.56,9.13,1.17a1.61,1.61,0,0,0-2.26,0L1.49,6.56a1,1,0,0,0,.72,1.73H5.52V14A1.28,1.28,0,0,0,6.8,15.3H9.23A1.29,1.29,0,0,0,10.52,14V8.29h3.27A1,1,0,0,0,14.51,6.56Z'}], Hot:[{w:'0.5', d:'M8.49,2.93c.7,1.56,3,2.81,3.69,3.52a5.14,5.14,0,0,1,1.36,5.45c-1.09,3.37-4.49,3.38-6.21,3.38s-4.18-.28-5-3,.8-4.41,1-5,1.06,2.52,2,3.12c1.19.79,2.85,0,2.85-1.18S6.72,7.65,6.44,5.37a10.59,10.59,0,0,1,1-4.9S7.83,1.46,8.49,2.93Z'}], New:[{f:'none', w:'1.5', d:'M7.5,3 L7.5,9 L12,6'}, {w:'0.5', d:'M8,15.5A7.5,7.5,0,1,1,15.5,8,7.5,7.5,0,0,1,8,15.5Zm0-14A6.5,6.5,0,1,0,14.5,8,6.51,6.51,0,0,0,8,1.5Z'}], Top:[{f:'none', w:'1.5', d:'M14.51,6.56,9.13,1.17a1.61,1.61,0,0,0-2.26,0L1.49,6.56a1,1,0,0,0,.72,1.73H5.52V14A1.28,1.28,0,0,0,6.8,15.3H9.23A1.29,1.29,0,0,0,10.52,14V8.29h3.27A1,1,0,0,0,14.51,6.56Z'}], Rising:[{f:'none', w:'2', d:'M8.5,4.5l4.68-2.45S14,5.31,14.5,7.5'}, {f:'none', w:'2',d:'M1.1,14.67c1.4-2.8,3.62-6.84,3.62-6.84L9,12.18l4.13-9.94'}] }; const tagGraphics = { Followed : [{d:'m0.43678,5.49532l4.46205,-0.8817l2.13151,-3.90349l2.39985,3.78849l4.1937,0.9967l-3.65339,3.14905l1.13489,4.59352l-4.19007,-2.03743l-3.96007,2.03743l0.86655,-4.78519l-3.38505,-2.95738l0,-0.00001l0.00001,0z'}], Liked : [{d:'m5.10986,11.44595c-3.39081,-2.55084 -4.60316,-4.16445 -4.61299,-6.1398c-0.00896,-1.80077 1.48763,-3.53879 3.03857,-3.52877c0.77447,0.00501 2.43577,0.6678 3.02404,1.20648c0.29641,0.27142 0.4368,0.24465 1.09671,-0.20908c1.79601,-1.23486 3.54983,-1.26078 4.68568,-0.06924c1.81534,1.90433 1.48483,4.17844 -0.95008,6.53732c-1.29454,1.25411 -4.12153,3.47898 -4.4205,3.47898c-0.09098,0 -0.92862,-0.57415 -1.86143,-1.27588l0,0l0,-0.00001z'}], Warning : [{d:'m0.74313,12.0159l6.28126,-9.83543l6.28126,9.83543l-12.56252,0z'}, {d:'m6.91754,5.38201l0,4.06927'}, {d:'m6.91754,10.29973l0,0.7153'}], Blocked : [{d:'M3.11,3l7.94,8'}, {d:'M7,12.88A5.88,5.88,0,1,1,12.91,7,5.88,5.88,0,0,1,7,12.88Z'}] }; const tagButtonGraphics = { Followed: [{d:'m1.56177,7.99265l5.62517,-1.11153l2.68713,-4.92101l3.02542,4.77603l5.28687,1.25651l-4.60571,3.96991l1.43072,5.79091l-5.28229,-2.56852l-4.99233,2.56852l1.09243,-6.03254l-4.26742,-3.72827l0,-0.00001z'}], Liked: [{d:'m7.28935,15.93702c-4.80908,-3.55432 -6.52852,-5.80272 -6.54246,-8.55516c-0.0127,-2.50919 2.10985,-4.93093 4.30951,-4.91697c1.09841,0.00698 3.45457,0.93051 4.2889,1.6811c0.42039,0.37819 0.6195,0.3409 1.55543,-0.29133c2.54723,-1.72064 5.03461,-1.75676 6.64556,-0.09648c2.57463,2.65349 2.10589,5.82222 -1.34747,9.10906c-1.836,1.74747 -5.84543,4.84759 -6.26946,4.84759c-0.12903,0 -1.31703,-0.80001 -2.64001,-1.7778l0,0l0,-0.00001z'}], Warning: [{d:'m1.24313,16.49297l8.78125,-13.75l8.78125,13.75l-17.5625,0z'}, {d:'m9.875,7.5l0,4.5'}, {d:'m9.875,14.09375l0,1'}], Blocked: [{d:'m9.78126,18.09375c-4.41989,0 -8,-3.58011 -8,-8c0,-4.41989 3.58011,-8 8,-8c4.41989,0 8,3.58011 8,8c0,4.41989 -3.58011,8 -8,8z'}, {d:'m4.43767,4.59392l10.81217,10.93716'}] }; const USERTAG_FOLLOWED = `Followed`; const USERTAG_LIKED = `Liked`; const USERTAG_WARNING = `Warning`; const USERTAG_BLOCKED = `Blocked`; const TAG_CONFIG_PRIORITY = 0; const TAG_CONFIG_HINT = 1; const TAG_CONFIG_HINT_ACTIVATED = 2; const TAG_CONFIG_COLOR = 3; const tagConfigs = { Followed:[100, `Follow`, `Unfollow`, `#0b7ed3`], Liked:[2, `Tag as liked`, `Remove liked tag`, `#C95A54`], Warning:[1, `Tag as warned`, `Remove warned tag`, `#D4A343`], Blocked:[0, `Block`, `Unblock`, `#663988`] }; const FEED_BUTTONS_EXTENDED = [`Best`, `Hot`, `New`, `Top`, `Rising`]; const FEED_BUTTONS = [`Hot`, `New`, `Top`, `Rising`]; const SETTING_WIDE_MODE = `wideMode`; const SETTING_FEED_BUTTONS = `feedButtons`; const SETTING_FLAIR_BAR = `flairbar`; const SETTING_BACKPLATES = `backplates`; const SETTING_USER_INFO = `userInfo`; const SETTING_USER_TAGS = `userTags`; const SETTING_SHOW_NAMES = `showNames`; const SETTING_BIGGER_FONTS = `biggerFonts`; const SETTING_HIDE_SHARE = `hideShare`; const SETTING_GHOSTED_COMMENTS = `ghostedComments`; const SETTING_COLLAPSE_AUTOMODERATOR = `collapseAutomoderator`; const css = new CustomCSS(); let settingsRevision = getSettingsRevision(); let settings = new Database(`SETTINGS`); let tags = new Database(`TAGS`); let users = new Database(`USERS`, true); let flairs = new Database(`FLAIRS`); let subs = new Database(`SUBS`, true); let flairsWindow = new Window(`Subreddit flairs settings`, renderFlairsWindow); let settingsWindow = new Window(`Reddit++ settings`, renderSettingsWindow); let imageViewer = new ImageViewer(); // *********************************************************************************************************************** // ***************************************** backward compatibility ****************************************************** // *********************************************************************************************************************** let legacy_subs = GM_getValue(`SUB_DATABASE`, null); if(legacy_subs != null) { for (const [key, value] of Object.entries(legacy_subs)) { if(value.flairs == undefined) continue; for(const flair of value.flairs) { if(flair.banned != undefined && flair.banned == true) { const flairData = flairs.get(key); if(flairData.banned == undefined) { flairData.banned = []; } flairData.banned.push(flair.text); flairs.set(key, flairData); } if(flair.hidden != undefined && flair.hidden == true) { const flairData = flairs.get(key); if(flairData.hidden == undefined) { flairData.hidden = []; } flairData.hidden.push(flair.text); flairs.set(key, flairData); } } } GM_setValue(`SUB_DATABASE`, null); } // *********************************************************************************************************************** // ************************************************ COMMON CSS *********************************************************** // *********************************************************************************************************************** css.addVar('--color-neutral-background-transparent', '#fff0', '#0b141600'); css.addVar('--stickiedColor', '#0e8a001c', '#0e8a001c'); css.addVar('--stickiedHoverColor', '#18900b3d', '#18900b3d'); // windows css.addRule('.pp_WindowContainer { cursor:pointer; position: fixed; top:0px; z-index: 10; width:100%; height:100%; display: flex; justify-content: center; align-items: center; background-color: #000000b3; }'); css.addRule('.pp_Window { cursor: auto; display: flex; flex-direction: column; width:900px; height: fit-content; min-height: 200px; max-height: 75%; border-radius: 15px; background-color: var(--color-neutral-background); box-shadow: 0px 0px 50px 0px #00000070; }'); css.addRule('.pp_WindowTittleContainer { height: 48px; margin: 1rem; display: flex; flex-direction: row; justify-content: space-between; align-items: center; }'); css.addRule('.pp_WindowTittle { margin-left: 1rem; }'); css.addRule('.pp_WindowCloseButton { margin: 1rem; }'); css.addRule('.pp_WindowContent { display: flex; flex-direction: column; overflow-y: overlay; }'); css.addRule('.pp_WindowFooter { height: 2rem; min-height: 2rem; }'); css.addRule('.pp_WindowElementsContainer { display: flex; flex-direction: column; padding: 0px; margin: 20px 40px; gap: 0.5rem;}'); css.addRule('.pp_WindowElement { display: flex; flex-direction: row; justify-content: flex-end; height: 3rem; }'); css.addRule('.pp_WindowElementsContainer > .pp_WindowElement:hover { background-color: var(--color-neutral-background-hover); border-radius: 15px;}'); // image viewer css.addRule('.pp_imageViewer { display: flex; justify-content: center; align-items: center; position: fixed; top:0px; z-index: 10; cursor:pointer; width:100%; height:100%; background-color: #000000b3; }'); css.addRule('.pp_imageViewer_closeButton { display: flex; align-items: center; justify-content: center; position: fixed; top:50px; z-index: 11; right:50px; cursor:pointer; width:50px; height:50px; color: #ffffff9c; background-color: #00000069; border-radius:30px; }'); css.addRule('.pp_imageViewer_closeButton:hover { color: #ffffffc7; }'); css.addRule('.pp_imageViewer_imageContainer {display: flex; justify-content: center; align-items: center; width:80%; height:90%;}'); css.addRule('.pp_imageViewer_imageContainer:not(.pp_imageViewer_drag) { transition: transform 0.5s;}'); css.addRule('.pp_imageViewer_image {cursor: grab; object-fit: scale-down; max-width:100%; max-height:100%; box-shadow: 0px 0px 20px 3px #14141485; }'); css.addRule('.pp_imageViewer_image:active {cursor: grabbing; }'); // ban hint css.addRule('faceplate-banner { max-width: 1000px !important;}'); if(settings.isEnabled(SETTING_BIGGER_FONTS)) { // resize fonts css.addRule('.text-14 { font-size: 1.0rem !important; line-height: 1.4rem !important;}'); css.addRule('faceplate-hovercard .text-12 { font-size: 0.9rem !important; font-color: #595959 !important;}'); // comments more space css.addRule('shreddit-comment-action-row { margin-bottom: 15px !important;}'); } // hide native share if(settings.isEnabled(SETTING_HIDE_SHARE)) { css.addRule('shreddit-comment-share-button { display:none !important;}'); } // posts gradient selection if(settings.isEnabled(SETTING_BACKPLATES)) { //[view-context="SubredditFeed"] css.addRule('article > shreddit-post { background-color: #00000000 !important; padding-top:10px !important; margin-top: 10px !important; margin-bottom: 10px !important;}'); //border-radius: 0px !important; css.addRule('article > shreddit-post::before { border-radius: 15px !important; position: absolute; content: ""; top: 0; right: 0; bottom: 0; left: 0; opacity: 0; z-index: -1; background: linear-gradient(var(--color-neutral-background-hover), var(--color-neutral-background)); transition: opacity 0.2s;}'); css.addRule('article > shreddit-post:hover::before { opacity: 1;}'); // override gold css.addRule('shreddit-post[gold-count]:not(shreddit-post[gold-count=""]) { background-image: linear-gradient(rgba(255, 214, 53, 0.2), rgba(255, 214, 53, 0)) !important;}'); css.addRule('shreddit-post[gold-count]:not(shreddit-post[gold-count=""])::before { background: linear-gradient(#fbed2966, var(--color-neutral-background)) !important;}'); // stickied css.addRule('.stickied::after { border-radius: 15px !important; position: absolute; content: ""; top: 0; right: 0; bottom: 0; left: 0; opacity: 1; z-index: -2; background: linear-gradient(var(--stickiedColor), var(--color-neutral-background)) !important;}'); css.addRule('.stickied::before { background: linear-gradient(var(--stickiedHoverColor), var(--color-neutral-background)) !important;}'); } // *********************************************************************************************************************** // ************************************************ Events *************************************************************** // *********************************************************************************************************************** // Settings changes window.addEventListener("storage", (event) => { const currentRevision = getSettingsRevision(); if(currentRevision != settingsRevision) { settingsRevision = currentRevision; document.addEventListener("visibilitychange", () => { window.location.reload(); }, {once:true}); } }); // Intersections const commentRenderIntersector = new IntersectionObserver(entries => { for (const entry of entries) { if (entry.isIntersecting) { renderComment(entry.target.parentNode); commentRenderIntersector.unobserve(entry.target); } } }, { threshold: 0.05}); // Mutations new MutationObserver(mutations => { const hasComments = window.location.href.includes(`/comments/`); for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node instanceof HTMLElement) { // check new posts if(node.matches(`faceplate-batch`)) { node.querySelectorAll(`shreddit-post`).forEach((post) => { renderPost(post); }); } // check new comments if(hasComments) { // post tree if(node.id != null && node.id.includes(`comment-tree`) == true) { registryAllComments(node); } // single thread if(node.matches(`shreddit-comment-tree`) == true) { registryAllComments(node); } // more comments if(node.matches(`shreddit-comment`) == true) { registryComment(node); registryAllComments(node); } } // check refresh header if(node.matches(`reddit-header-large`) == true) { renderHeader(node.parentNode); } // check refresh sub pages if(node.matches(`dsa-transparency-modal-provider`) == true) { renderApp(node); renderFeedButtons(node); renderSub(node); checkNativePosts(node); } // check refresh main pages if(node.classList.contains(`grid-container`) && node.querySelector(`.main-container`) != null) { renderApp(node); renderFeedButtons(node); checkNativePosts(node); } } } } }).observe(document.body, {childList: true, subtree: true}); // *********************************************************************************************************************** // ************************************************ CORE APP ************************************************************* // *********************************************************************************************************************** if(settings.isEnabled(SETTING_WIDE_MODE)) { css.addRule('@media (min-width: 1392px) { .pp_pageContainer { margin-right: 300px;}}'); css.addRule('@media (min-width: 1392px) { .pp_mainFeed { width: 700px !important; }}'); css.addRule('@media (min-width: 1392px) { .pp_rightSidebar { grid-column-start: 3; order: 10;}}'); css.addRule('@media (min-width: 1392px) { #right-sidebar-container { position: fixed; right: 0px; margin: 15px 10px 0px 0px; } }'); css.addRule('@media (min-width: 1392px) { #left-sidebar-container { position: fixed; left: 0px; max-width: 250px; transition: 0.2s}}'); css.addRule('@media (min-width: 1392px) { #left-sidebar-container:hover { max-width: 300px;}}'); css.addRule('@media (min-width: 1392px) { #left-sidebar > nav { flex-wrap: wrap;}}'); } renderApp(document.body); function renderApp(container) { if(settings.isDisabled(SETTING_WIDE_MODE)) return; const leftSidebar = container.querySelector(`#left-sidebar-container`); // Firefox first-load fix if(awaitElement(leftSidebar, renderApp)) return; const pageContainer = leftSidebar.parentNode; pageContainer.classList.add(`pp_pageContainer`); pageContainer.querySelector(`.subgrid-container`).classList.add(`pp_mainFeed`); const rightSidebar = container.querySelector(`#right-sidebar-container`); rightSidebar.classList.add(`pp_rightSidebar`); rightSidebar.classList.toggle(`styled-scrollbars`, true); const originContainer = rightSidebar.parentNode; let isWideMode = !(window.innerWidth >= 1392); applyRightBar(); window.addEventListener("resize", (event) => { applyRightBar(); }); function applyRightBar() { if(window.innerWidth >= 1392 && !isWideMode) { pageContainer.prepend(rightSidebar); isWideMode = true; } if(window.innerWidth < 1392 && isWideMode) { originContainer.append(rightSidebar); isWideMode = false; } } } // *********************************************************************************************************************** // *********************************************** HEADER ************************************************************** // *********************************************************************************************************************** css.addRule('#reddit-logo { text-decoration: none; }'); css.addRule('.pp_logo { width: max-content; color: var(--shreddit-color-wordmark); font-size: 22px; font-weight: 1000; letter-spacing: -2px; }'); css.addRule('.pp_userPanel { grid-column: span3 / span3 !important; }'); renderHeader(document.body); function renderHeader(container) { const nav = container.querySelector(`reddit-header-large`).querySelector(`nav`); const userPanel = nav.childNodes.item(4); // Firefox first-load fix if(awaitElement(userPanel, renderHeader)) return; if(checkIsRendered(nav)) return; userPanel.classList.add(`pp_userPanel`); userPanel.addEventListener(`click`, () => { renderSettingsButton(); }, {once:true}); const logo = container.querySelector(`#reddit-logo`); const logoPP = AppendNew(logo, `div`, `pp_logo`); logoPP.textContent = `++`; } function renderSettingsButton() { let userMenu = document.getElementById(`user-drawer-content`); if(userMenu.querySelector(`faceplate-tracker[noun="pp-settings"]`) != null) { return; } let originSettingsButton = userMenu.querySelector(`faceplate-tracker[noun="settings"]`); let ppSettingsButton = originSettingsButton.cloneNode(true); ppSettingsButton.setAttribute(`noun`, `pp-settings`); originSettingsButton.parentNode.appendChild(ppSettingsButton); ppSettingsButton.querySelector(`a`).removeAttribute(`href`); let path = ppSettingsButton.querySelector(`path`); path.setAttribute(`d`, settingsButtonGraphic); let text = ppSettingsButton.querySelector(`.text-14`); text.textContent = `Reddit++`; settingsWindow.onClose = () => { if(settingsWindow.changes > 0) { nextSettingsRevision(); window.location.reload(); } }; ppSettingsButton.addEventListener(`click`, () => { settingsWindow.open(); }); } // *********************************************************************************************************************** // ********************************************** FEED BUTTONS *********************************************************** // *********************************************************************************************************************** css.addRule('.pp_feedButtonsContainer { justify-content: space-between; height: 40px !important; }'); css.addRule('.pp_feedButton { gap: 0.25rem !important;}'); css.addRule('.pp_feedContainer { gap: 0.25rem; height: 40px; }'); renderFeedButtons(document.body); function renderFeedButtons(container) { if(settings.isDisabled(SETTING_FEED_BUTTONS)) return; const main = container.querySelector(`.main-container`)?.querySelector(`main`); // Firefox first-load fix if(awaitElement(main, renderFeedButtons)) return; const feedDropdown = main.querySelector(`shreddit-sort-dropdown`); // skip non feed page if(feedDropdown == null && !window.location.href.includes(`/about/`)) return; if(window.location.href.includes(`/user/`)) return; // get current feed const currentFeed = feedDropdown?.querySelector(`div[slot="selected-item"]`)?.textContent; feedDropdown?.remove(); const isHome = window.location.href.includes(`?feed=home`) || window.location.href == `https://www.reddit.com/`; const isPopular = window.location.href.includes(`reddit.com/r/popular/`); const isAll = window.location.href.includes(`reddit.com/r/all/`); // get container let buttonsContainer = null; let hrefGenerator = null; if(isHome || isPopular || isAll) { const originContainer = main.querySelector(`shreddit-layout-event-setter`).parentNode; originContainer.classList.add(`pp_feedButtonsContainer`); buttonsContainer = document.createElement(`div`); buttonsContainer.classList.add(`flex`, `mx-md`, `shrink-0`, `pp_feedContainer`); originContainer.prepend(buttonsContainer); if(isHome) { hrefGenerator = (feed) => { return `/${feed.toLowerCase()}/?feed=home`; }; } else { hrefGenerator = (feed) => { return `/r/${isPopular ? `popular` : `all`}/${feed.toLowerCase()}/`; }; } } else { const aboutContainer = main.querySelector(`a[slot="page-3"]`)?.parentNode?.parentNode; if(aboutContainer == null) { console.log(`FLAIR BAR WAS CORRUPTED`); setTimeout(() => {renderFeedButtons(container); }, 100 ); return; } buttonsContainer = document.createElement(`div`); buttonsContainer.classList.add(`flex`, `mx-md`, `shrink-0`, `pp_feedContainer`); aboutContainer.before(buttonsContainer); aboutContainer.remove(); const subName = getCurrentSub(); hrefGenerator = (feed) => { return `/r/${subName}/${feed.toLowerCase()}/`; }; } const feeds = (isHome || isPopular) ? FEED_BUTTONS_EXTENDED : FEED_BUTTONS; for(const feed of feeds) { const button = AppendNew(buttonsContainer, `a`, [`inline-flex`, `flex-col`, `text-neutral-content-weak`, `font-semibold`, `rounded-full`, `hover:no-underline`, `hover:bg-secondary-background-hover`, `hover:text-secondary-content`, `active:bg-secondary-background`, `pl-[var(--rem16)]`, `pr-[var(--rem16)]`]); button.href = hrefGenerator(feed); const isCurrent = feed == currentFeed; button.classList.toggle(`bg-secondary-background-selected`, isCurrent); button.classList.toggle(`!text-neutral-content-strong`, isCurrent); const spanContainer = AppendNew(button, `span`, [`inline-flex`, `flex-row`, `items-center`, `gap-xs`, `py-[var(--rem10)]`, `leading-5`, `font-14`, `pp_feedButton`]); let graphic = feedGraphics[feed]; if(graphic != null) { let svg = buildSvg(16, 16, graphic); spanContainer.append(svg); } const spanText = AppendNew(spanContainer, `span`); spanText.textContent = feed; } } // *********************************************************************************************************************** // ************************************************** SUBS *************************************************************** // *********************************************************************************************************************** renderSub(document.body); function renderSub(container) { // skip page without feed const checkIsFeed = container.querySelector(`.main-container`)?.querySelector(`main`)?.querySelector(`shreddit-feed-error-banner`); if(checkIsFeed == null) return; renderMasthead(); // flairBar awaitSubInfo(); } css.addRule('.masthead > section > div { display: flex; flex-direction: column; align-items: flex-start;}'); css.addRule('.masthead > section > div > div:last-child { align-self: flex-end;}'); function renderMasthead() { const masthead = document.body.querySelector(`.masthead`); if(masthead == null) return; const section = masthead.querySelector(`section`); section.style.top= `-3rem`; const div = section.querySelector(`div`); div.style.gap = `1rem`; } function awaitSubInfo() { const sub = getCurrentSub(); const subData = subs.get(sub); if(subData.flairs != undefined) { renderSubFlairs(sub); return; } fetch(`https://www.reddit.com/r/${sub}/api/link_flair_v2.json?raw_json=1`, { method: `get` }) .then(r => r.json()) .then(result => { if(result != null) { const loadedFlairs = []; for (const loadedFlair of result) { const flair = { text:loadedFlair.text, color:loadedFlair.text_color, background:loadedFlair.background_color, richtext: loadedFlair.richtext}; loadedFlairs.push(flair); } subData.flairs = loadedFlairs; subs.set(sub, subData); renderSubFlairs(sub); } else { console.log(`Reddit++: Unable to load subreddit flairs list`); } }); } css.addRule('.pp_flairMenuContainer { }'); css.addRule('.pp_flairMenu { display: flex; flex-direction: row; overflow: hidden; }'); css.addRule('.pp_flairUL { margin-top: 5px !important; flex-wrap: nowrap !important; position: relative; }'); //transition: left 0.2s; css.addRule('.pp_flairUL-smoothed { transition: left 0.1s ease-out;}'); css.addRule('.pp_flairBorderContainer { width: 100%; display: flex; justify-content: space-between; }'); css.addRule('.pp_flairBorderPrev { width: 20px; }'); css.addRule('.pp_flairBorder { z-index: 1; position: absolute; height: 40px; width: 20px; margin-top: 5px; }'); css.addRule('.pp_flairBorderLeft { background: linear-gradient(90deg, var(--color-neutral-background), 60%, var(--color-neutral-background-transparent));}'); css.addRule('.pp_flairBorderRight { background: linear-gradient(270deg, var(--color-neutral-background), 60%, var(--color-neutral-background-transparent));}'); css.addRule('.pp_flairSettings { color: #434849; padding-top:0.4rem !important; cursor:pointer;}'); function renderFlair(conatiner, sub, flair) { const a = AppendNew(conatiner, `a`, `no-decoration`); a.href = `/r/`+sub+`/?f=flair_name%3A%22`+flair.text+`%22`; const span = AppendNew(a, `span`, [`bg-tone-4`, `inline-block`, `truncate`, `max-w-full`, `text-12`, `font-normal`, `box-border`, `px-[6px]`, `rounded-[20px]`, `leading-4`, `max-w-full`, `py-xs`, `!px-sm`, `leading-4`, `h-xl`, `inline-flex`]); span.classList.add(flair.color == `light` ? `text-global-white` : `text-global-black`); span.style.backgroundColor = flair.background; for(const richElement of flair.richtext) { if(richElement.e == `text`) { const content = document.createTextNode(richElement.t); span.appendChild(content); } if(richElement.e == `emoji`) { const fimg = document.createElement(`faceplate-img`); fimg.classList.add(`flair-image`); fimg.setAttribute(`loading`, `lazy`); fimg.setAttribute(`width`, 16); fimg.setAttribute(`height`, 16); fimg.setAttribute(`src`, richElement.u); fimg.setAttribute(`alt`, richElement.a); span.appendChild(fimg); } } } function renderSubFlairs(sub) { if(settings.isDisabled(SETTING_FLAIR_BAR)) return; const feedContent = document.body.querySelector(`report-flow-provider`)?.querySelector(`.main-container`)?.querySelector(`shreddit-title`)?.parentNode; // skip render for non feed page if(feedContent == null) return; const prevFlairMenu = feedContent.parentNode.querySelector(`.pp_flairMenuContainer`) if(prevFlairMenu != null) { prevFlairMenu.remove(); } // load data const subData = subs.get(sub); const flairsData = flairs.get(sub); // skip render when sub haven't flairs if(subData.flairs == undefined || subData.flairs.length == 0) return; const flairMenuContainer = document.createElement(`div`); flairMenuContainer.classList.add(`pp_flairMenuContainer`); feedContent.before(flairMenuContainer); const flairMenu = AppendNew(flairMenuContainer, `div`, `pp_flairMenu`); const ul = AppendNew(flairMenu, `ul`, [`p-0`, `m-0`, `list-none`, `gap-xs`, `flex`, `flex-row`, `pp_flairUL`]); let flairsRendered = 0; for (const flair of subData.flairs) { if(flairsData.hidden != undefined && flairsData.hidden.includes(flair.text)) continue; const li = AppendNew(ul, `li`, `max-w-full`); renderFlair(li, sub, flair); flairsRendered++; } // prevent render empty menu if(flairsRendered == 0) { flairMenuContainer.remove(); return; } // borders const borderContainer = document.createElement(`div`); borderContainer.classList.add(`pp_flairBorderContainer`); flairMenuContainer.prepend(borderContainer); const borderLeftC = AppendNew(borderContainer, `div`, `pp_flairBorderPrev`); const borderLeft = AppendNew(borderLeftC, `div`, [`pp_flairBorder`, `pp_flairBorderLeft`]); borderLeft.textContent = ` `; const borderRightC = AppendNew(borderContainer, `div`, `pp_flairBorderPrev`); const borderRight = AppendNew(borderRightC, `div`, [`pp_flairBorder`, `pp_flairBorderRight`]); borderRight.textContent = ` `; const hr = document.createElement(`hr`); hr.classList.add(`border-0`, `border-b-sm`, `border-solid`, `border-b-neutral-border-weak`); flairMenuContainer.prepend(hr); const mymx = document.createElement(`div`); mymx.classList.add(`my-xs`, `mx-2xs0`); flairMenuContainer.prepend(mymx); // navigation ul.style.left = `25px`; const ulRect = ul.getBoundingClientRect(); const menuRect = flairMenu.getBoundingClientRect(); flairMenu.addEventListener(`mousemove`, e => {onMoveOverFlairs(e, ul, flairMenu)}); if(ulRect.width > menuRect.width * 1.72) { ul.classList.add(`pp_flairUL-smoothed`); } } function onMoveOverFlairs(e, ul, flairMenu) { const ulRect = ul.getBoundingClientRect(); const menuRect = flairMenu.getBoundingClientRect(); if(ulRect.width < menuRect.width) { ul.style.left = `25px`; return; } let scale = (e.clientX - (menuRect.x + 25)) / ((menuRect.right - 25) - (menuRect.x + 25)); scale = Math.max(0, Math.min(scale, 1)); ul.style.left = `${Math.round(25 - (ulRect.width - menuRect.width + 50) * scale)}px`; } //************ Flairs Settings ************* css.addRule('.pp_WindowScrollContent { overflow-y: scroll; }'); css.addRule('.pp_checkBoxContainer { float: right; position: relative; }'); css.addVar('--checkBox-background', '#1a1a1b1a', '#81818152'); css.addRule('.pp_checkBoxButtonActive { justify-content: flex-end; background-color: #0079d3; }'); css.addRule('.pp_checkBoxButton { position: relative; cursor:pointer; overflow:visible; display:flex; justify-content: start; background: transparent; background-color: var(--checkBox-background); padding: initial; height: 24px; width:37.5px; border-radius: 100px; border: 2px solid transparent; transition: background-color 0.2s linear; }'); css.addRule('.pp_checkBoxKnob { height: 19.5px; width:19.5px; background-color: #fff; box-shadow: 0 0 0 1px rgba(0,0,0,.1),0 2px 3px 0 rgba(0,0,0,.2); transition: 0.5s linear; border-radius: 57%; }'); css.addRule('.pp_fs_flairContainer { width: 100%; display: flex; align-items: center; margin-left: 3rem; }'); css.addRule('.pp_fs_flairPanelCheckboxArea { width: 200px; min-width: 200px; display: flex; justify-content: center; align-items: center; }'); css.addRule('.pp_fs_columnTittle { margin: 20px 57px 10px 40px; }'); function renderFlairsWindow(win, context) { const titlePanel = AppendNew(win.content, `div`, [`pp_WindowElement`, `pp_fs_columnTittle`]); const tittleBarArea = AppendNew(titlePanel, `div`, `pp_fs_flairPanelCheckboxArea`); const tittleBar = AppendNew(tittleBarArea, `div`, [`text-14`, `font-semibold`, `mb-xs`]); tittleBar.textContent = `show on bar:`; const tittleFeedArea = AppendNew(titlePanel, `div`, `pp_fs_flairPanelCheckboxArea`); const tittleFeed = AppendNew(tittleFeedArea, `div`, [`text-14`, `font-semibold`, `mb-xs`]); tittleFeed.textContent = `show in feed:`; const scroll = AppendNew(win.content, `div`, [`pp_WindowScrollContent`, `styled-scrollbars`]); const elements = AppendNew(scroll, `div`, `pp_WindowElementsContainer`); const subData = subs.get(context.sub); for (const flair of subData.flairs) { const panel = AppendNew(elements, `div`, `pp_WindowElement`); const flairContainer = AppendNew(panel, `div`, `pp_fs_flairContainer`); addFlairToggle(`hidden`); addFlairToggle(`banned`); function addFlairToggle(target) { const checkboxArea = AppendNew(panel, `div`, `pp_fs_flairPanelCheckboxArea`); const checkBoxContainer = AppendNew(checkboxArea, `div`, `pp_checkBoxContainer`); const checkBoxBack = AppendNew(checkBoxContainer, `button`, `pp_checkBoxButton`); const initState = !(flairs.get(context.sub)[target]?.includes(flair.text) ?? false); checkBoxBack.classList.toggle(`pp_checkBoxButtonActive`, initState); const knob = AppendNew(checkBoxBack, `div`, `pp_checkBoxKnob`); checkBoxBack.addEventListener(`click`, e => { const flairData = flairs.get(context.sub); let state = flairData[target]?.includes(flair.text) ?? false; checkBoxBack.classList.toggle(`pp_checkBoxButtonActive`, state); if(state) { flairData[target] = flairData[target].filter(f => f != flair.text); } else { const targetList = flairData[target] ?? []; targetList.push(flair.text); flairData[target] = targetList; } flairs.set(context.sub, flairData); }); } // flair renderFlair(flairContainer, context.sub, flair); } } // *********** Sub ContextMenu ************* document.body.addEventListener(`click`, clickSubredditMenu); function clickSubredditMenu(e) { if(e.target.matches(`shreddit-subreddit-header-buttons`) == true) { if(checkIsRendered(e.target)) return; const controlMenu = e.target.shadowRoot.querySelector(`shreddit-subreddit-overflow-control`).shadowRoot.querySelector(`faceplate-menu`); const originButton = controlMenu.querySelector(`li`); // flairs settings const menuFlairsButton = originButton.cloneNode(true); menuFlairsButton.querySelector(`.text-14`).textContent = `Flairs settings`; controlMenu.prepend(menuFlairsButton); const sub = getCurrentSub(); flairsWindow.onClose = () => { renderSubFlairs(sub); }; menuFlairsButton.addEventListener(`click`, () => { flairsWindow.open({sub:sub}); }); // about const link = document.createElement(`a`); link.href = `https://www.reddit.com/`+e.target.getAttribute(`prefixed-name`)+`/about/`; link.classList.add(`no-underline`); controlMenu.prepend(link); const menuAboutButton = originButton.cloneNode(true); menuAboutButton.querySelector(`.text-14`).textContent = `About`; link.prepend(menuAboutButton); } } // *********************************************************************************************************************** // ************************************************* POSTS *************************************************************** // *********************************************************************************************************************** css.addRule('.pp_bannedPost { visibility: hidden !important; max-height: 0px;}'); checkNativePosts(document.body); function checkNativePosts(container) { const main = container.querySelector(`main`); if(main != null) { main.querySelectorAll(`shreddit-post`).forEach((post) => { renderPost(post); }); } } function renderPost(post) { const sub = post.getAttribute(`subreddit-prefixed-name`).replace(`r/`, ``); const flairData = flairs.get(sub); const postFlair = post.querySelector(`shreddit-post-flair`)?.querySelector(`a`); if(postFlair != null) { const postFlairText = decodeURIComponent(postFlair.href.split(`%22`)[1]); if(flairData.banned != undefined && flairData.banned.includes(postFlairText)) { const next = post.parentElement.nextElementSibling; next.classList.add(`pp_bannedPost`); post.parentElement.classList.add(`pp_bannedPost`); post.classList.add(`pp_bannedPost`); post.querySelector(`faceplate-tracker[source="post_credit_bar"]`).classList.add(`pp_bannedPost`); } } } // *********************************************************************************************************************** // *********************************************** COMMENTS ************************************************************** // *********************************************************************************************************************** const NEWUSER_SECONDS_SHIFT = DAY_SECONDS * 64; const REFRESH_USER_SECONDS_SHIFT = DAY_SECONDS; const BLOCK_COOLDOWN_SECONDS = DAY_SECONDS + 42; let loadingComments = {}; let currentLoads = 0; function registryAllComments(container) { container.querySelectorAll(`shreddit-comment`).forEach((comment) => { registryComment(comment); }); } function registryComment(comment) { if(checkIsRendered(comment)) return; commentRenderIntersector.observe(comment.querySelector(`div[slot="commentMeta"]`)); } css.addRule('.pp_muted_avatar { opacity: 0.5; }'); css.addRule('.pp_muted_content { color: #a5a5a5; transition: color 0.2s; }'); css.addRule('.pp_muted_content:hover { color: var(--color-tone-1); }'); function renderComment(comment) { // collapse automoderator if(settings.isEnabled(SETTING_COLLAPSE_AUTOMODERATOR)) { const author = comment.getAttribute(`author`); if(author != null && author == `AutoModerator`) { comment.setAttribute(`collapsed`, ``); return; } } // add anchors const nickname = comment.querySelector(`div[slot="commentMeta"]`).querySelector(`faceplate-hovercard[data-id="user-hover-card"]`); // skip [deleted] if(nickname == null) return; const tagsAnchor = document.createElement(`div`); tagsAnchor.setAttribute(`pp-anchor`, `tags`); nickname.parentNode.querySelector(`.ml-2xs`).after(tagsAnchor); const time = nickname.parentNode.querySelector(`time`).parentNode.parentNode; const infoAnchor = document.createElement(`div`); infoAnchor.setAttribute(`pp-anchor`, `info`); time.before(infoAnchor); // make ghosted when karma below zero if(settings.isEnabled(SETTING_GHOSTED_COMMENTS) && comment.getAttribute(`score`) < 0) { comment.querySelector(`div[slot="commentAvatar"]`).classList.add(`pp_muted_avatar`); comment.querySelector(`faceplate-tracker[noun="comment_author"]`).querySelector(`a`).style.color = `#a5a5a5`; comment.querySelector(`div[slot="comment"]`).classList.add(`pp_muted_content`); } // registry image const image = comment.querySelector(`div[slot="comment"]`).querySelector(`faceplate-img`); if(image != null) { registryImage(image); } renderCommentTags(comment); awaitContextMenu(comment); awaitUserInfo(comment); } css.addRule('.pp_imageViewable { cursor: pointer; }'); function registryImage(imgContainer) { imgContainer.classList.add(`pp_imageViewable`); imgContainer.parentNode.removeAttribute(`href`); imgContainer.addEventListener(`click`, () => { imageViewer.open(imgContainer.getAttribute(`src`)); }); } function renderCommentTags(comment) { if(settings.isDisabled(SETTING_USER_TAGS)) return; const userId = comment.getAttribute(`author`); if(userId == null) return; const tagsData = tags.get(userId); const tagsContainer = comment.querySelector(`div[pp-anchor="tags"]`); // skip not ininitialized comments if(tagsContainer == null) return; // clear old tags tagsContainer.parentNode.querySelectorAll(`svg[tag="true"]`).forEach(tag => { tag.remove(); }); if(tagsData.tags != undefined) { for(const tag of tagsData.tags) { renderTag(tag); } } function renderTag(tag) { let tagSvg = buildSvg(20, 20, tagGraphics[tag], {w:1.5, f:`none`}); tagSvg.setAttribute(`tag`, `true`); tagSvg.setAttribute(`viewBox`, `-4 -4 20 20`); tagSvg.style.color = tagConfigs[tag][TAG_CONFIG_COLOR]; tagsContainer.after(tagSvg); } } function awaitUserInfo(comment) { const userId = comment.getAttribute(`author`); let userData = users.get(userId); if(userData.accountId == undefined) { loadUserInfo(comment, userId); return; } renderUserInfo(comment); } function loadUserInfo(comment, userId) { if(comment == null) { // do nothing } else if(loadingComments[userId] != undefined && loadingComments[userId] != null) { loadingComments[userId].push(comment); return; } else { loadingComments[userId] = [comment]; currentLoads++; } fetch(`https://oauth.reddit.com/user/${userId}/about.json`, { cache: `no-cache`, method: `get` }) .then(r => r.json()) .then(result => { if(result != null) { let userData = users.get(userId); userData.rating = result.data.link_karma + result.data.comment_karma / 2; if(result.data.subreddit.title) { userData.nick = result.data.subreddit.title; } userData.created = result.data.created; userData.accountId = result.kind + `_` + result.data.id; users.set(userId, userData); for (let comment of loadingComments[userId]) { renderUserInfo(comment); } loadingComments[userId] = null; currentLoads--; if(currentLoads == 0) { loadingComments = {}; } } }); } // TODO: need replace vars in top function renderUserInfo(comment) { let nickName = comment.querySelector(`faceplate-tracker[noun="comment_author"]`).querySelector(`a`); const userId = comment.getAttribute(`author`); let userData = users.get(userId); if(settings.isEnabled(SETTING_SHOW_NAMES) && userData.nick != undefined && userData.nick) { nickName.textContent = userData.nick; } // skip render user info if(settings.isDisabled(SETTING_USER_INFO)) return; const infoAnchor = comment.querySelector(`div[pp-anchor="info"]`); const rating = document.createElement(`div`); rating.classList.add(`text-neutral-content-weak`, `text-12`); rating.textContent = (userData.rating < 10000) ? `${Math.round(userData.rating / 100) / 10}K` : `${Math.round(userData.rating / 1000)}K`; infoAnchor.after(rating); const point = document.createElement(`span`); point.classList.add(`inline-block`, `my-0`, `mx-2xs`, `text-12`, `text-neutral-content-weak`); point.textContent = `•`; rating.after(point); if(userData.created > (Date.now()/1000 - NEWUSER_SECONDS_SHIFT)) { const tagsAnchor = comment.querySelector(`div[pp-anchor="tags"]`); const newSvg = buildSvg(20, 20, newUserGraphic); newSvg.setAttribute(`viewBox`, `-2 -2 20 20`); newSvg.style.color = `#69b508`; tagsAnchor.before(newSvg); } } function awaitContextMenu(comment) { let contextMenuButton = comment.querySelector(`shreddit-overflow-menu`)?.shadowRoot?.querySelector(`faceplate-dropdown-menu`); if(contextMenuButton == null) { setTimeout(() => { awaitContextMenu(comment); }, 50); return; } contextMenuButton.addEventListener(`click`, () => { renderContextMenu(comment); }, {once:true}); } css.addRule('.pp_tagsPanel { display: flex; justify-content: space-around; width:auto; border-bottom: solid 1px var(--color-neutral-border-weak); padding: 4px; gap: 8px; margin-bottom: 4px; }'); css.addRule('.pp_tagButton { cursor:pointer; display: flex; align-content: center; flex-wrap: wrap; height:45px; padding: 4px 20px; margin: 0px 0px; color: var(--color-neutral-border-weak); border-radius: 5px; }'); css.addRule('.pp_tagButton svg { width:20px; transition: transform 0.15s; }'); css.addRule('.pp_tagButton:hover svg { transform: scale(1.2, 1.2); transition: transform 0.3s; }'); css.addRule('.pp_tagButton:hover { background-color: var(--color-neutral-background-hover); }'); css.addRule('.pp_tagButtonActive:hover { opacity: 0.8; }'); css.addRule('.pp_tagHintOffset { left: 50%; position:absolute; }'); css.addRule('.pp_tagHintContainer { display: flex; justify-content: center; position:fixed; }'); css.addRule('.pp_tagHint { display: flex; align-items: center; position: absolute; top: -35px; height: 25px; padding: 0px 12px; color: var(--color-neutral-background-weak); font: var(--font-small); background-color: var(--color-neutral-content-strong); border-radius: 5px;}'); function renderContextMenu(comment) { // close other context menu document.body.click(); let contextMenuButton = comment.querySelector(`shreddit-overflow-menu`).shadowRoot; let originButton = contextMenuButton.querySelector(`faceplate-tracker[noun="report"]`); let contextMenuContainer = originButton.parentNode; if(settings.isEnabled(SETTING_HIDE_SHARE)) { let linkButton = originButton.cloneNode(true); linkButton.querySelector(`span .text-14`).textContent = `Copy link`; originButton.before(linkButton); let linkPath = linkButton.querySelector(`path`); linkPath.setAttribute(`d`, linkGraphics[0].d); let linkPathB = linkPath.cloneNode(true); linkPathB.setAttribute(`d`, linkGraphics[1].d); linkPath.after(linkPathB); const permalink = comment.getAttribute(`permalink`); linkButton.addEventListener(`click`, () => { navigator.clipboard.writeText(`https://www.reddit.com${permalink}`); notify(`Link copied`); }); } css.registry(contextMenuButton); // close context menu let openButton = contextMenuButton.querySelector(`button`); openButton.addEventListener(`click`, () => {document.body.click(); }); // skip render tags if(settings.isDisabled(SETTING_USER_TAGS)) return; const tagHintOffset = document.createElement(`div`); tagHintOffset.classList.add(`pp_tagHintOffset`); contextMenuContainer.prepend(tagHintOffset); const tagHintContainer = document.createElement(`div`); tagHintContainer.classList.add(`pp_tagHintContainer`); tagHintOffset.prepend(tagHintContainer); const tagHint = AppendNew(tagHintContainer, `div`, `pp_tagHint`); tagHint.style.display = `none`; const tagsPanel = document.createElement(`div`); tagsPanel.classList.add(`pp_tagsPanel`); tagHintOffset.after(tagsPanel); const userId = comment.getAttribute(`author`); renderTagButton(USERTAG_FOLLOWED); renderTagButton(USERTAG_LIKED); renderTagButton(USERTAG_WARNING); renderTagButton(USERTAG_BLOCKED); function renderTagButton(tag) { const tagButton = AppendNew(tagsPanel, `span`, `pp_tagButton`); tagButton.setAttribute(`tag`, tag); const subscribeIcon = buildSvg(20, 20, tagButtonGraphics[tag], {w:2, f:`none`}); tagButton.appendChild(subscribeIcon); tagButton.addEventListener(`click`, () => { tagButtonClick(userId, tag, tagButton); }); tagButton.addEventListener(`mouseenter`, () => {tagHintEnter(userId, tag, tagButton, tagHint); }); tagButton.addEventListener(`mouseleave`, () => {tagHintOut(userId, tag, tagButton, tagHint); }); } refreshTagButtons(tagsPanel, userId); } function refreshTagButtons(tagsPanel, userId) { const tagsData = tags.get(userId); const tagsList = tagsData?.tags ?? []; tagsPanel.querySelectorAll(`.pp_tagButton`).forEach(button => { const tag = button.getAttribute(`tag`); button.removeAttribute(`has-cooldown`); button.removeAttribute(`has-blocked`); if(tagsList.includes(tag)) { button.classList.add(`pp_tagButtonActive`); button.style.backgroundColor = tagConfigs[tag][TAG_CONFIG_COLOR]; button.style.color = `white`; } else { button.classList.remove(`pp_tagButtonActive`); button.style.color = tagConfigs[tag][TAG_CONFIG_COLOR]; button.style.removeProperty(`background-color`); if(tag == USERTAG_BLOCKED && tagsData.blockCooldown != undefined && Date.now()/1000 < tagsData.blockCooldown) { button.setAttribute(`has-cooldown`, ``); button.style.color = `#adadad`; } if(tag == USERTAG_FOLLOWED && tagsList.includes(USERTAG_BLOCKED)) { button.setAttribute(`has-blocked`, ``); button.style.color = `#adadad`; } } }); } function tagButtonClick(userId, tag, button) { if(button.getAttribute(`has-cooldown`) != null || button.getAttribute(`has-blocked`) != null) { notify(`Unable to do this`); return; } let tagsData = tags.get(userId); if(tagsData.tags == undefined) { tagsData.tags = []; } let isAdded = false; if(tagsData.tags.includes(tag)) { tagsData.tags = tagsData.tags.filter(t => t != tag); } else { tagsData.tags.push(tag); isAdded = true; // auto clear follow state if(tag == USERTAG_BLOCKED) { tagsData.tags = tagsData.tags.filter(t => t != USERTAG_FOLLOWED); } } if(tagsData.tags.length > 1) { tagsData.tags.sort((firstItem, secondItem) => tagConfigs[firstItem][TAG_CONFIG_PRIORITY] - tagConfigs[secondItem][TAG_CONFIG_PRIORITY]); } tags.set(userId, tagsData); // refresh comments tags document.body.querySelectorAll(`shreddit-comment[author="${userId}"]`).forEach(comment => { renderCommentTags(comment); if(isAdded && tag == USERTAG_BLOCKED) { comment.setAttribute(`collapsed`, ``); } }); // execute specific operations if(tag == USERTAG_FOLLOWED) { executeOperation(`UpdateProfileFollowState`, isAdded ? `FOLLOWED` : `NONE`, userId); } if(tag == USERTAG_BLOCKED) { if(!isAdded) { tagsData.blockCooldown = Date.now()/1000 + BLOCK_COOLDOWN_SECONDS; tags.set(userId, tagsData); } executeOperation(`UpdateRedditorBlockState`, isAdded ? `BLOCKED` : `NONE`, userId); } // refresh context menu refreshTagButtons(button.parentNode, userId); } function tagHintEnter(userId, tag, button, tagHint) { tagHint.style.display = null; tagHint.dataset.target = tag; const tagsData = tags.get(userId); tagHint.innerText = tagConfigs[tag][(tagsData?.tags ?? []).includes(tag) ? TAG_CONFIG_HINT_ACTIVATED : TAG_CONFIG_HINT]; if(button.getAttribute(`has-cooldown`) != null) { const cooldownHours = Math.round((tagsData.blockCooldown - Date.now()/1000) / HOUR_SECONDS); tagHint.innerText = `Unable block for ${cooldownHours}h after unblocking`; } if(button.getAttribute(`has-blocked`) != null) { tagHint.innerText = `Unable to follow on blocked user`; } } function tagHintOut(userId, tag, button, tagHint) { if(tagHint.dataset?.target == tag) { tagHint.style.display = `none`; } } function executeOperation(operation, state, userId) { let userData = users.get(userId); const body = { csrf_token:getCookie(`csrf_token`), operation:operation, variables:{ input: operation == `UpdateRedditorBlockState` ? { redditorId:userData.accountId, blockState:state } : { accountId:userData.accountId, state:state, } } }; fetch(`https://www.reddit.com/svc/shreddit/graphql`, { method: `post`, headers: new Headers({ 'Accept': `application/json`, 'Content-Type': `application/json` }), body: JSON.stringify(body) }) .then(r => r.json()) .then(result => { if(result != null && result.errors?.message) { notify(result.errors.message); } }); } // *********************************************************************************************************************** // *********************************************** SETTINGS ************************************************************** // *********************************************************************************************************************** css.addRule(' .pp_SettingSubtittle { font-size: 10px; font-weight: 700; letter-spacing: 0.5px; line-height: 12px; min-height: 20px; color: #7c7c7c; text-transform: uppercase; border-bottom: 1px solid #edeff1; margin-top: 1rem; padding: 0rem 3rem;}'); css.addRule(' .pp_SettingPropertyHeader { display: flex; flex-direction: column; margin-left: 3rem; justify-content: center; }'); css.addRule(' .pp_SettingPropertyHeaderTittle { font-size: 16px; font-weight: 500; line-height: 20px; margin-bottom: 4px; }'); css.addRule(' .pp_SettingPropertyHeaderDescription { font-size: 12px; font-weight: 400; line-height: 16px; color: #7c7c7c; }'); css.addRule(' .pp_SettingPropertyButtonContainer { display: flex; align-items: center; justify-content:flex-end; flex-grow:1; }'); css.addRule(' .pp_SettingChangesBannerContainer { position: absolute; top: 0px; width: 900px; overflow-y: hidden; opacity: 0; transition: opacity 0.15s ease-in-out; }'); css.addRule(' .pp_SettingChangesBanner { display: flex; justify-content: center; margin: 2rem 15%; padding: 1rem; border-radius: 15px; background-color: #ffd40017; border: solid 1px #ffd400; color: #d7b300; font-weight: 500; }'); css.addRule(' .pp_SettingChangesBannerActive { opacity: 1 !important; }'); function renderSettingsWindow(win, context) { // close user context menu document.body.click(); win.changes = 0; const changesBannerContainer = AppendNew(win.content, `div`, `pp_SettingChangesBannerContainer`); const changesBanner = AppendNew(changesBannerContainer, `div`, `pp_SettingChangesBanner`); changesBanner.textContent = `Page will be reloaded to apply new settings`; const scroll = AppendNew(win.content, `div`, [`pp_WindowScrollContent`, `styled-scrollbars`]); const elements = AppendNew(scroll, `div`, `pp_WindowElementsContainer`); addSettingToggle(`Wide mode`, `Fit sidebars to screen borders and limit content width to 700px`, SETTING_WIDE_MODE); addSubtittle(`Feed`); addSettingToggle(`Feed buttons`, `Unwrap feed sorting buttons`, SETTING_FEED_BUTTONS); addSettingToggle(`Flair bar`, `Display available flairs to faster navigation. Specific flairs may be hidden in subreddit flairs settings.`, SETTING_FLAIR_BAR); addSettingToggle(`Soft backplates`, `Make post backplates with gradient color`, SETTING_BACKPLATES); addSubtittle(`Comments`); addSettingToggle(`User info`, `Show user karma and leaf mark for new user`, SETTING_USER_INFO); addSettingToggle(`User tags`, `Enable custom tags for users`, SETTING_USER_TAGS); addSettingToggle(`Show names`, `Use display names (if user set it) instead usernames`, SETTING_SHOW_NAMES); addSettingToggle(`Bigger fonts`, `Make fonts bigger for better reading`, SETTING_BIGGER_FONTS); addSettingToggle(`Hide share button`, `Replace share button to context menu`, SETTING_HIDE_SHARE); addSettingToggle(`Ghosted comments`, `Make comments ghosted when karma below zero`, SETTING_GHOSTED_COMMENTS); addSettingToggle(`Collapse Automoderator`, `Automatic collapse all automoderator comments`, SETTING_COLLAPSE_AUTOMODERATOR); function addSubtittle(text) { const subtittle = AppendNew(elements, `h3`, `pp_SettingSubtittle`); subtittle.textContent = text; } function addSettingToggle(tittleText, descriptionText, settingName) { const propertyArea = AppendNew(elements, `div`, `pp_WindowElement`); const header = AppendNew(propertyArea, `div`, `pp_SettingPropertyHeader`); const tittle = AppendNew(header, `div`, `pp_SettingPropertyHeaderTittle`); tittle.textContent = tittleText; if(descriptionText != null) { const description = AppendNew(header, `div`, `pp_SettingPropertyHeaderDescription`); description.textContent = descriptionText; } const buttonContainer = AppendNew(propertyArea, `div`, `pp_SettingPropertyButtonContainer`); const toggleArea = AppendNew(buttonContainer, `div`, `pp_fs_flairPanelCheckboxArea`); const toggleContainer = AppendNew(toggleArea, `div`, `pp_checkBoxContainer`); const toggleBack = AppendNew(toggleContainer, `button`, `pp_checkBoxButton`); toggleBack.classList.toggle(`pp_checkBoxButtonActive`, settings.get(settingName) != false); const knob = AppendNew(toggleBack, `div`, `pp_checkBoxKnob`); let changed = false; toggleBack.addEventListener(`click`, e => { let state = !(settings.get(settingName) == false); toggleBack.classList.toggle(`pp_checkBoxButtonActive`, !state); settings.set(settingName, !state); win.changes += changed ? -1 : 1; changed = !changed; changesBannerContainer.classList.toggle(`pp_SettingChangesBannerActive`, win.changes > 0); }); } } // *********************************************************************************************************************** // ************************************************* UTILS *************************************************************** // *********************************************************************************************************************** function getCurrentSub() { const raw = window.location.href.split(`reddit.com/r/`); return raw.length > 1 ? raw[1].split(`/`)[0] : null; } function checkIsRendered(node) { if(node.getAttribute(`pp-rendered`) != null) { return true; } else { node.setAttribute(`pp-rendered`, ``); return false; } } function notify(message, actionTittle, actionCallback) { const hasAction = actionTittle != undefined; let toaster = document.body?.querySelector(`alert-controller`)?.shadowRoot?.querySelector(`toaster-lite`); if(toaster == null) { console.log(`Reddit++: Failed to notify (${message})`); return; } let toast = document.createElement(`faceplate-toast`); toast.classList.add(`theme-rpl`); toast.textContent = message; toaster.appendChild(toast); setTimeout(() => {toast.setAttribute(`_fading`, ``); }, hasAction ? 8000 : 3000); } function buildSvg(w, h, graphics, settings) { const XMLNS = `http://www.w3.org/2000/svg`; const svg = document.createElementNS(XMLNS, `svg`); svg.setAttribute(`viewBox`, `0 0 ${w} ${h}`); svg.setAttribute(`width`, `${w}px`); svg.setAttribute( `height`, `${h}px`); svg.setAttribute(`fill`, `none`); for(var graphic of graphics) { let path = document.createElementNS(XMLNS, `path`); path.setAttribute(`stroke-linecap`, `round`); path.setAttribute(`stroke-linejoin`, `round`); path.setAttribute(`stroke`, graphic?.c ?? (settings?.c ?? `currentColor`)); path.setAttribute(`stroke-width`, graphic?.w ?? (settings?.w ?? 1)); path.setAttribute(`fill`, graphic?.f ?? (settings?.f ?? `currentColor`)); path.setAttribute(`d`, graphic.d); svg.appendChild(path); } return svg; } function AppendNew(prev, name, classes) { const el = document.createElement(name); if(classes != undefined) { if(typeof classes === `string` && classes) { el.classList.add(classes); } else { for(const c of classes) { el.classList.add(c); } } } prev.append(el); return el; } function getSettingsRevision() { return localStorage.getItem(`pp_settings_revision`) ?? 0; } function nextSettingsRevision() { settingsRevision++; localStorage.setItem(`pp_settings_revision`, settingsRevision); } function getCookie(key) { return document.cookie .split(`; `) .find((row) => row.startsWith(key)) ?.split(`=`)[1]; } function awaitElement(element, callback) { if(element == null) { setTimeout(() => { callback(document.body); }, 100); return true; } return false; } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址