HowlsOfOutrage

show MetaFilter favoriters (favoritors?) on hover

当前为 2014-10-04 提交的版本,查看 最新版本

  1. // HowlsOfOutrage.user.js
  2. //
  3. // Copyright 2010-2014, Michael Devore
  4. // This file is licensed under the terms of the Artistic License 2.0.
  5. // See http://opensource.org/licenses/Artistic-2.0 for the license itself.
  6. //
  7. // This is a Greasemonkey script.
  8. // See http://www.greasespot.net/ for more information on Greasemonkey.
  9. //
  10. // ==UserScript==
  11. // @name HowlsOfOutrage
  12. // @namespace http://www.devoresoftware.com/gm/hoo
  13. // @description show MetaFilter favoriters (favoritors?) on hover
  14. // @include http://*.metafilter.com/*
  15. // @include https://*.metafilter.com/*
  16. // @version 4.6
  17. // ==/UserScript==
  18. //
  19. // version 4.6: post favorite fix
  20. // version 4.5: fix for MetaFilter redesign, add https support
  21. // version 4.2: fixes a problem that Greasemonkey 2.1 has with GM_xmlhttpRequest and Windows
  22. // version 4.1: minor fixes, works for nonregistered users
  23. // version 4: fixes breakage, made popup positioning more intelligent, minor layout changes
  24. // version 3: adds loading message for when response is slow,
  25. // minimum delay between favorites popup displays, cosmetic stuff
  26. // version 2: adds explicit text color for white background, variable-height box,
  27. // hover over post favorites for list in addition to existing comment support
  28.  
  29. "use strict";
  30.  
  31. var favPopUp;
  32. var favWork;
  33. var mfBackgroundColor = "#006699"; // MetaFilter blue as background color
  34. var favLoadDelay = 1; // minimum seconds between subsequent displays of favorite lists
  35. var waitForDelay = false;
  36. var waitForHover = false;
  37. var currentEvent;
  38.  
  39. var popupWidth = 400;
  40.  
  41. function howlMain()
  42. {
  43. favWork = document.createElement("div");
  44. favWork.id = 'howlDiv';
  45. favWork.style.display = "none";
  46. document.getElementsByTagName('body')[0].appendChild(favWork);
  47.  
  48. favPopUp = document.createElement("div");
  49. favPopUp.style.width = popupWidth+"px";
  50. favPopUp.style.height = "auto";
  51. favPopUp.style.top = "0px";
  52. favPopUp.style.left = "0px";
  53. favPopUp.style.overflow = "hidden";
  54. favPopUp.style.color = "white";
  55. favPopUp.style.paddingLeft = "8px";
  56. favPopUp.style.paddingRight = "8px";
  57. favPopUp.style.paddingTop = "8px";
  58. favPopUp.style.paddingBottom = "8px";
  59. favPopUp.style.fontSize = "12pt";
  60. favPopUp.style.backgroundColor = mfBackgroundColor;
  61. favPopUp.style.borderStyle = "dashed";
  62. favPopUp.style.borderWidth = "medium";
  63. favPopUp.style.borderColor = "black";
  64. favPopUp.style.position = "absolute";
  65. favPopUp.style.display = "none";
  66. favPopUp.style.opacity = ".94";
  67. document.getElementsByTagName('body')[0].appendChild(favPopUp);
  68.  
  69. var xpath = "//DIV/SPAN[starts-with(text(),'posted by') and @class='smallcopy']/SPAN[starts-with(@id,'fav')]/SPAN[starts-with(@id,'favcnt')]/A";
  70. var postNodes = document.evaluate(
  71. xpath,
  72. document,
  73. null,
  74. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  75. null
  76. );
  77.  
  78. if (!postNodes.snapshotLength)
  79. {
  80. xpath = "//DIV[@class='comments']/SPAN[starts-with(text(),'posted by') and @class='smallcopy']/A[starts-with(@href, '/favorited/')]";
  81. postNodes = document.evaluate(
  82. xpath,
  83. document,
  84. null,
  85. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  86. null
  87. );
  88. }
  89.  
  90. var totalFavAnchors = postNodes.snapshotLength;
  91.  
  92. for (var i = 0; i < totalFavAnchors; i++)
  93. {
  94. // this is for comments
  95. var favNode = postNodes.snapshotItem(i);
  96. favNode = favNode.parentNode;
  97. favNode.addEventListener('mouseover', checkFavHover, false);
  98. favNode.addEventListener('mouseout', favGoAway, false);
  99. }
  100.  
  101. // pick up post favorites
  102. xpath = "//DIV[@class='copy']/SPAN[starts-with(text(),'posted by') and (@class='smallcopy' or @class='smallcopy postbyline')]/SPAN[starts-with(@id,'favcnt')]/A";
  103. postNodes = document.evaluate(
  104. xpath,
  105. document,
  106. null,
  107. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  108. null
  109. );
  110. if (postNodes.snapshotLength)
  111. {
  112. // this is for the original post
  113. var favNode = postNodes.snapshotItem(0);
  114. var newSpan = document.createElement("span");
  115. favNode.parentNode.parentNode.insertBefore(newSpan, favNode.parentNode.nextSibling);
  116. newSpan.appendChild(favNode.parentNode);
  117.  
  118. // favNode.addEventListener('mouseover', checkFavHover, false);
  119. // favNode.addEventListener('mouseout', favGoAway, false);
  120. newSpan.addEventListener('mouseover', checkFavHover, false);
  121. newSpan.addEventListener('mouseout', favGoAway, false);
  122. }
  123. }
  124.  
  125. function favGoAway(evt)
  126. {
  127. waitForHover = false;
  128. favPopUp.style.display = "none";
  129. }
  130.  
  131. function checkFavHover(evt)
  132. {
  133. currentEvent = evt;
  134. if (waitForDelay)
  135. {
  136. waitForHover = true;
  137. return;
  138. }
  139. waitForHover = false;
  140. favHover(evt);
  141. }
  142.  
  143. function timesUp()
  144. {
  145. waitForDelay = false;
  146. if (waitForHover)
  147. {
  148. waitForHover = false;
  149. favHover(currentEvent);
  150. }
  151. }
  152.  
  153. function favHover(evt)
  154. {
  155. waitForDelay = true;
  156. window.setTimeout(timesUp, favLoadDelay * 1000);
  157.  
  158. favPopUp.style.color = "#CCCC00";
  159. favPopUp.style.backgroundColor = "#888888";
  160. favPopUp.innerHTML = "&nbsp;...loading...";
  161.  
  162. favPopUp.style.top = (evt.pageY - 4)+"px";
  163. favPopUp.style.left = (evt.pageX + 20)+"px";
  164.  
  165. var divX = parseInt(favPopUp.style.left);
  166. if (window.innerWidth <= divX + popupWidth+25)
  167. {
  168. var left = parseInt(window.innerWidth-popupWidth-25);
  169. favPopUp.style.left = left+"px";
  170. }
  171.  
  172. favPopUp.style.display = "";
  173. var favURL = evt['target']['href'];
  174. evt['target']['title'] = "";
  175.  
  176. /*
  177. // cross-domain request, have to use GM_xmlhttpRequest()
  178. GM_xmlhttpRequest(
  179. {
  180. method : "GET",
  181. url : favURL,
  182. headers :
  183. {
  184. // "User-Agent" : "Mozilla/5.0",
  185. "Accept" : "text/xml"
  186.  
  187. },
  188. onload:function(response)
  189. {
  190. processFav(response.responseText);
  191. }
  192. });
  193. }
  194. */
  195.  
  196. var request = new XMLHttpRequest();
  197. request.open("GET", favURL, true);
  198. // request.setRequestHeader("User-Agent", "Mozilla/5.0");
  199. request.setRequestHeader("Accept", "text/xml");
  200. try
  201. {
  202. request.addEventListener("readystatechange", favCheckPageReady, false);
  203. }
  204. catch (e)
  205. {
  206. if (e.message.indexOf("NS_ERROR_ILLEGAL_VALUE" >= 0))
  207. {
  208. // readystatechange event not unsupported for this version browser
  209. request.onreadystatechange = function() {
  210. if (request.readyState == 4 && request.status == 200)
  211. {
  212. if (request.responseText)
  213. {
  214. processFav(request.responseText);
  215. }
  216. }
  217. };
  218. }
  219. else
  220. {
  221. alert("broken script.");
  222. }
  223. }
  224. request.send(null);
  225. }
  226.  
  227. function favCheckPageReady()
  228. {
  229. if (this.readyState == 4 && this.status == 200)
  230. {
  231. if (this.responseText)
  232. {
  233. processFav(this.responseText);
  234. }
  235. }
  236. }
  237.  
  238. function processFav(pText)
  239. {
  240. // console.log(pText);
  241. favWork.innerHTML = pText;
  242. // var xpath = "/DIV[@class='copy']/A";
  243. var xpath = "//DIV[@id='howlDiv']//DIV[@class='copy']/A";
  244. var userNodes = document.evaluate(
  245. xpath,
  246. favWork,
  247. null,
  248. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  249. null
  250. );
  251. var totalUserAnchors = userNodes.snapshotLength;
  252. favPopUp.innerHTML = "";
  253.  
  254. favPopUp.style.backgroundColor = mfBackgroundColor;
  255. for (var i = 0; i < totalUserAnchors; i++)
  256. {
  257. var currentNode = userNodes.snapshotItem(i);
  258. var aNode = currentNode.cloneNode(true);
  259. aNode.style.color = "white";
  260. if (i)
  261. {
  262. favPopUp.appendChild(document.createTextNode(", "));
  263. }
  264. favPopUp.appendChild(aNode);
  265. }
  266.  
  267. var divHeight = parseInt(favPopUp.offsetHeight);
  268. var divY = parseInt(favPopUp.offsetTop);
  269. var divBottom = (divY-window.pageYOffset) + divHeight;
  270.  
  271. if (window.innerHeight <= divBottom)
  272. {
  273. var theTop = parseInt(favPopUp.style.top);
  274. theTop -= divHeight;
  275. favPopUp.style.top = theTop+"px";
  276. }
  277.  
  278. var divX = parseInt(favPopUp.style.left);
  279. if (window.innerWidth <= divX + popupWidth+25)
  280. {
  281. var left = parseInt(window.innerWidth-popupWidth-25);
  282. favPopUp.style.left = left+"px";
  283. }
  284.  
  285. }
  286.  
  287. howlMain();