您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
UX enhancements for Reddit's 2018 redesign: filters, collapse child comments, subreddit info...
当前为
// ==UserScript== // @name Fixdit for Reddit Redesign // @namespace http://tampermonkey.net/ // @version 0.6.8 // @description UX enhancements for Reddit's 2018 redesign: filters, collapse child comments, subreddit info... // @author scriptpost (u/postpics) // @match https://www.reddit.com/* // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js // @grant GM_setValue // @grant GM_getValue // @grant GM_listValues // @grant GM_deleteValue // @noframes // ==/UserScript== (function ($, undefined) { $(function () { if (!$('#hamburgers').length) return; // probably using old site. const _stylesheet = String.raw`<style type="text/css" id="fixdit">.fixd_filter_msg button:hover,button.fixd_collapse:hover,button.fixd_collapse_all:hover{text-decoration:underline}#fixd_launch{box-sizing:border-box;position:fixed;top:2px;right:2px;width:24px;height:24px;z-index:100;text-align:center;border-radius:50%;border:2px solid hsla(70,0%,100%,.5);border-left-color:hsla(70,0%,0%,1);border-right-color:hsla(70,0%,0%,1);background:0 0;cursor:pointer;-moz-user-select:none;user-select:none}#fixd_launch.fixd_active{border-color:hsla(70,0%,100%,.5);border-top-color:hsla(70,0%,0%,1);border-bottom-color:hsla(70,0%,0%,1)}#fixd_launch:not(.fixd_active):hover:before{content:"Fixdit settings...";display:inline-block;padding:6px 12px;font-size:80%;color:#fff;position:absolute;right:calc(100% + 12px);white-space:nowrap;background:hsla(0,0%,0%,.8);border-radius:2px;pointer-events:none}#fixd_settings{position:fixed;top:0;right:0;opacity:0;z-index:101;padding:24px 12px;background:hsla(180,2%,90%,.95);border-radius:3px;box-shadow:0 5px 10px hsla(0,0%,0%,.25),0 0 3px hsla(0,0%,0%,.25);font-size:80%;width:300px;visibility:hidden;overflow:hidden;transition:visibility .2s,height .2s,bottom .2s,left .2s,top .2s,right .2s,opacity .2s}#fixd_settings.fixd_active{visibility:visible;opacity:1;top:5px;right:24px}#fixd_settings.fixd_expanded{bottom:5px}#fixd_settings .fixd_dialog,#fixd_settings .fixd_dialog>div{position:absolute;top:0;left:0;bottom:0;border-radius:4px;right:0}body.fixd_debug #fixd_settings:after{display:block;margin-top:4px;content:"Version " attr(data-version);text-align:right;font-size:12px;color:#7f7f7f}.fixd_description{margin:12px 0 0;line-height:1.3}#fixd_settings h2,#fixd_settings h3{font-weight:400;display:inline-block;margin-bottom:10px;vertical-align:middle}#fixd_settings h1{font-size:140%;font-weight:400;margin:0 0 .5em}#fixd_settings h2{font-size:120%}#fixd_settings h3{font-size:100%}#fixd_settings h3 span{display:block;margin-top:4px;font-size:120%}#fixd_settings .fixd_option,#fixd_settings .fixd_option_btn,#fixd_settings .fixd_option_select,#fixd_settings .fixd_switch{display:block;margin:1px -12px 0;cursor:default;-moz-user-select:none;user-select:none;background:#f3f0f0}#fixd_settings .fixd_option_select input,#fixd_settings .fixd_switch input{vertical-align:middle}#fixd_settings .fixd_option_btn{margin:1px -12px;cursor:default}#fixd_settings .fixd_option_btn:after{content:" >";color:#999;font-weight:700;float:right}#fixd_settings .fixd_option_btn:hover:after{color:#000}#fixd_settings .fixd_option_btn:hover,#fixd_settings .fixd_setting:hover{outline:hsla(0,0%,0%,.3) solid 1px}#fixd_settings .fixd_option_btn,#fixd_settings .fixd_option_select,#fixd_settings .fixd_switch{padding:10px 16px}#fixd_settings .fixd_enabled{background:#fff}#fixd_settings .fixd_switch:not(.fixd_option){font-size:120%;margin-bottom:8px;background:#f3f0f0}#fixd_settings .fixd_switch.fixd_enabled:not(.fixd_option){color:#000;background:#fff}#fixd_settings .fixd_option span{padding-left:.3em}#fixd_settings .fixd_dialog>div{padding:24px 12px 8px;background:#e4e6e6;display:flex;flex-direction:column}.fixd_dialog textarea{box-sizing:border-box;min-width:100%;max-width:100%;min-height:50px;max-height:100%;border:1px solid #fff;flex:1}.fixd_settings_buttons{text-align:right;margin:6px 0}.fixd_btn_back,.fixd_btn_save{text-transform:uppercase;border:1px solid transparent;border-radius:2px;vertical-align:middle}.fixd_btn_back{font-weight:700;color:transparent;margin-right:12px;padding:0;overflow:hidden;width:30px;height:30px;line-height:30px;margin-bottom:10px;border:1px solid #ccc;background:0 0}.fixd_btn_back:hover{border:1px solid #666}.fixd_btn_back:before{color:#000;content:"< "}.fixd_btn_save{color:#fff;padding:8px 24px;background:#0076b2;cursor:pointer}.fixd_btn_save:hover{background-color:#0087cc}button.fixd_collapse,button.fixd_collapse_all{cursor:pointer;color:inherit;display:inline-block;background:0 0;outline:0;font-size:12px;font-weight:700}a+button.fixd_collapse{margin-left:10px}button.fixd_collapse_all{margin-left:20px;color:#a6a4a4;font-size:12px;font-weight:700;text-transform:uppercase}button.fixd_collapse:before,button.fixd_collapse_all:before{content:"Hide "}button.fixd_collapse:after,button.fixd_collapse_all:after{content:" <<"}button.fixd_collapse.fixd_active:before,button.fixd_collapse_all.fixd_active:before{content:"Show "}button.fixd_collapse.fixd_active:after,button.fixd_collapse_all.fixd_active:after{content:" >"}#fixd_indicators{position:fixed;top:40px;right:0;z-index:100}.fixd_observer_indicator{box-sizing:border-box;margin:5px 0 0;width:20px;height:15px;border:1px solid hsla(0,0%,0%,.2);background:hsla(0,0%,50%,.2);transition:all .3s}.fixd_observer_indicator.fixd_active{border-color:hsla(0,0%,0%,1);background:hsla(0,0%,100%,1)}.fixd_observer_indicator.fixd_active:before,.fixd_observer_indicator:hover:before{content:attr(data-fixd-name);display:block;position:absolute;margin-top:-1px;right:100%;white-space:nowrap;color:#fff;font-size:11px;padding:3px 6px;background:hsla(0,0%,0%,.4);pointer-events:none}.fixd_observer_indicator:hover:before{background:hsla(0,0%,0%,.8)}.fixd_popup{box-sizing:border-box;position:absolute;z-index:100;padding:12px;font-size:12px;border-radius:4px;border-top:4px solid #c1cfd6;color:#1c1c1c;background-color:#fff;box-shadow:rgba(0,0,0,.2) 0 1px 3px;overflow:hidden}#fixd_popup_subreddit.fixd_subscriber{border-top-color:#0076d1}.fixd_popup>div{float:left}.fixd_popup>div:nth-of-type(2){width:200px;margin-left:12px;padding-left:12px;border-left:1px solid #edeff1}.fixd_popup .fixd_popup_subs span,.fixd_popup h2{display:block;font-size:16px;font-weight:500;line-height:20px}.fixd_filtered .Comment,.fixd_no_blank .icon-outboundLink,.fixd_popup:not(.fixd_filterable) .fixd_popup_filter,.fixd_unfiltered .fixd_filter_msg{display:none}.fixd_popup .fixd_popup_created,.fixd_popup .fixd_popup_subs{font-weight:500}.fixd_popup .fixd_popup_subs{margin-top:12px}.fixd_popup h2:before{content:"r/"}.fixd_popup .fixd_popup_subtitle{margin-bottom:.5em;color:#7f7f7f}.fixd_popup .fixd_popup_desc{color:#7f7f7f;line-height:1.2}.fixd_popup .fixd_popup_desc,.fixd_popup .fixd_popup_title{margin:0 0 .5em}.fixd_popup_filter{font-size:12px;text-transform:uppercase;padding:8px 12px;margin-top:12px;border-radius:2px;color:#fff;border:1px solid transparent;background:#0076d1;cursor:pointer}.fixd_popup_filter.fixd_active{color:#0076d1;border:1px solid;background:#fff}.fixd_popup_filter.fixd_active:before{content:"un"}.fixd_tooltip{pointer-events:none}body:not(.fixd_debug) .fixd_hidden{visibility:hidden;position:absolute}body.fixd_debug .fixd_hidden{opacity:.6}body.fixd_debug .fixd_filtered{outline:#dc143c solid 1px}body.fixd_debug .fixd_unfiltered{outline:green solid 1px}.fixd_filter_msg{font-size:12px;color:#878a8c}.fixd_filter_msg em{font-style:italic}.fixd_filter_msg button{content:"Show comment";color:inherit;background:0 0;cursor:pointer;margin:12px 0 0 12px}button.fixd_no_icon.fixd_active[data-click-id=upvote]{color:#f40!important}button.fixd_no_icon.fixd_active[data-click-id=downvote]{color:#7091ff!important}button.fixd_no_icon[data-click-id=upvote]:hover{color:#cc3600}button.fixd_no_icon[data-click-id=downvote]:hover{color:#5b75cc}button.fixd_no_icon[data-click-id=upvote]:before,button.fixd_no_icon[data-click-id=downvote]:before{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:redesignFont}button.fixd_no_icon[data-click-id=upvote]:before{content:"\F12A"}button.fixd_no_icon[data-click-id=downvote]:before{content:"\F107"}body.fixd_reduce_comment_spacing .Comment.top-level{margin-top:0}body.fixd_debug .fixd_no_blank{outline:#00f solid 1px}</style>`; $('body').append(_stylesheet); // TODO: // Add notification each time settings are changed. // Add observer to hidden comments. // Add option to change the color of visited links. // Observer for switching from comment permalink to all comments. // Find more observers to add. // strip_prefixes doesn't fire when switching view or page location. // Firefox adds horiz scrollbar to window when static header enabled. // CHANGELOG (latest): // Prevent settings from flashing on page load. // Fix issues on user profile page. // Fix subreddit info popup not disappearing sometimes. // Optimize overall efficiency. // Improved overall consistency and reliability. // Remove ability to disable internal/required features. // Remove ability to toggle options of type 'radio'. // Oserver for switching view. class Feature { constructor(data) { this.loaded = JSON.parse(GM_getValue('features', '{}')); if (this.loaded.hasOwnProperty(data.id)) { data.enabled = this.loaded[data.id].enabled; } else { const db_entry = this.loaded; db_entry[data.id] = { enabled: data.enabled, options: {} }; GM_setValue('features', JSON.stringify(db_entry)); this.enabled = data.enabled; } this.feature = data; this.setting = Feature.add_setting(data); this.options = {}; this.public = {}; } static add_setting(data) { return new Setting(data); } add_option(option) { const loaded_options = this.loaded[this.feature.id].options; let is_stored = loaded_options.hasOwnProperty(option.id); const is_choices = option.hasOwnProperty('choices'); // Check if outdated values are in storage and reset to new values. // TBDL: Remove this code after June 10. if (is_stored && is_choices) { if (option.type === 'radio') { const is_outdated = isNaN(loaded_options[option.id].value); if (is_outdated) { is_stored = false; } } else if (option.type === 'checkbox') { const is_oudated = isNaN(loaded_options[option.id].value[0]); if (is_oudated) { is_stored = false; } } } // Remove 'enabled' from storage if it's deprecated. // TBDL: Remove this code after June 10. if (is_stored && !option.hasOwnProperty('enabled') && loaded_options[option.id].hasOwnProperty('enabled')) { let db_entry = this.loaded; delete db_entry[this.feature.id].options[option.id].enabled; delete loaded_options[option.id].enabled; GM_setValue('features', JSON.stringify(db_entry)); } if (is_stored) { // Modify passed object. if (option.hasOwnProperty('enabled')) { option.enabled = loaded_options[option.id].enabled; } if (loaded_options[option.id].hasOwnProperty('value')) { option.value = loaded_options[option.id].value; } } else { // Continue without modifying. const db_entry = this.loaded; db_entry[this.feature.id].options[option.id] = {}; if (option.hasOwnProperty('value')) { db_entry[this.feature.id].options[option.id].value = option.value; } db_entry[this.feature.id].options[option.id].enabled = option.enabled; GM_setValue('features', JSON.stringify(db_entry)); this.loaded = db_entry; } this.options[option.id] = new Option(option); } set toggle(fn) { this.setting.toggle = fn; } // arg: object set options_toggle(functions) { this.options_triggers = functions; } redraw() { // Finds all the changed options and updates their appearance. // Also calls any provided callback functions. this.setting.redraw(); for (var oid in this.options) { const new_val = this.loaded.options[oid].enabled; const current_val = this.options[oid].enabled; if (new_val !== current_val) { const fid = this.feature.id; this.options[oid].enabled = new_val; const panel = '.fixd_options[data-id="' + fid + '"]'; const dialog = '.fixd_dialog[data-id="' + oid + '"]'; const $btn = $(panel + ' .fixd_option[data-id="' + oid + '"]'); const $switch = $(dialog + ' .fixd_switch[data-id="' + oid + '"]'); $btn.add($switch).each((idx, el) => { if (el.classList.contains('fixd_enabled')) { el.classList.remove('fixd_enabled'); } else { el.classList.add('fixd_enabled'); } }); if (this.options_triggers && this.options_triggers.hasOwnProperty(oid) && this.setting.enabled) { this.options_triggers[oid](new_val); } } } } set update(data) { this.setting.enabled = data.enabled; this.loaded = data; this.redraw(); } } class Setting { constructor(data) { for (let key in data) { this[key] = data[key]; } this.button = $('<div>', { "text": data.label, "class": "fixd_option_btn", "data-click-id": "fixd_setting", "data-id": data.id })[0]; if (data.enabled) { $(this.button).addClass('fixd_enabled'); } if (!data.hidden) { $('#fixd_settings .fixd_panel:not(.fixd_options)').append(this.button); } } redraw() { const el = $('.fixd_options .fixd_switch:not(.fixd_option)[data-id="' + this.id + '"]')[0]; if (this.enabled) { $(el).add(this.button).addClass('fixd_enabled'); } else { $(el).add(this.button).removeClass('fixd_enabled'); } if (this.toggle) { this.toggle(this.enabled); } } } class Option { constructor(data) { for (let key in data) { this[key] = data[key]; } } } /** * Creates an observer wrapper that can be extended from anywhere. * Each time an RO is extended, it disconnects the last observer * and adds a new one in its place with all the added functions. * When there's a mutation and a condition is met, it loops over * all the saved functions (from this.actions). */ class Reddit_Observer { constructor(info) { this.name = info.name; this.target = info.target; this.options = { childList: true }; if (info.subtree) { this.options.subtree = true; } this.actions = []; this.connect(info.target); } set any(callback) { this.any_basis = callback; } set added(callback) { this.added_basis = callback; } set removed(callback) { this.removed_basis = callback; } get records() { if (this.observer) { return this.observer.takeRecords(); } } loop_actions(a, b) { for (var i = 0; i < this.actions.length; i++) { this.actions[i](this, a, b); } } extend(fn) { this.actions.push(fn); if (this.target) { this.connect(); } } connect(newTarget) { if (newTarget) { this.target = newTarget; } else if (!this.target) { return console.warn("Can't connect observer because target node is undefined"); } const self = this; const mutation = function (mutations) { for (let i = 0; i < mutations.length; i++) { const a = mutations[i].addedNodes; const r = mutations[i].removedNodes; if (self.added_basis && a.length && a[0].nodeType === Node.ELEMENT_NODE) { self.added_basis(self, a[0], mutations[i]); } else if (self.removed_basis && r.length && r[0].nodeType === Node.ELEMENT_NODE) { self.removed_basis(self, r[0], mutations[i]); } else if (self.any_basis) { self.any_basis(self, mutations[i]); } } }; if (this.observer) { this.observer.disconnect(); } this.observer = new MutationObserver(mutation); this.observer.observe(this.target, this.options); } } // Set up pre-defined Observers: const Body_Obs = new Reddit_Observer({ name: 'Body', target: document.body }); const Page_Subtree_Obs = new Reddit_Observer({ name: 'Page subtree', target: $('#hamburgers + div')[0], subtree: true }); const Post_Obs = new Reddit_Observer({ name: 'Post', target: (() => { const p = document.getElementsByClassName('Post'); if (p.length) { return p[0].parentNode.parentNode.parentNode; } })() }); const Comment_Obs = new Reddit_Observer({ name: 'Comment', target: (() => { const c = document.getElementsByClassName('Comment'); if (c.length) { return c[0].parentNode.parentNode.parentNode.parentNode; } })() }); const Lb_Obs = new Reddit_Observer({ name: 'L-box', target: document.getElementById('hamburgers').parentNode.parentNode }); const Lb_Subtree_Obs = new Reddit_Observer({ name: 'L-box sub', target: document.getElementById('lightbox'), subtree: true }); const Lb_TT_Obs = new Reddit_Observer({ name: 'L-box tooltip', target: document.getElementById('overlayAbsoluteTooltipContent') }) const Side_Obs = new Reddit_Observer({ name: 'Side', target: document.getElementsByClassName('fixd--side')[0] }); // Handle each mutation: const added_to_body = (self, node, mutation) => { self.loop_actions(node); }; Body_Obs.added = added_to_body; const added_to_page_subtree = (self, node, mutation) => { const posts = node.getElementsByClassName('Post'); const lb = document.getElementById('lightbox'); if (posts.length) { const list = posts[0].parentNode.parentNode.parentNode; if (list === node || list.parentNode === node || node.parentNode === self.target) { Post_Obs.connect(list); Side_Obs.connect(document.getElementsByClassName('fixd--side')[0]); self.loop_actions(node); } } else if (node.classList.contains('Post')) { self.loop_actions(node); } }; Page_Subtree_Obs.added = added_to_page_subtree; const added_to_post_list = (self, node, mutation) => { if (node.getElementsByClassName('Post')[0]) { self.loop_actions(node); } }; Post_Obs.added = added_to_post_list; const added_to_comment_list = (self, node, mutation) => { if (node.getElementsByClassName('Comment')[0]) { self.loop_actions(node, mutation); } }; Comment_Obs.added = added_to_comment_list; const add_remove_lightbox = (self, mutation) => { if (mutation.addedNodes.length) { const lb = document.getElementById('lightbox'); Lb_Subtree_Obs.connect(lb); const comments = lb.getElementsByClassName('Comment'); if (comments.length) { // Loaded from memory. const list = comments[0].parentNode.parentNode.parentNode.parentNode; Comment_Obs.connect(list); self.loop_actions(list, mutation); } Side_Obs.connect(lb.childNodes[0].childNodes[1]); Lb_TT_Obs.connect(document.getElementById('overlayAbsoluteTooltipContent')); } else { Lb_Obs.connect(); } }; Lb_Obs.any = add_remove_lightbox; const added_to_lightbox = (self, node, mutation) => { const comments = node.getElementsByClassName('Comment'); if (comments.length) { const list = comments[0].parentNode.parentNode.parentNode.parentNode; if (list.parentNode === node) { Comment_Obs.connect(list); Side_Obs.connect(self.target.childNodes[0].childNodes[1]); self.loop_actions(node, mutation); } } }; Lb_Subtree_Obs.added = added_to_lightbox; const added_lb_tooltip = (self, node, mutation) => { self.loop_actions(node, mutation); }; Lb_TT_Obs.added = added_lb_tooltip; const added_to_side = (self, node, mutation) => { self.loop_actions(node, mutation); }; Side_Obs.added = added_to_side; const UT = { format_date: { age: date => { // https://stackoverflow.com/a/23286781 const diff_date = new Date(new Date() - date); let y = diff_date.toISOString().slice(0, 4) - 1970; let m = diff_date.getMonth() + 0; let d = diff_date.getDate(); let result; if (y > 0) result = (y === 1) ? y + ' year' : y + ' years'; else if (m > 0) result = (m === 1) ? m + ' month' : m + ' months'; else result = (d === 1) ? d + ' day' : d + ' days'; return result; } }, get_post_author_link: node => { const $context = $(node).find('div[data-click-id="body"] > div:nth-of-type(2)'); let links; if ($context.length) { links = $context[0].getElementsByTagName('a'); for (let i = 0; i < links.length; i++) { const href = links[i].getAttribute('href'); if (href && href.startsWith('/user/')) { return links[i]; } } } }, is_page_type: (keyword) => { let result; switch (keyword) { case 'profile': result = !!document.getElementById('profile-nav-menu-tooltip'); break; case 'search': result = !!document.getElementById('search-results-sort'); break; } return result; } }; const get_reddit_data = function (kind, name) { return new Promise(function (resolve, reject) { let url; const key = kind + '_' + name; const cache = JSON.parse(GM_getValue('cache', '{}')); let ratelimit = JSON.parse(GM_getValue('ratelimit_get', '{}')); if (cache[key]) { resolve(cache[key]); } else if (!ratelimit.remaining || ratelimit.remaining > 150) { const req = new XMLHttpRequest(); url = '/r/' + name + '/about.json'; req.open('GET', url); req.onload = function () { if (req.status === 200) { const response = JSON.parse(this.response).data; let json_data = cache; ratelimit = { used: this.getResponseHeader('x-ratelimit-used'), remaining: this.getResponseHeader('x-ratelimit-remaining'), reset: this.getResponseHeader('x-ratelimit-reset') }; json_data[key] = { name: response.display_name, title: response.title, subtitle: response.header_title, desc: response.public_description, created: response.created, subs: response.subscribers, subscriber: response.user_is_subscriber }; GM_setValue('ratelimit_get', JSON.stringify(ratelimit)); GM_setValue('cache', JSON.stringify(json_data)); resolve(json_data[key]); } else { reject(Error(req.statusText)); } }; req.onerror = function () { reject(Error("Network Error")); }; req.send(); } else if (ratelimit.remaining) { reject(ratelimit.remaining + ' requests remaining.'); } }) }; /** * // TBDL: deprecate. * @param {string} html_tag Element to look inside. * @param {string} query Inner text to match against. * @param {Node} context Region to search. */ var getElementByText = function (html_tag, query, context) { const result = document.evaluate( "//" + html_tag + "[contains(., '" + query + "')]", context, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); return result; }; const clear_cache = (() => { let ratelimit = JSON.parse(GM_getValue('ratelimit_get', '{}')); if (ratelimit.used !== undefined && ratelimit.used <= 1) { GM_setValue('cache', '{}'); } })(); const settings_form = function () { const layout = $('<div>', { "id": "fixd_settings", 'data-version': GM_info.script.version }).append($('<div>', { "class": "fixd_panel" }).append($('<h1>', { "text": "Fixdit Settings" })))[0]; layout.addEventListener('click', handle_settings_click, false); return layout; }; const draw_options_panel = function (fid) { const settings = $('#fixd_settings')[0]; const feature = FT[fid]; const setting_toggle = $('<label>', { "html": '<input type="checkbox"> On', "data-click-id": 'fixd_setting_toggle', "data-id": fid, "class": "fixd_switch" })[0]; const panel = $('<div>', { "class": "fixd_panel fixd_options" })[0]; const btn_back = $('<button>', { "data-click-id": "fixd_back", "class": "fixd_btn_back", "text": "Back" })[0]; settings.appendChild(panel); panel.dataset.id = feature.setting.id; panel.appendChild(btn_back); panel.appendChild($('<h2>', { "text": feature.setting.label })[0]); if (!feature.setting.hidden) { panel.appendChild(setting_toggle); } if (feature.setting.enabled) { setting_toggle.querySelector('input').setAttribute('checked', 'checked'); setting_toggle.classList.add('fixd_enabled'); } for (var oid in feature.options) { draw_option_item(panel, feature.options[oid]); } }; const draw_option_item = function (panel, option) { const label = document.createTextNode(option.label); const row = $('<label>', { "class": "fixd_option", "data-id": option.id })[0]; if (option.hidden) return; // Will it be a checkbox or button? if (!option.type || option.type !== 'bool') { row.dataset.clickId = "fixd_option_btn"; row.appendChild($('<div>', { "class": "fixd_option_btn", "text": option.label })[0]); row.classList.add('fixd_option_btn'); row.innerText = option.label; } else { row.classList.add('fixd_switch'); row.dataset.clickId = "fixd_option_toggle"; row.innerHTML = '<input type="checkbox"> ' + option.label; } if (option.enabled || option.type === 'radio') { row.classList.add('fixd_enabled'); let checkbox = row.querySelector('input'); if (checkbox) { checkbox.setAttribute('checked', 'checked'); } } panel.appendChild(row); }; const append_option_selector = function (el, data, saved) { for (let uid in data.choices) { if (isNaN(uid)) { saved = data; // Reset outdated saved values. } const choice = data.choices[uid]; const row = $('<label>', { 'html': '<input type="checkbox"> ' + choice[1], 'class': 'fixd_option_select' })[0]; const input = row.querySelector('input'); input.setAttribute('name', 'fixd_option_choices'); input.setAttribute('value', uid); if (data.type === 'radio') { input.setAttribute('type', 'radio'); } if (saved.value.includes(uid)) { input.setAttribute('checked', 'checked'); row.classList.add('fixd_enabled'); } el.appendChild(row); }; }; const draw_option_dialog = function (oid) { const panel = $('#fixd_settings .fixd_options')[0]; const fid = $(panel).data('id'); const saved = JSON.parse(GM_getValue('features', '{}')); const option = FT[fid].options[oid]; const saved_option = saved[fid].options[oid]; if (option.type === 'list') { saved_option.value.sort((a, b) => { return a.localeCompare(b, 'en', { 'sensitivity': 'base' }); }); } const dialog = panel.appendChild($('<div>', { "data-id": oid, "class": "fixd_dialog" })[0]); const dialog_content = dialog.appendChild($('<div>')[0]); const header = $('<div>', { "class": "fixd_settings_header" })[0]; dialog_content.appendChild(header); header.appendChild($('<button>', { "data-click-id": "fixd_back", "class": "fixd_btn_back", "text": "Back" })[0]); header.appendChild($('<h3>', { "html": FT[fid].setting.label + '<span>' + option.label + '</span>' })[0]); if (option.type !== 'radio') { const toggle = $('<label>', { "html": '<input type="checkbox"> On', "data-click-id": 'fixd_option_toggle', "data-id": option.id, "class": "fixd_switch" })[0]; dialog_content.appendChild(toggle); if (saved_option.enabled) { toggle.classList.add('fixd_enabled'); toggle.querySelector('input').setAttribute('checked', 'checked'); } } if (option.description) { dialog_content.appendChild($('<div>', { "class": "fixd_description", "html": option.description })[0]); } const buttons = $('<div>', { "class": "fixd_settings_buttons" })[0]; dialog_content.appendChild(buttons); buttons.appendChild($('<button>', { "data-click-id": "fixd_save", "class": "fixd_btn_save", "text": "Save changes" })[0]); if (option.type === 'list') { dialog_content.appendChild($('<textarea>', { "data-click-id": "fixd_option_value", "text": saved_option.value.join('\n') })[0]); } else if (option.hasOwnProperty('choices')) { append_option_selector(dialog_content, option, saved_option); } $('#fixd_settings').addClass('fixd_expanded').append(dialog).css({}); }; const close_dialog = function () { $('#fixd_settings').removeClass('fixd_expanded'); $('#fixd_settings .fixd_dialog').remove(); }; const handle_submit = function (ev) { const panel = $('#fixd_settings .fixd_options')[0]; const fid = panel.dataset.id; const oid = document.getElementById('fixd_selected_option').dataset.id; const saved = JSON.parse(GM_getValue('features', '{}')); const feature = FT[fid]; const option = feature.options[oid]; let db_entry = saved; if (option.type === 'list') { let value = $('#fixd_settings .fixd_dialog textarea').val(); value = value.replace(/[^a-zA-Z\d\n#._-]/mg, ""); db_entry[fid].options[oid].value = value.split('\n'); } else if (option.hasOwnProperty('choices')) { const inputs = document.getElementsByName('fixd_option_choices'); // Clear the old value before repopulating. db_entry[fid].options[oid].value = []; $(inputs).each((idx, el) => { if (el.value && el.checked) { if (option.type === 'radio') { db_entry[fid].options[oid].value[0] = el.value; return false; } else { db_entry[fid].options[oid].value[idx] = el.value; } } }); } GM_setValue('features', JSON.stringify(db_entry)); close_dialog(); }; const handle_back = function (ev) { if (document.getElementsByClassName('fixd_dialog').length) { close_dialog(); } else { $('#fixd_settings .fixd_options').remove(); $('#fixd_settings .fixd_panel:not(.fixd_options)').show(); } }; const close_settings = ev => { $('#fixd_settings, #fixd_launch').removeClass('fixd_active'); close_dialog(); }; const handle_settings_click = (ev) => { if (ev.target.dataset.clickId === 'fixd_setting') { $('#fixd_settings .fixd_panel:not(.fixd_options)').hide(); draw_options_panel(ev.target.dataset.id); } if (ev.target.dataset.clickId === 'fixd_option_btn') { const oid = ev.target.dataset.id; let selected = document.getElementById('fixd_selected_option'); if (selected) { selected.removeAttribute('id'); } ev.target.id = 'fixd_selected_option'; draw_option_dialog(oid); } if (ev.target.dataset.clickId === 'fixd_save') { handle_submit(ev); } if (ev.target.dataset.clickId === 'fixd_back') { handle_back(ev); } if ($(ev.target).hasClass('fixd_switch') || $(ev.target.parentNode).hasClass('fixd_switch')) { let fid = $('#fixd_settings .fixd_options').data('id'); let oid = ev.target.dataset.id || ev.target.parentNode.dataset.id; if (!ev.target.dataset.id) { // Checkbox has been clicked by user or label. handle_settings_switch(ev, fid, oid); } } if ($(ev.target).hasClass('fixd_option_select') || $(ev.target.parentNode).hasClass('fixd_option_select')) { if (ev.target.tagName === 'INPUT') { if (ev.target.getAttribute('type') === 'radio') { let choices = document.getElementsByName('fixd_option_choices'); $(choices).parent().removeClass('fixd_enabled'); } ev.target.parentNode.classList.toggle('fixd_enabled'); } } }; $('body').append(settings_form); document.addEventListener('click', ev => { const path = ev.composedPath(); const launcher = document.getElementById('fixd_launch'); const settings = document.getElementById('fixd_settings'); const popup = document.getElementsByClassName('fixd_popup')[0]; if (!path.includes(popup)) { FT.subreddit_info.public.close_popup(); } if (!path.includes(launcher) && !path.includes(settings)) { close_settings(); } if (ev.target.dataset.clickId === 'fixd_launcher') { if ($(ev.target).hasClass('fixd_active')) { close_settings(); } else { $('#fixd_launch, #fixd_settings').addClass('fixd_active'); $('#fixd_settings .fixd_options').remove(); $('#fixd_settings .fixd_panel:not(.fixd_options)').show(); } } }); const handle_settings_switch = function (ev, fid, oid) { const saved = JSON.parse(GM_getValue('features', '{}')); const feature = FT[fid]; const clicked = ev.target.dataset.clickId || ev.target.parentNode.dataset.clickId; let db_entry = saved; if (clicked === 'fixd_setting_toggle') { if (saved[fid].enabled) { db_entry[fid].enabled = false; } else { db_entry[fid].enabled = true; } } else { let option = feature.options[oid]; if (saved[fid].options[oid].enabled) { db_entry[fid].options[oid].enabled = false; } else { db_entry[fid].options[oid].enabled = true; } } GM_setValue('features', JSON.stringify(db_entry)); feature.update = db_entry[fid]; } // Begin modules/features. const OBS = { on_add: {}, on_rem: {}, on_any: {} }; const FT = {}; FT.ui_selectors = (() => { var feature = new Feature({ id: "ui_selectors", label: "Add UI Selectors", enabled: true, internal: true, hidden: true }); const tag_subreddits_boxes = (arg) => { let context = arg; let is_profile = UT.is_page_type('profile'); let lightbox = document.getElementById('lightbox'); $(context).find('button').each((idx, el) => { if (['SUBSCRIBE', 'UNSUBSCRIBE'].includes(el.innerText.toUpperCase())) { const item = el.parentNode.parentNode; // TBDL: img/svg selectors should be more precise. const img = item.getElementsByTagName('img'); const svg = item.getElementsByTagName('svg'); if (img.length === 1 || svg.length === 1) { let $contents = $(item).parent().parent(); if (is_profile && !lightbox) { $contents = $contents.parent(); } let $h3 = $contents.find('> h3'); if ($h3.length === 1) { let $container = $contents.parent().parent(); $container.addClass('fixd--subreddits'); return false; } } } }); context = $('.fixd--subreddits').filter(':last').next()[0]; let button_text = $(context).find('button:first').text().toUpperCase(); if (['SUBSCRIBE', 'UNSUBSCRIBE'].includes(button_text)) { // Repeat tag_subreddits_boxes(context); } }; // TBDL: reduce repitition: var init = function () { let $page, $content, $side, posts; let lightbox = document.getElementById('lightbox'); let is_profile = UT.is_page_type('profile'); if (!lightbox) { let comment_sort_pick = document.getElementById('CommentSort--SortPicker'); $page = $('#hamburgers + div').addClass('fixd--page'); posts = document.getElementsByClassName('Post'); $content = $(posts[0]).parent().parent().parent().parent().parent(); if (comment_sort_pick) { $content = $(posts[0]).parent().parent().parent(); } else if (is_profile) { $content = $content.parent().parent(); } } else { $content = $(lightbox).find('.Post').parent().parent(); } $content.addClass('fixd--content'); $side = $content.find('+ div').addClass('fixd--side'); if ($side.length) { // Side_Obs.connect($side[0].childNodes[0].childNodes[1]); const moderators_h3 = getElementByText('h3', 'Moderators', $side[0]).iterateNext(); $(moderators_h3).parent().addClass('fixd--moderators'); tag_subreddits_boxes($side[0]); } }; init(); Page_Subtree_Obs.extend((self, mutation) => { init(); }); Lb_Obs.extend((self, mutation) => { init(); }); Lb_Subtree_Obs.extend((self, mutation) => { init(); }); Side_Obs.extend((self, mutation) => { init(); }); return feature; })(); FT.ui_tweaks = (() => { var feature = new Feature({ id: "ui_tweaks", label: "UI Tweaks", enabled: false }); feature.add_option({ id: "no_prefix", label: "No prefixes for subreddits, users", type: "bool", enabled: false }); feature.add_option({ id: "no_blanks", label: "All links can open in current tab", type: "bool", enabled: false }); feature.add_option({ id: "override_vote_icons", label: "Override custom vote icons", enabled: false, type: "bool" }); feature.add_option({ id: "static_header", label: "Make the top bar stationary/static", enabled: false, type: "bool", update: 'test' // TBDL: function to be called when option is modified }); feature.add_option({ id: "reduce_comment_spacing", label: "Reduce comment spacing", enabled: false, type: 'bool' }); const init_override_vote_icon = function (el) { if (!el.childNodes.length) { // Remove style override so we can search the url string for active/inactive el.style.background = ''; const computed_style = window.getComputedStyle(el); const bg_value = computed_style.getPropertyValue('background-image'); const class_list = ['fixd_no_icon']; if (bg_value.includes('IconInactive')) { class_list.push('fixd_inactive'); } else if (bg_value.includes('IconActive')) { class_list.push('fixd_active'); } $(el).addClass(class_list.join(' ')); el.style.background = 'none'; } }; const init_override_vote_icons = function (context) { if (feature.options.override_vote_icons.enabled) { const selector = 'button[data-click-id="upvote"], button[data-click-id="downvote"]'; $(context).find(selector).each((idx, el) => { init_override_vote_icon(el); }); } }; const strip_prefixes = function (node) { if (feature.options.no_prefix.enabled) { let u = UT.get_post_author_link(node); if (u) { u.innerText = u.innerText.replace("u/", ""); } let s = $(node).find('a[data-click-id="subreddit"]'); s.each((idx, el) => { if (!el.children.length) { el.innerText = el.innerText.replace("r/", ""); } }); } }; const remove_blanks = function (context) { if (feature.options.no_blanks.enabled) { const anchors = context.getElementsByTagName('a'); for (let i = 0; i < anchors.length; i++) { if (anchors[i].getAttribute('target')) { anchors[i].removeAttribute('target'); anchors[i].classList.add('fixd_no_blank'); } } } }; const static_header = (enabled) => { if (enabled) { document.querySelector('body > div #header').style.position = 'absolute'; } else { document.querySelector('body > div #header').style.position = ''; } }; const reduce_comment_spacing = (enabled) => { if (enabled) { document.body.classList.add('fixd_reduce_comment_spacing'); } else { document.body.classList.remove('fixd_reduce_comment_spacing'); } } feature.options_toggle = { 'static_header': static_header, 'reduce_comment_spacing': reduce_comment_spacing }; if (feature.setting.enabled) { static_header(feature.options.static_header.enabled); reduce_comment_spacing(feature.options.reduce_comment_spacing.enabled); init_override_vote_icons(document.body); remove_blanks(document); const posts = document.getElementsByClassName('Post'); for (let i = 0; i < posts.length; i++) { strip_prefixes(posts[i]); } Body_Obs.extend((self, node) => { remove_blanks(node); }); Lb_Obs.extend((self, mutation) => { init_override_vote_icons(self.target); remove_blanks(self.target); }); Lb_Subtree_Obs.extend((self, mutation) => { init_override_vote_icons(document.getElementById('lightbox')); remove_blanks(self.target); }); Page_Subtree_Obs.extend((self, node) => { const posts = node.getElementsByClassName('Post'); for (let i = 0; i < posts.length; i++) { strip_prefixes(posts[i]); } remove_blanks(node); init_override_vote_icons(node); }); Post_Obs.extend((self, node) => { strip_prefixes(node); init_override_vote_icons(node); remove_blanks(node); }); Comment_Obs.extend((self, node) => { init_override_vote_icons(node); remove_blanks(node); }); $(document).click(ev => { if (['upvote', 'downvote'].includes(ev.target.dataset.clickId)) { init_override_vote_icons(ev.target.parentNode); } }); } return feature; })(); FT.filter_content = (function () { const feature = new Feature({ id: "filter_content", label: "Filter Content", enabled: true }); feature.add_option({ id: "subreddits", label: "Posts by subreddit", type: "list", enabled: true, description: "One subreddit name per line. No commas or slashes. Ignores search results.", value: [] }); feature.add_option({ id: "users", label: "Posts by user", description: "One user name per line. No commas or slashes. Ignores search results.", type: "list", enabled: false, value: [] }); feature.add_option({ id: "comments", label: "Comments by user", description: "One user name per line. No commas or slashes.", type: "list", enabled: false, value: [] }); const blocked_subs = feature.options.subreddits; const blocked_submitters = feature.options.users; const blocked_comments = feature.options.comments; const regex = { url_subreddit: /.*\/r\//i, url_user: /.*\/user\//i }; const is_match = function (query, list) { let result = list.findIndex(item => query.toUpperCase() === item.toUpperCase()); if (result !== -1) { return true; } }; const init_posts = function (context) { const posts = context.getElementsByClassName('Post'); for (let i = 0; i < posts.length; i++) { filter_post(posts[i].parentNode.parentNode); } }; const init_comments = function (context) { const comments = context.getElementsByClassName('Comment'); for (let i = 0; i < comments.length; i++) { filter_comment(comments[i].parentNode.parentNode.parentNode); } }; const filter_post = function (node) { // if (document.getElementById('lightbox') || UT.is_page_type('profile')) return; let sub_href, user_href; if (feature.options.subreddits.enabled) { sub_href = $(node).find('.Post a[data-click-id="subreddit"]:first').attr('href'); } if (feature.options.users.enabled) { let user_link = UT.get_post_author_link(node); if (user_link) { user_href = user_link.getAttribute('href'); } } if (sub_href) { let sub_name = sub_href.replace(regex.url_subreddit, "").replace("/", ""); // TEST: if (node.classList.contains('fixd_hidden')) { console.error("Already filtered", node); } if (is_match(sub_name, blocked_subs.value)) { node.classList.add('fixd_hidden'); } } if (!node.classList.contains('fixd_hidden') && user_href) { let user_name = user_href.replace(regex.url_user, "").replace("/", ""); if (is_match(user_name, blocked_submitters.value)) { node.classList.add('fixd_hidden'); } } }; const filter_comment = function (node) { let user_link = $(node).find('.Comment > div:nth-of-type(2) > div > div > a:first').attr('href'); let is_hidden = !!node.getElementsByClassName('icon-expand').length; if (!user_link || is_hidden) return false; let user_name = user_link.replace(regex.url_user, ""); user_name = user_name.replace("/", ""); if (is_match(user_name, blocked_comments.value)) { node.classList.add('fixd_filtered'); let cid; node.querySelector('.Comment').classList.forEach(str => { const match = /^t1_/.exec(str); if (match) { cid = match.input; return false; } }); let c_node = document.getElementById(cid); $(c_node).append($('<div>', { 'html': "<span>" + user_name + "</span> <em>(Fixdit filtered)</em>", 'class': "fixd_filter_msg" }).append($('<button>', { 'text': 'Show comment', 'data-click-id': "fixd_unfilter_btn" }))); } }; const handle_body_click = (ev) => { let clicked = ev.target; if (clicked.dataset.clickId === 'fixd_unfilter_btn') { // TBDL: reconnect comment_change observer for fixd_collapse. let comment = $(clicked).parents('.fixd_filtered')[0]; if (comment.classList.contains('fixd_filtered')) { comment.classList.replace('fixd_filtered', 'fixd_unfiltered'); } else if (comment.classList.contains('fixd_unfiltered')) { comment.classList.replace('fixd_unfiltered', 'fixd_filtered'); } } }; if (feature.setting.enabled) { if (feature.options.comments.enabled) { init_comments(document); document.body.addEventListener('click', handle_body_click, false); Lb_Obs.extend((self, node) => { init_comments(node); }); Lb_Subtree_Obs.extend((self, node) => { init_comments(node); }); Comment_Obs.extend((self, node) => { filter_comment(node); }); } if (feature.options.subreddits.enabled || feature.options.users.enabled) { init_posts(document); // page loaded Page_Subtree_Obs.extend((self, node) => { init_posts(node); }); Post_Obs.extend((self, node) => { filter_post(node); }); } } return feature; })(); FT.subreddit_info = (function () { const feature = new Feature({ id: "subreddit_info", label: "Subreddit Info Box", enabled: true }); feature.add_option({ id: 'delay', label: 'Popup delay', choices: { 1: ['short', 'Short', 200], 2: ['medium', 'Medium', 400], 3: ['long', 'Long', 700] }, value: ['2'], type: 'radio' }); const delay_uid = feature.options.delay.value[0]; const delay_open = feature.options.delay.choices[delay_uid][2] || 400; const delay_close = 100; let tmo_open; let tmo_close; const get_popup = function (ev) { feature.public.close_popup(); const box = $('<div>', { "id": "fixd_popup_subreddit", "class": 'fixd_popup' }).css({ 'display': 'none' })[0]; box.addEventListener('click', ev => { if (ev.target.classList.contains('fixd_popup_filter')) { const saved = JSON.parse(GM_getValue('features', '{}')); const name = $('#fixd_popup_subreddit').data('id'); let db_entry = saved; const arr = saved.filter_content.options.subreddits.value; if (ev.target.classList.contains('fixd_active')) { const idx = arr.indexOf(name); db_entry.filter_content.options.subreddits.value.splice(idx); ev.target.classList.remove('fixd_active'); } else { db_entry.filter_content.options.subreddits.value.push(name); ev.target.classList.add('fixd_active'); } GM_setValue('features', JSON.stringify(db_entry)); } }, false); box.addEventListener('mouseover', ev => { window.clearTimeout(tmo_close); }, false); box.addEventListener('mouseleave', ev => { feature.public.close_popup(); }, false); return box; }; const add_popup = (data, ev) => { const box = get_popup(ev); const classes = []; const filter_btn_classes = ["fixd_popup_filter"]; const saved = JSON.parse(GM_getValue('features', '{}')); const filter_data = saved.filter_content.options.subreddits; if (data.subscriber) { classes.push('fixd_subscriber'); } if (saved.filter_content.enabled && filter_data.enabled) { classes.push('fixd_filterable'); if (filter_data.value.includes(data.name)) { filter_btn_classes.push('fixd_active'); } } let document_width = $('html')[0].offsetWidth; let target_offset = $(ev.target).offset().left; let offset_left = target_offset; if ((document_width - target_offset) < (document_width / 2)) { offset_left -= 240; } $(box) .attr('data-id', data.name) .addClass(classes.join(' ')) .css({ top: $(ev.target).offset().top + ev.target.offsetHeight, left: offset_left, display: '' }) .append($('<div>') .append($('<h2>', { "class": "fixd_popup_name", "text": data.name })) .append($('<div>', { "class": "fixd_popup_created", "html": data.created })) .append($('<div>', { "class": "fixd_popup_subs", "html": '<span>' + data.subs + '</span> Subscribers' })) .append($('<button>', { "class": filter_btn_classes.join(' '), "text": 'Filter' })) ) .append($('<div>') .append($('<div>', { "class": "fixd_popup_title", "text": data.title })) .append($('<div>', { "class": "fixd_popup_subtitle", "text": data.subtitle })) .append($('<div>', { "class": "fixd_popup_desc", "html": data.desc })) ); document.body.appendChild(box); }; const init_popup = function (ev) { let name = ev.target.getAttribute('href').split('/')[2]; get_reddit_data('t5', name).then((data) => { const date_created = new Date(data.created * 1000); const formatted = { name: data.name, title: data.title, subtitle: data.subtitle, created: UT.format_date.age(date_created), subs: data.subs.toLocaleString(), subscriber: data.subscriber, desc: data.desc }; add_popup(formatted, ev); }, function (error) { console.warn("Error retrieving subreddit info popup.", error); }); }; feature.public.close_popup = (ev) => { $('#fixd_popup_subreddit').remove(); }; if (feature.setting.enabled) { let mouse_over = document.body.addEventListener('mouseover', ev => { if (ev.target.tagName === 'A') { let a = ev.target; if ((a.dataset.clickId === 'subreddit' || $(a).parents('p, .md, .fixd--subreddits').length) && a.getAttribute('href').startsWith('/r/')) { window.clearTimeout(tmo_open); window.clearTimeout(tmo_close); tmo_open = window.setTimeout(() => { init_popup(ev); }, delay_open); let mouse_out = (ev) => { ev.target.removeEventListener('mouseleave', mouse_out); window.clearTimeout(tmo_open); tmo_close = window.setTimeout(() => { feature.public.close_popup(); }, delay_close); }; a.addEventListener('mouseleave', mouse_out, false); } } }, false); } return feature; })(); FT.comments_collapse = (function () { const feature = new Feature({ id: "comments_collapse", label: "Collapsible Child Comments", enabled: true }); feature.add_option({ id: 'auto', type: 'bool', label: 'Automatically collapse children', enabled: false }); const auto_on = feature.options.auto.enabled; const init = function (context, is_mutate) { let lb = document.getElementById('lightbox'); if (!lb && UT.is_page_type('profile')) return; let comments = context.getElementsByClassName('Comment'); if (comments.length) { let comments_list = comments[0].parentNode.parentNode.parentNode.parentNode.childNodes; const sort_picker = $('#CommentSort--SortPicker').parent()[0]; const btn_all_classList = ['fixd_collapse_all']; if (auto_on && !is_mutate) { btn_all_classList.push('fixd_active'); } const $btn_all = $('<button>', { "text": "children", "class": btn_all_classList.join(' ') }); if (sort_picker) { $(sort_picker).append($btn_all); } for (var i = 0; i < comments_list.length; i++) { init_comment(comments_list[i], is_mutate); } } }; const init_comment = function (item, is_mutate) { item.classList.add('fixd_comment_wrap'); const is_comment = !!item.getElementsByClassName('Comment').length; const is_child = !item.getElementsByClassName('top-level').length; const is_thread = !!item.getElementsByClassName('threadline').length; const is_hidden = !!item.getElementsByClassName('icon-expand').length; if (is_comment && !is_child) { $(item).addClass('fixd_top-level'); const $next = $(item).next(); const next_is_child = $next.length && !$next[0].getElementsByClassName('top-level').length; const next_is_thread = $next.length && !!$next[0].getElementsByClassName('threadline').length; if (next_is_child && next_is_thread && !is_hidden && !$(item).find('.fixd_collapse').length) { const btn_classList = ['fixd_collapse']; if (auto_on && !is_mutate) { btn_classList.push('fixd_active'); } const $btn = $('<button>', { "text": "children", "class": btn_classList.join(' ') }); let $target = $(item).find('button:last'); if (!$target.length) { $target = $(item).find('a:last'); } $btn.insertAfter($target); } } if (auto_on && is_child && !is_mutate && !is_hidden) { item.classList.add('fixd_hidden'); } }; const toggle_visibility = function (clicked) { let wrap = $(clicked).parents('.fixd_comment_wrap')[0]; let is_active = $(clicked).hasClass('fixd_active'); const check_next = function ($next) { const next_is_child = !$next[0].getElementsByClassName('top-level').length; const next_is_thread = !!$next[0].getElementsByClassName('threadline').length; if (next_is_child && next_is_thread) { if (is_active) { $next.removeClass('fixd_hidden'); } else { $next.addClass('fixd_hidden'); } if ($next.next().length) { check_next($next.next()); } } }; check_next($(wrap).next()); if (is_active) { $(clicked).removeClass('fixd_active'); } else { $(clicked).addClass('fixd_active'); } }; const handle_click = function (ev) { if ($(ev.target).hasClass('fixd_collapse')) { ev.stopImmediatePropagation(); toggle_visibility(ev.target); } else if ($(ev.target).hasClass('fixd_collapse_all')) { ev.stopImmediatePropagation(); if ($(ev.target).hasClass('fixd_active')) { $('.fixd_collapse.fixd_active').click(); } else { $('.fixd_collapse:not(.fixd_active)').click(); } if ($('.fixd_collapse:not(.fixd_active)').length) { $(ev.target).removeClass('fixd_active'); } else { $(ev.target).addClass('fixd_active'); } } }; if (feature.setting.enabled) { init(document, false); $(document).on('click', handle_click); Lb_Obs.extend((self, node) => { init(node, true); }); Lb_Subtree_Obs.extend((self, node) => { init(node, true); }); Comment_Obs.extend((self, node, mutation) => { init_comment(mutation.previousSibling, true); }); } return feature; })(); FT.menu_hover = (function () { var feature = new Feature({ id: "menu_hover", label: "Hover to open menus", enabled: false }); feature.add_option({ id: 'menus', label: "Choose menus", enabled: true, type: 'checkbox', choices: { 1: ['sortpicker', 'Sort Posts', '#ListingSort--SortPicker'], 2: ['commentsort', 'Comment Sort Picker', '#CommentSort--SortPicker'], 3: ['user', 'User dropdown', '#USER_DROPDOWN_ID'], 4: ['headermoderate', 'Moderate (header)', '#Header--Moderation'] }, value: ['1', '2', '3', '4'] }); feature.add_option({ id: 'delay', label: "Delay", requires: "menus", type: 'radio', choices: { 1: ['short', 'Short', 200], 2: ['medium', 'Medium', 400], 3: ['long', 'Long', 700] }, value: ['2'] }); const menus_val = feature.options.menus.value; const delay_uid = feature.options.delay.value[0]; const delay_open = feature.options.delay.choices[delay_uid][2] || 400; if (feature.setting.enabled && feature.options.menus.enabled) { const init = function () { for (let i = 0; i < menus_val.length; i++) { const uid = menus_val[i]; if (uid !== null) { add_menu_listener(feature.options.menus.choices[uid]); } } }; const add_menu_listener = function (menu) { let commit_timeout; const name = menu[0]; let selector = menu[2]; if (name === 'sortpicker') { selector = $(selector).parent()[0]; } $(selector).on('mouseenter', function (ev) { commit_timeout = window.setTimeout(() => { ev.currentTarget.click(); }, delay_open); }); $(selector).on('mouseleave', function (ev) { window.clearTimeout(commit_timeout); }); }; init(); if (menus_val.includes('2')) { Lb_Obs.extend((self, mutation) => { add_menu_listener(feature.options.menus.choices[2]); }); Lb_Subtree_Obs.extend((self, mutation) => { add_menu_listener(feature.options.menus.choices[2]); }); } } return feature; })(); FT.remindme = (function () { const feature = new Feature({ id: "remindme", label: "Remind Me", enabled: false, hidden: true }); return feature; })(); FT.debug = (function () { var feature = new Feature({ id: "debug", label: "Debug Fixdit", enabled: false }); const init = (enabled) => { if (enabled) { $('#fixd_indicators').show(); document.body.classList.add('fixd_debug'); } else { $('#fixd_indicators').hide(); document.body.classList.remove('fixd_debug'); } }; feature.toggle = init; init(feature.setting.enabled); return feature; })(); $('body').append('<div id="fixd_launch" data-click-id="fixd_launcher"></div>'); }); })(window.jQuery.noConflict(true));
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址