Post Toast for Lithium comunities

preview forum posts, etc. on Lithium based communities

  1. // ==UserScript==
  2. // @name Post Toast for Lithium comunities
  3. // @description preview forum posts, etc. on Lithium based communities
  4. // @namespace http://sites.google.com/site/cerisesorbet/
  5. // @description preview forum posts, etc. on Lithium based communities
  6. // @include http://lithosphere.lithium.com/*
  7. // @include http://boards.adultswim.com/*
  8. // @include http://forums.verizon.com/*
  9. // @include http://bookclubs.barnesandnoble.com/*
  10. // @include http://community.secondlife.com/*
  11. // @include https://community.secondlife.com/*
  12. // @exclude http://forums.comcast.com/*
  13. // @version 20150312
  14. // @license MIT License
  15. // @copyright (c) 2012-2015 Cerise Sorbet
  16. // @grant none
  17.  
  18. // ==/UserScript==
  19.  
  20. /*
  21. Notes:
  22. - Not usable on Comcast forums, they do not allow XML message reading
  23. - Verizon already has previews on some links, these will display in addition
  24. */
  25.  
  26. /*
  27. Changes:
  28.  
  29. 2014-07-21 - data for greatest.deepsurf.us
  30.  
  31. 2012-01-14 - Previews for contest entries
  32.  
  33. 2011-05-07 - Answers now expands automatically, so remove old tweak
  34.  
  35. 2011-04-08 - Try to automatically expand comments on Answers thread pages
  36.  
  37. 2011-04-04 - cosmetics
  38.  
  39. 2011-03-31 - More flexible URL parsing, more error reporting,
  40. appearance tweaks, keep previews off some menus and
  41. kudos, regen KB popups after filter changes; 2nd:
  42. extend to search results
  43. */
  44.  
  45. function sendClick(element) {
  46. var ev = document.createEvent('MouseEvents');
  47. ev.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  48. element.dispatchEvent(ev);
  49. }
  50.  
  51. String.prototype.escapeHTML = function () {
  52. return this.replace(/&/g,'&amp;').replace(/>/g,'&gt;').replace(/</g,'&lt;').replace(/\"/g,'&quot;');
  53. };
  54.  
  55. function SetupPop(node) {
  56. var links = node.getElementsByTagName('a');
  57. var x = 0;
  58. for (var i = 0; i < links.length; i++) {
  59. if (!~links[i].id.indexOf("dropDownLink") && !~links[i].id.indexOf("showAddTag")) {
  60. var postCommand = String(links[i].pathname).match(/\/(?:qaq-p|td-p|m-p|ba-p|bc-p|ta-p|idi-p|idc-p|cnc-p|cns-p)\/(?:\d+)/);
  61. if(postCommand) {
  62. x++;
  63. links[i].addEventListener('mouseover', PopTimer, true);
  64. links[i].addEventListener('mouseout', KillPop, true);
  65. links[i].addEventListener('click', KillPop, true);
  66. }
  67. }
  68. }
  69. }
  70.  
  71. function PopTimer(e) {
  72. var tag = document.createElement('a');
  73. var postCommand = String(this.pathname).match(/\/(?:qaq-p|td-p|m-p|ba-p|bc-p|ta-p|idi-p|idc-p|cnc-p|cns-p)\/(?:\d+)/);
  74. if (postCommand) {
  75. var postNumber = postCommand[0].split('/').pop();
  76. tag.href = this.href;
  77. tag.search = tag.hash = "";
  78. tag.pathname ="/restapi/vc/messages/id/" + postNumber + '?xslt=json.xsl';
  79.  
  80. gPopTimeout = window.setTimeout(function(){MakePop(e, tag);}, 850);
  81. }
  82. }
  83.  
  84. function KillPop() {
  85. window.clearTimeout(gPopTimeout);
  86. if (gPopXML) gPopXML.abort();
  87. gHoverTip.style.display = 'none';
  88. }
  89.  
  90. function MakePop(ev, tag) {
  91. var divWidth = 420;
  92. var divHeight = 250;
  93. var divTop = window.pageYOffset + ev.clientY;
  94. var divLeft = window.pageXOffset + ev.clientX;
  95.  
  96. gHoverTip.class = '';
  97. gHoverTip.style.fontFamily = 'Helvetica,Arial,sans-serif';
  98. gHoverTip.style.fontSize = '10pt';
  99. gHoverTip.style.lineHeight = '1.15';
  100. gHoverTip.style.position = 'absolute';
  101. gHoverTip.style.zIndex = '9998';
  102. gHoverTip.style.backgroundColor = '#FFFFE8';
  103. gHoverTip.style.borderTop = gHoverTip.style.borderLeft = '1px solid #BFBFBF';
  104. gHoverTip.style.borderBottom = gHoverTip.style.borderRight = '2px solid #7F7F7F';
  105. gHoverTip.style.width = divWidth + 'px';
  106. gHoverTip.style.height = divHeight + 'px';
  107. gHoverTip.style.overflow = 'hidden';
  108. gHoverTip.style.textAlign = 'center';
  109. gHoverTip.style.padding = '5px';
  110.  
  111. divLeft = (ev.clientX > window.innerWidth / 2) ? divLeft - divWidth - 10 : divLeft + 10;
  112. gHoverTip.style.left = divLeft + 'px';
  113.  
  114. divTop = (ev.clientY > window.innerHeight / 2) ? divTop - divHeight - 20 : divTop + 20;
  115. gHoverTip.style.top = divTop + 'px';
  116.  
  117. gHoverTip.innerHTML='<strong>Loading ' + tag.pathname + ' ...</strong>';
  118. gHoverTip.style.display = 'block';
  119.  
  120. gPopXML = new XMLHttpRequest();
  121. gPopXML.open('GET', tag.href, true);
  122. gPopXML.onreadystatechange = function(e) {
  123. if (gPopXML.readyState == 4) {
  124. if(gPopXML.status != 200) { // HTTP error
  125. gHoverTip.innerHTML = '<b>HTTP Error ' + gPopXML.status + '</b><br/>';
  126. gHoverTip.innerHTML += '<p>I has a sad.</p>';
  127. }
  128. else { // yay!
  129. gHoverTip.innerHTML = "Success?";
  130. gHoverTip.style.textAlign = '';
  131.  
  132. try {
  133. var article = JSON.parse(gPopXML.responseText);
  134. }
  135. catch(err) {
  136. gHoverTip.innerHTML = '<b>Eek! invalid JSON, something is broken.</b><br />' + String(err.message);
  137. return;
  138. }
  139.  
  140. if (!article.response.status) { // all responses should have this
  141. gHoverTip.innerHTML = '<b>Eek! No response status, something is broken.</b>';
  142. }
  143. else if (article.response.status != "success") { // internal Lithium error, like no permission or deleted message
  144. gHoverTip.innerHTML = '<b>Arrgh, error when fetching the message :(</b>';
  145.  
  146. if (article.response.error.code)
  147. gHoverTip.innerHTML += '<p><b>Error code:</b> ' + article.response.error.code + '</p>';
  148.  
  149. if (article.response.error.message)
  150. gHoverTip.innerHTML += '<p><b>Error text: </b> ' + article.response.error.message + '</p>';
  151. }
  152.  
  153. else { // must be a success...
  154. var message = article.response.message;
  155. gHoverTip.innerHTML = message.subject.$ ? '<strong>' + message.subject.$ + '</strong> ' : '<strong>(no subject)</strong> ';
  156. gHoverTip.innerHTML += message.author.login.$ ? '(' + message.author.login.$ + ')' : '(no author)';
  157. gHoverTip.innerHTML += '<div style="border-top:1px solid #000; padding:2px 0 2px 0"></div>';
  158. gHoverTip.innerHTML += message.body.$ ? message.body.$ : '<p><i>[ no body ]</i></p>';
  159.  
  160. var i;
  161.  
  162. tags = gHoverTip.getElementsByTagName('font');
  163. if (tags) {
  164. for (i = 0; i < tags.length; i++)
  165. tags[i].color = tags[i].face = tags[i].size = '';
  166. }
  167.  
  168. tags = gHoverTip.getElementsByTagName('p');
  169. if (tags) {
  170. for (i = 0; i < tags.length; i++) {
  171. if (tags[i].innerHTML.match(/^(&nbsp;|\s)*$/))
  172. tags[i].style.lineHeight = '0.3';
  173. else
  174. gHoverTip.style.lineHeight = '1.15';
  175. tags[i].style.margin = tags[i].style.padding = '0';
  176. }
  177. }
  178.  
  179. // squish blockquotes
  180. var tags = gHoverTip.getElementsByTagName('blockquote');
  181. if (tags) {
  182. for (i = 0; i < tags.length; i++) {
  183. tags[i].style.maxHeight = '4em';
  184. tags[i].style.overflow = 'hidden';
  185. tags[i].style.fontSize = '90%';
  186. tags[i].style.margin = tags[i].style.padding = '0 0 0 3px';
  187. tags[i].style.borderLeft = '1px dotted #555';
  188. tags[i].style.color='#000';
  189. tags[i].style.fontStyle='normal';
  190. }
  191. }
  192.  
  193. tags = gHoverTip.getElementsByTagName('hr');
  194. if (tags) { for (i = 0; i < tags.length; i++) tags[i].style.display = 'none'; }
  195.  
  196. tags = gHoverTip.getElementsByTagName('img');
  197. if (tags) { for (i = 0; i < tags.length; i++) tags[i].style.maxWidth ='100%'; }
  198.  
  199. tags = gHoverTip.getElementsByTagName('object');
  200. if (tags) { for (i = 0; i < tags.length; i++) tags[i].style.maxWidth ='100%'; }
  201. }
  202. }
  203. }
  204. gHoverTip.innerHTML = gHoverTip.innerHTML;
  205. };
  206.  
  207. gPopXML.send(null);
  208. }
  209.  
  210.  
  211. // On */t5/forums/recentpostspage/* displays for q&a/answers, the
  212. // links in both the subject and last post show only the question and
  213. // that one response. Change it so that the "Subject" link shows the
  214. // whole thread focused on the comment. The "Last Post" link is left
  215. // untouched, in case somebody wants the original isolated view.
  216. function FixRecentAnswersSubjects() {
  217. if (~gWindowPathParts.indexOf('recentpostspage')) {
  218. var subjects = document.getElementsByClassName('message-subject');
  219. for (var i = 0; i < subjects.length; i++) {
  220. var anchor = subjects[i].getElementsByTagName('a')[0];
  221. if (anchor)
  222. anchor.href = anchor.href.replace(/\/comment-id\/(\d*)?/, "");
  223. }
  224. }
  225. }
  226.  
  227. function ResetPop() {
  228. SetupPop(document);
  229. if (gEvents['KBFilter'] == 0) {
  230. this.addEventListener('DOMSubtreeModified', KBFilter, false);
  231. gEvents['KBFilter'] = 1;
  232. }
  233. }
  234.  
  235. function KBFilter() {
  236. this.removeEventListener('DOMSubtreeModified', KBFilter, false);
  237. gEvents['KBFilter'] = 0;
  238. window.setTimeout(ResetPop, 500);
  239. }
  240.  
  241.  
  242. if (document.body) {
  243.  
  244. // article preview div
  245. gHoverTip = document.createElement('div');
  246. gHoverTip.style.className = 'lia-menu-navigation';
  247. gHoverTip.style.display = 'none';
  248. document.body.appendChild(gHoverTip);
  249.  
  250. // event listener scoreboard
  251. gEvents = {};
  252. gPopTimeout = null;
  253. gPopXML = null;
  254. SetupPop(document);
  255.  
  256. gWindowPathParts = window.location.pathname.replace(/^\//, "").split('/');
  257.  
  258. FixRecentAnswersSubjects();
  259.  
  260. // On dynamic AJAXy pages, event handlers need a refresh when results change.
  261. var searchListings = document.getElementsByClassName('lia-summary-view-message-list'); // KB filter page
  262. var searchList = null;
  263. if (searchListings.length)
  264. searchList = searchListings[0];
  265. if (!searchListings.length) {
  266. searchListings = document.getElementsByClassName('thread-search-results-list'); // search results
  267. if (searchListings.length)
  268. searchList = searchListings[0].parentNode;
  269. }
  270. if (searchList) {
  271. gEvents['KBFilter'] = 1;
  272. searchList.addEventListener('DOMSubtreeModified', KBFilter, false);
  273. }
  274. }