UserNotes

adds ajaxy viewing of user notes on Metafilter whenever a username is shown

  1. // ==UserScript==
  2.  
  3. // @name UserNotes
  4. // @namespace jacalata
  5. // @description adds ajaxy viewing of user notes on Metafilter whenever a username is shown
  6. // @include *.metafilter.com/*
  7. // @version 3.1 (firefox 8.0 support)
  8. // 2.0 (optional marking added to usernames sitewide to indicate notes
  9. // 1.5 (pressing enter in the text input box now submits the new note)
  10. // 1.4 (fixed buttons into text-like pieces of ajaxy goodness)
  11. // 1.3 (fixed bug that was not saving the first note on each page)
  12. // 1.2 (added autoclear of textbox on focus)
  13. // 1.1 (changed to handle multiple notes with individual deletion)
  14. // ==/UserScript==
  15.  
  16.  
  17. // chrome doesn't support GM_getValue, using replacement localStorage
  18. // this code borrowed from http://devign.me/greasemonkey-gm_getvaluegm_setvalue-functions-for-google-chrome/
  19. if (!this.GM_getValue || (this.GM_getValue.toString && this.GM_getValue.toString().indexOf("not supported")>-1)) {
  20. this.GM_getValue=function (key,def) {
  21. return localStorage[key] || def;
  22. };
  23. this.GM_setValue=function (key,value) {
  24. return localStorage[key]=value;
  25. };
  26. this.GM_deleteValue=function (key) {
  27. return delete localStorage[key];
  28. };
  29. }
  30.  
  31. var currentUserID = "User"+location.href.substring(31);
  32. numUserNotes = GM_getValue(currentUserID, 0);
  33. var isAjaxOn = "ajaxSetting";
  34. var ajaxSetting = GM_getValue(isAjaxOn, false);
  35. var currentBalloon;
  36.  
  37.  
  38. // adding a button to turn on/off the sitewide notes
  39. // (off means they are only visible on the profiles, no markings around the rest of the site)
  40. ajaxButton = createButton("ajaxButton", "ajaxButton", "##", "sitewide notes off", "_self", toggleAjax);
  41. //create an invisible div that will show the notes in a popup
  42. // this is from http://blog.kung-foo.tv/archives/001614.html, with the style stuff based on the bookburro extension code
  43. var notesBox = document.createElement('div');
  44. notesBox.setAttribute("id", "balloon");
  45. notesBox.setAttribute("style", "background-color:#778899;text-align:left;");
  46. notesBox.style.position = "absolute";
  47. notesBox.style.border = "1px solid navy";
  48. notesBox.style.margin = "10";
  49. notesBox.style.zIndex = "99";
  50. notesBox.style.font = "8pt sans-serif";
  51. notesBox.style.overflow = "hidden";
  52. notesBox.style.opacity = "85";
  53. notesBox.style.padding = "4px";
  54. notesBox.style.display = "none";
  55. var searchPattern = "//div[@class='mefimessages']";
  56. var options = document.evaluate( searchPattern, document, null,
  57. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  58. var i;
  59. for (var targetClass = null, i=0; (targetClass = options.snapshotItem(i)); i++)
  60. {
  61. targetClass.innerHTML += " | ";
  62. targetClass.appendChild(ajaxButton);
  63. targetClass.appendChild(notesBox);
  64. }
  65.  
  66. // if this is a profile page, add all the note-taking elements
  67. if (location.href.match("metafilter.com/user") )
  68. {
  69. takeNotes();
  70. }
  71. if (ajaxSetting == true)
  72. {
  73. ajaxButton.innerHTML = ajaxButton.innerHTML.replace("off", "on");
  74. showLinkedNotes();
  75. }
  76. // called on loading a page with ajaxNotes set, or when turning on the ajax notes.
  77. // searches for links to user profiles, and makes a mark next to those you have given notes
  78. function showLinkedNotes()
  79. {
  80. // find every spot where a user profile is linked
  81. var profileSearch = "//a[contains(@href, '/user/') and not(contains(./text(), 'My')) and not(contains(@href, 'rss' ))]";
  82. var profiles = document.evaluate( profileSearch, document, null,
  83. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  84. var j;
  85. for (var targetClass = null, j=0; (targetClass = profiles.snapshotItem(j)); j++)
  86. {
  87. var thisUserID = "User"+targetClass.href.substring(31);
  88. var thisUserNotes = GM_getValue(thisUserID, 0);
  89. if (thisUserNotes > 0)
  90. {
  91. targetClass.innerHTML += " <i>i</i> ";
  92. targetClass.addEventListener("mouseover", viewNotes, false);
  93. targetClass.addEventListener("click", hideBalloon, false);
  94. }
  95. }
  96. } // function showLinkedNotes
  97.  
  98.  
  99. // the user has just turned off the ajax notes feature - hide the little 'i' icons (ie; reverse what showLinkedNotes did)
  100. function removeLinkedNotes()
  101. {
  102. hideBalloon(); //just in case it is currently displayed
  103. // find every spot where a user profile is linked
  104. var profileSearch = "//a[contains(@href, '/user/') and not(contains(./text(), 'My')) and not(contains(@href, 'rss' ))]";
  105. var profiles = document.evaluate( profileSearch, document, null,
  106. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  107. var j;
  108. for (var targetClass = null, j=0; (targetClass = profiles.snapshotItem(j)); j++)
  109. {
  110. var thisUserID = "User"+targetClass.href.substring(31);
  111. var thisUserNotes = GM_getValue(thisUserID, 0);
  112. if (thisUserNotes > 0)
  113. {
  114. targetClass.innerHTML = targetClass.innerHTML.replace(" <i>i</i> ", "");
  115. targetClass.removeEventListener("mouseover", viewNotes, false);
  116. }
  117. }
  118.  
  119.  
  120. }//funtion removeLinkedNotes
  121.  
  122. // displays the info balloon, containing the notes for the user whose profile you mousedover
  123. // from http://blog.kung-foo.tv/archives/001614.html
  124. function viewNotes()
  125. {
  126.  
  127. var thisUserID = "User"+this.href.substring(31);
  128. if ( thisUserID == currentBalloon )
  129. {
  130. return false;
  131. }
  132. currentBalloon = thisUserID; // global variable to track which info balloon is currently displayed
  133. var objX = findPosX(this);
  134. var objY = findPosY(this);
  135. var balloon = document.getElementById("balloon");
  136. if ( balloon && balloon.childNodes )
  137. {
  138. while ( balloon.childNodes.length > 0 )
  139. {
  140. balloon.removeChild(balloon.childNodes[0]); // remove anything in the current balloon
  141. }
  142. }
  143.  
  144. var closeElt = document.createElement('img');
  145. closeElt.setAttribute("id","closebox");
  146. closeElt.setAttribute("src","http://images.metafilter.com/mefi/icons/stockholm_mini/close.gif");
  147. closeElt.addEventListener('click', hideBalloon, false);
  148. balloon.appendChild(closeElt);
  149. var newElt = document.createElement('div');
  150. newElt.setAttribute("id","balloon_contents");
  151. var thisUserID = "User"+this.href.substring(31);
  152. var thisUserNotes = GM_getValue(thisUserID, 0);
  153. var k;
  154. var height = 13;
  155. for (k=0; k<thisUserNotes; k++)
  156. {
  157. var newLine = "-" + GM_getValue(thisUserID+k, "");
  158. newElt.innerHTML += newLine;
  159. newElt.innerHTML += "<br>";
  160. var nRows = Math.ceil( (newLine.length)/30);
  161. console.log("line Lenght = " + newLine.length + "nRows = " + nRows);
  162. height += 13 * nRows;
  163. }
  164. balloon.style.width = "150px";
  165. balloon.style.height = height;
  166. balloon.appendChild(newElt);
  167. balloon.style.top = (objY-0) + 'px';
  168. balloon.style.left = (objX+45) + 'px';
  169. balloon.style.display = 'block';
  170. } // function viewNotes
  171.  
  172. // make the info balloon disappear
  173. // from http://blog.kung-foo.tv/archives/001614.html
  174. function hideBalloon()
  175. {
  176. var balloon = document.getElementById("balloon");
  177. if ( balloon )
  178. {
  179. balloon.style.display = 'none';
  180. currentBalloon = null;
  181. }
  182. return false;
  183. } // function hideBalloon
  184. // find the position of an object along the x axis of the page
  185. // from http://blog.kung-foo.tv/archives/001614.html
  186. function findPosX(obj)
  187. {
  188. var curleft = 0;
  189. if (obj.offsetParent)
  190. {
  191. while (obj.offsetParent)
  192. {
  193. curleft += obj.offsetLeft;
  194. obj = obj.offsetParent;
  195. }
  196. if ( obj != null )
  197. {
  198. curleft += obj.offsetLeft;
  199. }
  200. }
  201. else if (obj.x)
  202. {
  203. curleft += obj.x;
  204. }
  205. return curleft;
  206. } // function findPosX
  207.  
  208. // finds the position of an object along the y axis of the page
  209. // from http://blog.kung-foo.tv/archives/001614.html
  210. function findPosY(obj)
  211. {
  212. var curtop = 0;
  213. if (obj.offsetParent)
  214. {
  215. while (obj.offsetParent)
  216. {
  217. curtop += obj.offsetTop;
  218. obj = obj.offsetParent;
  219. }
  220. if ( obj != null )
  221. {
  222. curtop += obj.offsetTop;
  223. }
  224. }
  225. else if (obj.y)
  226. {
  227. curtop += obj.y;
  228. }
  229. return curtop;
  230. } // funcion findYPos
  231.  
  232. // creates a button element that calls a function on this page
  233. function createButton(id, name, href, words, target, clickFunction)
  234. {
  235. //ajaxButton = createButton("ajaxButton", "ajaxButton", "##", "ajax notes", "_self", toggleAjax);
  236. newButton = document.createElement("a");
  237. newButton.setAttribute("id", id);
  238. newButton.setAttribute("name", name);
  239. newButton.setAttribute("href", href);
  240. newButton.innerHTML = words;
  241. newButton.setAttribute("target", target);
  242. newButton.addEventListener("click", clickFunction, false);
  243. return newButton;
  244. } // function createButton
  245.  
  246.  
  247. // toggles the value of our 'is ajax on?' boolean
  248. function toggleAjax()
  249. {
  250. ajaxSetting = !ajaxSetting;
  251. GM_setValue(isAjaxOn, ajaxSetting);
  252. if (ajaxSetting) // turning it on
  253. {
  254. ajaxButton.innerHTML = ajaxButton.innerHTML.replace("off", "on");
  255. showLinkedNotes();
  256. }
  257. else //turning it off
  258. {
  259. ajaxButton.innerHTML = ajaxButton.innerHTML.replace("on", "off");
  260. removeLinkedNotes();
  261. }
  262. } // function toggleAjax
  263. // for profile pages: create and display all the notes for this user.
  264. function takeNotes()
  265. {
  266. anchor = document.getElementById('contact');
  267. if (anchor == null) // this is your own profile - no notes about yourself, because the 'contact' element doesn't exist
  268. {
  269. return;
  270. }
  271. outputDiv = anchor.parentNode;
  272. if (numUserNotes > 0)
  273. {
  274. notesHeadline = document.createElement("div");
  275. notesHeadline.setAttribute("id", "headline");
  276. notesHeadline.innerHTML = "<b>My Notes</b>";
  277.  
  278. delNotes = createButton("delBut", "DelNotes", "##", " [delete all]", "_self", delAllNotes);
  279. outputDiv.appendChild(notesHeadline);
  280. notesHeadline.appendChild(delNotes);
  281.  
  282. for (var i = 0; i < numUserNotes; i++)
  283. {
  284. note = GM_getValue(currentUserID+i, "");
  285. currentNotes = document.createElement("div");
  286. currentNotes.setAttribute("id", "currentNotes"+i);
  287. currentNotes.innerHTML = note;
  288. delCurrent = createButton("delBut"+i, "DelNote", "##", " [x]", "_self", delOneNote);
  289. outputDiv.appendChild(currentNotes);
  290. currentNotes.appendChild(delCurrent);
  291. }
  292. }
  293.  
  294. inputForm = document.createElement("form");
  295. inputForm.setAttribute("method", "post");
  296. inputForm.addEventListener("submit", addNote, false);
  297. inputForm.setAttribute("target", "_self");
  298. newNotes = document.createElement("input");
  299. newNotes.setAttribute("type", "text");
  300. newNotes.setAttribute("value", "new note...");
  301. newNotes.setAttribute("id", "noteInput");
  302. newNotes.addEventListener("focus", clearValue, true);
  303. newNotes.innerHTML = "<br>";
  304.  
  305. addNotes = document.createElement("input");
  306. addNotes.setAttribute("type", "submit");
  307. addNotes.setAttribute("id", "addBut");
  308. addNotes.setAttribute("value", "Add Note");
  309. addNotes.setAttribute("target", "_self");
  310.  
  311. inputForm.appendChild(newNotes);
  312. inputForm.appendChild(addNotes);
  313. outputDiv.appendChild(inputForm);
  314. } // function takeNotes
  315.  
  316. // clears the default text in the textbox when the user clicks on it
  317. function clearValue()
  318. {
  319. textbox = document.getElementById("noteInput");
  320. if (textbox.value == "new note...") {
  321. textbox.setAttribute("value", "");
  322. }
  323. } // function clearValue
  324.  
  325. // creates and displays a new note, from the text in the textbox
  326. function addNote()
  327. {
  328. var note = document.getElementById("noteInput");
  329. var noteNum = numUserNotes;
  330.  
  331. if (note.value == "")
  332. {
  333. return;
  334. }
  335. if (numUserNotes == 0)
  336. {
  337. notesHeadline = document.createElement("div");
  338. notesHeadline.setAttribute("id", "headline");
  339. notesHeadline.innerHTML = "<b>My Notes</b>";
  340. delNotes = createButton("delBut", "DelNotes", "##", " [delete all]", "_self", delAllNotes);
  341.  
  342. //alert(note);
  343. note.parentNode.insertBefore(notesHeadline, note);
  344. notesHeadline.appendChild(delNotes);
  345. }
  346.  
  347. numUserNotes++;
  348. GM_setValue(currentUserID, numUserNotes);
  349. GM_setValue(currentUserID+noteNum, note.value);
  350.  
  351. currentNoteNew = document.createElement("div");
  352. currentNoteNew.setAttribute("id", "currentNotes"+noteNum);
  353. currentNoteNew.innerHTML = note.value;
  354.  
  355. delCurrentNew = createButton("delBut"+noteNum, "DelNote", "##", " [x]", "_self", delOneNote);
  356. note.parentNode.insertBefore(currentNoteNew, note);
  357. currentNoteNew.appendChild(delCurrentNew);
  358. }//function addNotes
  359.  
  360.  
  361. // delete a single note
  362. function delOneNote()
  363. {
  364. var newID = (+this.id.substring(6));
  365. var nextID = newID + 1;
  366. while (nextID < numUserNotes)
  367. {
  368. var thisNoteID = currentUserID+newID;
  369. var nextNoteID = currentUserID+nextID;
  370. nextNoteValue = GM_getValue(nextNoteID, "");
  371.  
  372. currentNotes = document.getElementById("currentNotes"+nextID);
  373. currentNotes.setAttribute("id", "currentNotes"+newID);
  374.  
  375. delCurrent = document.getElementById("delBut"+nextID);
  376. delCurrent.setAttribute("id", "delBut"+newID);
  377.  
  378. GM_setValue(thisNoteID, nextNoteValue);
  379. newID++;
  380. nextID++;
  381. }
  382.  
  383.  
  384. numUserNotes--;
  385. GM_setValue(currentUserID, numUserNotes);
  386. noteText = this.parentNode;
  387. noteText.removeChild(this);
  388. noteText.parentNode.removeChild(noteText);
  389.  
  390. if (numUserNotes == 0)
  391. {
  392. headline = document.getElementById("headline");
  393. headline.parentNode.removeChild(headline);
  394. }
  395. } // fucntion delOneNote
  396.  
  397. // delete all notes about this user
  398. function delAllNotes()
  399. {
  400. var newID;
  401. for (newID = 0; newID < numUserNotes; newID++)
  402. {
  403. note = document.getElementById("currentNotes"+newID);
  404. note.parentNode.removeChild(note);
  405. }
  406. headline = document.getElementById("headline");
  407. headline.parentNode.removeChild(headline);
  408. numUserNotes = 0;
  409. GM_setValue(currentUserID, numUserNotes);
  410.  
  411. return false;
  412. } // function delAll Notes
  413.