您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Allows users to view MetaFilter comments by favorite count.
当前为
- // ==UserScript==
- // @name MetaFilter Filter By Favorites
- // @namespace http://namespace.kinobe.com/metafilter/
- // @description Allows users to view MetaFilter comments by favorite count.
- // @include /^https?://(www|ask|metatalk|fanfare|projects|music|irl)\.metafilter\.com/.*$/
- // @include http://mefi/*
- // @version 1.0
- // @grant GM_addStyle
- // ==/UserScript==
- /*
- This copyright section and all credits in the script must be included in modifications or redistributions of this script.
- MetaFilterFilterByFavorites is Copyright (c) 2014, Jonathan Gordon
- MetaFilterFilterByFavorites is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License
- License information is available here: http://creativecommons.org/licenses/by-sa/3.0/
- */
- /*
- This script borrows heavily from Jimmy Woods' MetaFilter favorite posts filter script
- http://userscripts.org/scripts/show/75332
- Also from Jordan Reiter's MetaFilter MultiFavorited Multiwidth - November Experiment
- http://userscripts.org/scripts/show/61012
- Please see the README.md for more info:
- https://greasyfork.org/scripts/5717-metafilter-filter-by-favorites
- Version 1.0
- - Initial Release.
- */
- var LogLevelEnum = {
- DEBUG:{value:0, name:"Debug"},
- INFO:{value:1, name:"Info"},
- WARN:{value:2, name:"Warn"},
- ERROR:{value:3, name:"Error"}
- };
- var SiteEnum = {
- WWW:{
- name:"www", titleRE:/^.+?\| MetaFilter$/, fav_prefix:"2"
- }, ASK:{
- name:"ask", titleRE:/^.+?\| Ask MetaFilter$/, fav_prefix:"4"
- }, TALK:{
- name:"talk", titleRE:/^.+?\| MetaTalk$/, fav_prefix:"6"
- }, PROJECTS:{
- name:"projects", titleRE:/^.+?\| MetaFilter Projects$/, fav_prefix:"13"
- }, MUSIC:{
- name:"music", titleRE:/^.+?\| MeFi Music$/, fav_prefix:"9"
- }, IRL:{
- name:"irl", titleRE:/^.+?\| IRL: MeFi Events$/, fav_prefix:"20"
- }, FANFARE:{
- name:"fanfare", titleRE:/^.+?\| FanFare$/, fav_prefix:"24"
- }
- };
- Global = {
- last_tr:null // Reference to the last TR tag in the select table that a user clicked on.
- , table_bg_color:"#E6E6E6" // Background color for the table rows.
- , selected_color:"#88c2d8" // BG color for the selected table row.
- , hover_color:"#DC5E04" // BG color for the selected table row.
- , favorite_color:"#ff7617" // BG color for the selected table row.
- , max_count:100 // Largest possible # of favorites
- , min_count:0 // Smallest # of favorites that are highlighted
- , posts:[] // Stores info about each post
- , max_favorites:0 // Highest favorite count so far.
- , doLog:true // Should we log messages?
- , row_prefix:"summary_id_" // Used to set the ID for each row in the comment/favorite chart
- , logLevel:LogLevelEnum.INFO // What's the default log level?
- };
- /**
- * ----------------------------------
- * Logger
- * ----------------------------------
- * Allows swapping out GM logger for console
- */
- Logger = {
- log:function (message, logLevelEnum) {
- logLevelEnum = logLevelEnum || LogLevelEnum.INFO;
- if (Global.doLog && logLevelEnum.value >= Global.logLevel.value) {
- // GM_log(message);
- console.log(message);
- }
- }, debug:function (message) {
- Logger.log(message, LogLevelEnum.DEBUG);
- }, info:function (message) {
- Logger.log(message, LogLevelEnum.INFO);
- }, warn:function (message) {
- Logger.log(message, LogLevelEnum.WARN);
- }, error:function (message) {
- Logger.log(message, LogLevelEnum.ERROR);
- }
- };
- /**
- * ----------------------------------
- * Util
- * ----------------------------------
- * Various utility functions
- */
- Util = {
- /**
- * Returns an array of DOM elements that match a given XPath expression.
- *
- * @param path string - Xpath expression to search for
- * @param from DOM Element - DOM element to search under. If not specified, document is used
- * @return Array - Array of selected nodes (if any)
- */
- getNodes:function (path, from) {
- Logger.debug("getNodes of path: " + path);
- from = from || document;
- var item, ret = [];
- var iterator = document.evaluate(path, from, null, XPathResult.ANY_TYPE, null);
- while (item = iterator.iterateNext()) {
- ret.push(item);
- // Logger.debug("Item is: "+item);
- }
- Logger.debug("Num elements found by getNodes: " + ret.length);
- return ret;
- }
- /**
- * Deletes a DOM element
- * @param DOM element - DOM element to remove
- * @return DOM element - the removed element
- */, removeElement:function (element) {
- return element.parentNode.removeChild(element);
- }
- /**
- * Binds an event handler function to an object context, so that the handler can be executed as if it
- * was called using "this.<method name>(event)", i.e. it can use "this.foo" inside it.
- *
- * @param function method - a function to execute as an event handler
- * @param Object context - the object that will be used as context for the function, as if the function had been
- * called as context.method(event);
- * @return function - the function to pass to addEventListener
- */, bindAsEventHandler:function (method, context) {
- var __method = method;
- return function (event) {
- return __method.apply(context, [event]);
- }
- }
- };
- /*
- * Event handler for when user clicks on a row
- */
- function filterPosts(evt) {
- // Find the parent <TR> tag.
- Logger.debug("filterPosts");
- var t = evt.target;
- Logger.debug("t: " + t);
- while (null == t.getAttribute("id")) {
- Logger.debug("Looking for DIV");
- t = t.parentNode;
- }
- var summary_id = t.getAttribute('id');
- Logger.debug("t.id: " + summary_id);
- var summary_row_re = /^summary_id_(\d+)$/;
- var max_cnt = (summary_row_re.exec(summary_id) !== null) ? parseInt(RegExp.$1) : 0;
- Logger.debug("Parsed max_cnt: " + max_cnt);
- // Hide/unhide all posts that don't match the chosen fav count.
- var i = Global.posts.length;
- while (i--) {
- var is_showing = (Global.posts[i].div.style.display !== "none");
- var do_show = (Global.posts[i].num_favs >= max_cnt);
- Logger.debug("is_showing: " + is_showing);
- Logger.debug("do_show: " + do_show);
- if (do_show != is_showing) {
- Logger.debug("Hiding post: " + i);
- Global.posts[i].div.style.display = (do_show ? "" : "none");
- Global.posts[i].div.nextSibling.style.display = (do_show ? "" : "none");
- Global.posts[i].div.nextSibling.nextSibling.style.display = (do_show ? "" : "none");
- }
- }
- // Reset the color of the previous row to be clicked on.
- if (Global.last_tr !== null) {
- Logger.debug("Resetting the background color.");
- removeClass(Global.last_tr, "wrapperSelected");
- }
- // Set the color of the row we just clicked on
- addClass(t, "wrapperSelected");
- Global.last_tr = t;
- }
- function addClass(obj, className) {
- if (null != obj && undefined != obj) {
- var prevClass = obj.className;
- if (null != prevClass && undefined != prevClass) {
- if (!prevClass.match(new RegExp(className))) {
- obj.className = obj.className + " " + className;
- }
- }
- }
- }
- function removeClass(obj, className) {
- if (null != obj && undefined != obj) {
- var prevClass = obj.className;
- if (null != prevClass && undefined != prevClass) {
- var regExp = new RegExp(className);
- if (prevClass.match(regExp)) {
- obj.className = obj.className.replace(regExp, '');
- }
- }
- }
- }
- // ---------------------------
- //Finds y value of given object
- function findPos(obj) {
- var current_top = 0;
- if (obj.offsetParent) {
- do {
- current_top += obj.offsetTop;
- } while (obj = obj.offsetParent);
- }
- return current_top;
- }
- function simulateClickShow(id) {
- // jquery isn't working here
- // $('#filter0').trigger('click');
- // use non-jquery method to simulate the click of the count row specified
- var evt = document.createEvent("MouseEvents");
- evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
- var show_all_link = document.getElementById(id);
- show_all_link.dispatchEvent(evt);
- }
- function getElementsByClassName(node, classname) {
- if (node.getElementsByClassName) { // use native implementation if available
- Logger.debug("Using native implementation of getElementsByClassName.");
- return node.getElementsByClassName(classname);
- } else {
- return (function getElementsByClass(searchClass, node) {
- node = node || document;
- var classElements = [], els = document.getElementsByTagName("*"), elsLen = els.length, pattern = new RegExp("(^|\\s)" + searchClass + "(\\s|$)"), i, j;
- Logger.debug("Total elements: " + els.length);
- Logger.debug("Looking for" + searchClass);
- for (i = 0, j = 0; i < elsLen; i++) {
- var elsClassName = els[i].className;
- if ("" != elsClassName) {
- // Logger.debug("Class of element: " + elsClassName);
- }
- if (pattern.test(elsClassName)) {
- classElements[j] = els[i];
- j++;
- }
- }
- return classElements;
- })(classname, node);
- }
- }
- // a function that loads jQuery and calls a callback function when jQuery has finished loading
- function addJQuery(callback) {
- var script = document.createElement("script");
- script.setAttribute("src", "http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js");
- script.addEventListener('load', function () {
- var script = document.createElement("script");
- script.textContent = "(" + callback.toString() + ")();";
- document.body.appendChild(script);
- }, false);
- document.body.appendChild(script);
- }
- function captureShowClick(e) {
- var click_target = e.target;
- while (click_target.tagName != "SPAN") {
- click_target = click_target.parentNode;
- }
- Logger.debug("e.target is: " + click_target);
- Logger.debug("e.target.id is: " + click_target.id);
- var recommended_re = /^(\d+)_(\d+)$/;
- var id = recommended_re.exec(click_target.id)[1];
- Logger.debug("ID is: " + id);
- var count = recommended_re.exec(click_target.id)[2];
- Logger.debug("Count is: " + count);
- var comment_anchor = Util.getNodes('.//a[@name="' + id + '"]')[0];
- var prevPos = findPos(comment_anchor);
- Logger.debug("prevPos: " + prevPos);
- Logger.debug("Previous window.pageYOffset: " + window.pageYOffset);
- var diff = prevPos - window.pageYOffset;
- simulateClickShow(Global.row_prefix + count);
- //Get object
- Logger.debug("Did we find SupportDiv? " + comment_anchor);
- //Scroll to location of SupportDiv on load
- var newPos = findPos(comment_anchor);
- Logger.debug("newPos: " + newPos);
- Logger.debug("Current window.pageYOffset (before scrolling): " + window.pageYOffset);
- window.scroll(0, newPos - diff);
- Logger.debug("Current window.pageYOffset (after scrolling): " + window.pageYOffset);
- // simulateClickShow(id);
- return false;
- }
- function getSite() {
- // Which subsite are we on?
- var title = document.title;
- Logger.debug("document.title: >" + title + "<");
- for (var propertyName in SiteEnum) {
- // propertyName is what you want
- if (SiteEnum[propertyName].titleRE.test(title)) {
- return SiteEnum[propertyName];
- }
- }
- return null;
- }
- //check if the previous sibling node is an element node
- function getPreviousElement(n) {
- var x = n.previousSibling;
- while (null != x && x.nodeType != 1) {
- x = x.previousSibling;
- Logger.debug("Previous sibling?: " + typeof x);
- Logger.debug("Previous sibling: " + x);
- }
- return x;
- }
- function init() {
- Logger.info("Loading MetaFilterFilterByFavorites...");
- // if we can't find comments, it's probably this is being called for a page we haven't excluded
- if (undefined == document.getElementById("posts")) {
- Logger.info("MetaFilterFilterByFavorites can not find top node. Exiting.");
- return;
- }
- Logger.debug("MetaFilterFilterByFavorites found top node. Continuing...");
- var site = getSite();
- if (null == site) {
- Logger.error("MetaFilterFilterByFavorites can not determine site. Exiting...");
- return;
- }
- Logger.debug("site: " + site.name);
- // Prepare array for storing counts of how many posts have been favorited this many times.
- var counts = [];
- for (var j = 0; j <= Global.max_count; j++) {
- counts[j] = 0;
- }
- // some useful regexes for parsing ids and such
- var numeric_re = /^(\d+)$/, favorites_re = /^(\d+)\sfavorite[s]?$/;
- // Get all comments and compile them into arrays
- var commentDivs = Util.getNodes('.//div[@id="posts"]//div[contains(concat(" ", normalize-space(@class), " "), " comments ")]');
- Logger.debug("Num comments found: " + commentDivs.length);
- // if there are no comments, don't show table
- if (0 == commentDivs.length) {
- Logger.info("MetaFilterFilterByFavorites can not find comments. Exiting.");
- return;
- }
- for (var i = 0; i < commentDivs.length; i++) {
- Logger.debug("MetaFilterFilterByFavorites found comment div. Continuing...");
- var comment_div = commentDivs[i];
- Logger.debug("Found comment_div: " + comment_div.textContent);
- var sibling_a = getPreviousElement(comment_div);
- // if the comment doesn't have a previous sibling, we're not interested
- if (null == sibling_a) {
- continue;
- }
- Logger.debug("sibling_a: " + typeof sibling_a);
- Logger.debug("sibling_a.name: " + sibling_a.name);
- var comment_div_id = sibling_a.name;
- // Logger.debug("Id is: " + comment_div_id);
- Logger.debug("comment_div_id: " + comment_div_id);
- if (comment_div_id !== undefined && numeric_re.test(comment_div_id)) {
- Logger.debug("Found a valid id: " + comment_div_id);
- var fav_count_a = Util.getNodes('.//span[@id="favcnt' + site.fav_prefix + comment_div_id + '"]/a')[0];
- Logger.debug("fav_count_a: " + fav_count_a);
- Logger.debug("typeof fav_count_a: " + typeof fav_count_a);
- var recommended_text = undefined !== fav_count_a ? fav_count_a.textContent : "0 favorites";
- var favorite_count = (favorites_re.exec(recommended_text) !== null) ? Math.min(parseInt(RegExp.$1), Global.max_count) : 0;
- Logger.debug("favorite_count: " + favorite_count);
- counts[favorite_count]++;
- Logger.debug("Done pushing recommended_count: " + favorite_count);
- // we only highlight if there's a fav count over the minimum
- if (favorite_count > Global.min_count) {
- Logger.debug("recommended_count > " + Global.min_count + ": " + favorite_count);
- var recommendedWidthSize = (Math.round(favorite_count / 2) + 1);
- comment_div.style.borderLeft = '' + recommendedWidthSize + 'px solid ' + Global.favorite_color;
- comment_div.style.borderTop = '0px';
- comment_div.style.borderBottom = '0px';
- comment_div.style.paddingLeft = '5px';
- }
- Global.max_favorites = Math.max(favorite_count, Global.max_favorites);
- Logger.debug("Calculating max_favorites:" + Global.max_favorites);
- Global.posts.push({
- div:comment_div, num_favs:favorite_count
- });
- Logger.debug("Calculated max_favorites:" + favorite_count);
- var id_text = comment_div_id + "_" + favorite_count;
- Logger.debug("id_text" + id_text);
- var all_id_text = comment_div_id + "_0";
- var show_all_span = document.createElement('span');
- show_all_span.className = "click_count";
- show_all_span.id = all_id_text;
- var show_count_span = document.createElement('span');
- show_count_span.className = "click_count";
- show_count_span.id = id_text;
- show_all_span.innerHTML = " <a>Show: all</a>";
- show_count_span.innerHTML = " <a> / " + favorite_count + " and above</a>";
- var show_more_span = document.createElement('span');
- show_more_span.innerHTML = " <a href='#posts'> / More options</a>";
- var flag_div = Util.getNodes('.//span[@id="flag' + site.fav_prefix + comment_div_id + '"]', comment_div)[0];
- Logger.debug("Inserting show all");
- flag_div.parentNode.insertBefore(show_all_span, flag_div);
- if (favorite_count > Global.min_count) {
- Logger.debug("Inserting show count");
- flag_div.parentNode.insertBefore(show_count_span, flag_div);
- }
- Logger.debug("Inserting show more options");
- flag_div.parentNode.insertBefore(show_more_span, flag_div);
- }
- }
- Logger.debug("Done looping through comments!");
- GM_addStyle('#posts { margin-bottom: 1em; }');
- GM_addStyle('.chart {'
- + 'background-color: ' + Global.table_bg_color + ';'
- + 'font: 14px sans-serif;'
- + 'margin: 0px 4px;'
- + 'color: black;'
- + 'border:1px solid white;'
- + 'border-collapse:collapse;'
- + '}');
- GM_addStyle('.comms {'
- + 'margin-left: 1em;'
- + 'float: left;'
- + 'width: 5%;'
- + '}');
- GM_addStyle('.favs {'
- + 'float: left;'
- + 'background-color: ' + Global.favorite_color + ';'
- + 'margin-right: 4px;'
- + 'text-align: center;'
- + '}');
- GM_addStyle('.wrapper {'
- + 'display: block;'
- + 'padding: 3px 0px;'
- + '}');
- GM_addStyle('.wrapperSelected {'
- + 'background-color: ' + Global.selected_color + ';'
- + '}');
- GM_addStyle('.wrapper:hover {'
- + 'background-color: ' + Global.hover_color + ';'
- + '}');
- GM_addStyle('.clearfix:after {'
- + 'content: ".";'
- + 'display: block;'
- + 'height: 0;'
- + 'clear: both;'
- + 'visibility: hidden;'
- + '}');
- Logger.debug("Done adding style.");
- initTable(counts);
- document.addEventListener('keydown', function (e) {
- // pressed alt+g
- if (e.keyCode == 71 && !e.shiftKey && !e.ctrlKey && e.altKey && !e.metaKey) {
- simulateClickShow(Global.row_prefix + 0);
- }
- }, false);
- var allClickClasses = getElementsByClassName(document, "click_count");
- Logger.debug("allClickClasses count: " + allClickClasses.length);
- for (var k = 0; k < allClickClasses.length; k++) {
- var n = allClickClasses[k];
- Logger.debug("n is: " + n);
- Logger.debug("n.target is: " + n.target);
- n.addEventListener('click', captureShowClick, false);
- }
- Logger.info("Loading MetaFilterFilterByFavorites is complete.");
- }
- /**
- * Generates the table at the top of the page
- * @param counts - Array of post counts, from 0 to Global.max_total. [fav_count => # of posts]
- * @return void
- */
- function initTable(counts) {
- Logger.debug("Total counts: " + counts);
- var dummyDiv = document.createElement('div');
- var data_rows_html = '<div class="chart" style="width: 70%;">';
- var m = Global.max_count + 1, cum_comment_total = 0;
- // Generate the table rows
- while (m-- >= 0) {
- // we only show differences where the comment count has increased, or the very last row, showing all
- if (counts[m] > 0 || m == 0) {
- cum_comment_total += counts[m];
- var recommendedWidthSize = (Math.round((m / Global.max_favorites) * 90));
- data_rows_html += '<div id="' + Global.row_prefix + m + '" class="wrapper clearfix"><div class="comms">' + cum_comment_total + '</div>'
- + '<div class="favs" style="width: ' + recommendedWidthSize + '%;">(' + ((m == 0) ? "All" : m) + ')</div>'
- + '</div>';
- }
- }
- // Insert table into page
- Logger.debug("data_rows_html: " + data_rows_html);
- dummyDiv.innerHTML = '<div>'
- + '<div id="MultiFavoritesOptions" class="clearfix" style="white-space:nowrap; padding: 3px 0;">Show me this many comments (with at least this many favorites)</div>'
- + data_rows_html
- + '</div>';
- var page_div = document.getElementById("posts");
- page_div.insertBefore(dummyDiv.firstChild, page_div.firstChild);
- // Add the event listeners.
- var rows = Util.getNodes('.//div[@class="wrapper clearfix"]');
- var n = rows.length;
- Logger.debug("Found rows: " + n);
- while (n--) {
- Logger.debug("addEventListener");
- rows[n].addEventListener('click', filterPosts, false);
- }
- }
- init();