PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/usernotes.js

https://github.com/creesch/reddit-moderator-toolbox
JavaScript | 582 lines | 499 code | 49 blank | 34 comment | 52 complexity | 86a0a50436bc1f8de74b89690e2bd54a MD5 | raw file
Possible License(s): Apache-2.0
  1. function usernotes() {
  2. if (!TBUtils.logged || !TBUtils.getSetting('UserNotes', 'enabled', true)) return;
  3. $.log('Loading User Notes Module');
  4. var subs = [];
  5. TBUtils.getModSubs(function () {
  6. run();
  7. });
  8. // Compatibility with Sweden
  9. var COMMENTS_LINK_RE = /\/comments\/(\w+)\/[^\/]+(\/(\w+))?\/?(\?.*)?$/;
  10. var MODMAIL_LINK_RE = /\/messages\/(\w+)\/?(\?.*)?$/;
  11. var ConstManager = function(init_pools) {
  12. return {
  13. _pools: init_pools,
  14. create: function(poolName, constant) {
  15. var pool = this._pools[poolName];
  16. var id = pool.indexOf(constant);
  17. if(id !== -1)
  18. return id;
  19. pool.push(constant);
  20. return pool.length - 1;
  21. },
  22. get: function(poolName, id) {
  23. return this._pools[poolName][id];
  24. }
  25. };
  26. };
  27. function getUser(users, name) {
  28. if(users.hasOwnProperty(name)) {
  29. return users[name];
  30. }
  31. return undefined;
  32. }
  33. function squashPermalink(permalink) {
  34. var linkMatches = permalink.match(COMMENTS_LINK_RE);
  35. var modMailMatches = permalink.match(MODMAIL_LINK_RE);
  36. if(linkMatches) {
  37. var squashed = "l," + linkMatches[1];
  38. if(linkMatches[3] !== undefined)
  39. squashed += "," + linkMatches[3];
  40. return squashed
  41. } else if(modMailMatches) {
  42. return "m," + modMailMatches[1];
  43. } else {
  44. return "";
  45. }
  46. }
  47. function unsquashPermalink(subreddit, permalink) {
  48. var linkParams = permalink.split(/,/g);
  49. var link = "/r/" + subreddit + "/";
  50. if(linkParams[0] == "l") {
  51. link += "comments/" + linkParams[1] + "/";
  52. if(linkParams.length > 2)
  53. link += "a/" + linkParams[2] + "/";
  54. } else if(linkParams[0] == "m") {
  55. link += "message/messages/" + linkParams [1];
  56. } else {
  57. return "";
  58. }
  59. return link;
  60. }
  61. function postToWiki(sub, json, reason) {
  62. TBUtils.noteCache[sub] = json;
  63. json = deflateNotes(json);
  64. $.log("Saving usernotes to wiki...");
  65. TBUtils.postToWiki('usernotes', sub, json, reason, true, false, function postToWiki(succ, err) {
  66. if (succ) {
  67. $.log("Success!");
  68. run();
  69. } else {
  70. $.log("Failure: "+err);
  71. }
  72. });
  73. }
  74. // NER support.
  75. window.addEventListener("TBNewThings", function () {
  76. run();
  77. });
  78. function processThing(thing) {
  79. if ($(thing).hasClass('ut-processed')) {
  80. return;
  81. }
  82. $(thing).addClass('ut-processed');
  83. var subreddit = TBUtils.getThingInfo(thing, true).subreddit;
  84. if (!subreddit) return;
  85. var tag = '<span class="usernote-span-' +
  86. subreddit + '" style="color:#888888; font-size:x-small;">&nbsp;[<a class="add-user-tag-' +
  87. subreddit + '" id="add-user-tag" "href="javascript:;">N</a>]</span>';
  88. $(thing).attr('subreddit', subreddit);
  89. // More mod mail hackery... all this to see your own tags in mod mail. It's likely not worth it.
  90. var userattrs = $(thing).find('.userattrs');
  91. if ($(userattrs).length > 0) {
  92. $(userattrs).after(tag);
  93. } else {
  94. $(thing).find('.head').append(tag);
  95. }
  96. if ($.inArray(subreddit, subs) == -1) {
  97. subs.push(subreddit);
  98. }
  99. }
  100. function processSub(currsub) {
  101. if (TBUtils.noteCache[currsub] !== undefined) {
  102. setNotes(TBUtils.noteCache[currsub], currsub);
  103. return;
  104. }
  105. if (!currsub || TBUtils.noNotes.indexOf(currsub) != -1) return;
  106. TBUtils.readFromWiki(currsub, 'usernotes', true, function (resp) {
  107. if (!resp || resp === TBUtils.WIKI_PAGE_UNKNOWN) {
  108. return;
  109. }
  110. if (resp === TBUtils.NO_WIKI_PAGE) {
  111. TBUtils.noNotes.push(currsub);
  112. return;
  113. }
  114. if (!resp || resp.length < 1) {
  115. TBUtils.noNotes.push(currsub);
  116. return;
  117. }
  118. resp = convertNotes(resp);
  119. TBUtils.noteCache[currsub] = resp;
  120. setNotes(resp, currsub);
  121. });
  122. }
  123. // Inflate notes from the database, converting between versions if necessary.
  124. function convertNotes(notes) {
  125. function decodeNoteText(notes) {
  126. // We stopped using encode()d notes in v4
  127. notes.users.forEach(function(user) {
  128. user.notes.forEach(function(note) {
  129. note.note = unescape(note.note);
  130. });
  131. });
  132. return notes;
  133. }
  134. function keyOnUsername(notes) {
  135. // we have to rebuild .users to be an object keyed on .name
  136. var users = {};
  137. notes.users.forEach(function(user){
  138. users[user.name] = {
  139. "notes": user.notes
  140. }
  141. });
  142. notes.users = users;
  143. return notes;
  144. }
  145. if(notes.ver <= 2) {
  146. var newUsers = [];
  147. var corruptedNotes = false;
  148. //TODO: v2 support drops next version
  149. notes.users.forEach(function(user) {
  150. if(!user.hasOwnProperty('name') || !user.hasOwnProperty('notes')) {
  151. corruptedNotes = true;
  152. } else {
  153. user.notes.forEach(function(note) {
  154. if(note.link && note.link.trim()) {
  155. note.link = squashPermalink(note.link);
  156. }
  157. });
  158. newUsers.push(user);
  159. }
  160. });
  161. notes.users = newUsers;
  162. notes.ver = TBUtils.notesSchema;
  163. notes.corrupted = corruptedNotes;
  164. return keyOnUsername(decodeNoteText(notes));
  165. } else if(notes.ver == 3) {
  166. notes = keyOnUsername(decodeNoteText(inflateNotesV3(notes)));
  167. notes.ver = TBUtils.notesSchema;
  168. return notes;
  169. } else if(notes.ver == 4) {
  170. return inflateNotes(notes);
  171. }
  172. //TODO: throw an error if unrecognized version?
  173. }
  174. // Compress notes so they'll store well in the database.
  175. function deflateNotes(notes) {
  176. var deflated = {
  177. ver: TBUtils.notesSchema,
  178. users: {},
  179. constants: {
  180. users: [],
  181. warnings: []
  182. }
  183. };
  184. var mgr = new ConstManager(deflated.constants);
  185. $.each(notes.users, function(name, user) {
  186. deflated.users[name] = {
  187. "ns": user.notes.map(function(note) {
  188. return {
  189. "n": note.note,
  190. "t": note.time,
  191. "m": mgr.create("users", note.mod),
  192. "l": note.link,
  193. "w": mgr.create("warnings", note.type),
  194. };
  195. })
  196. };
  197. });
  198. return deflated;
  199. }
  200. // Decompress notes from the database into a more useful format
  201. function inflateNotes(deflated) {
  202. var notes = {
  203. ver: TBUtils.notesSchema,
  204. users: {}
  205. };
  206. var mgr = new ConstManager(deflated.constants);
  207. $.each(deflated.users, function(name, user) {
  208. notes.users[name] = {
  209. "name": name,
  210. "notes": user.ns.map(function(note) {
  211. return inflateNote(mgr, note);
  212. })
  213. };
  214. });
  215. return notes;
  216. }
  217. // Decompress notes from the database into a more useful format (MIGRATION ONLY)
  218. function inflateNotesV3(deflated) {
  219. var notes = {
  220. ver: 3,
  221. users: []
  222. };
  223. var mgr = new ConstManager(deflated.constants);
  224. notes.users = deflated.users.map(function(user) {
  225. return {
  226. "name": mgr.get("users", user.u),
  227. "notes": user.ns.map(function(note) {
  228. var note = inflateNote(mgr, note);
  229. if(note.link) note.link = "l," + note.link;
  230. return note;
  231. })
  232. };
  233. });
  234. return notes;
  235. }
  236. // Inflates a single note
  237. function inflateNote(mgr, note) {
  238. return {
  239. "note": TBUtils.htmlDecode(note.n),
  240. "time": note.t,
  241. "mod": mgr.get("users", note.m),
  242. "link": note.l,
  243. "type": mgr.get("warnings", note.w),
  244. };
  245. }
  246. function setNotes(notes, subreddit) {
  247. //$.log("notes = " + notes);
  248. //$.log("notes.ver = " + notes.ver);
  249. // schema check.
  250. if (notes.ver > TBUtils.notesSchema) {
  251. // Remove the option to add notes.
  252. $('.usernote-span-' + subreddit).remove();
  253. TBUtils.alert("You are using a version of toolbox that cannot read a newer usernote data format. Please update your extension.", function(clicked) {
  254. if (clicked) window.open("/r/toolbox/wiki/download");
  255. });
  256. return;
  257. }
  258. var things = $('div.thing .entry[subreddit=' + subreddit + ']');
  259. TBUtils.forEachChunked(things, 25, 250, function (thing) {
  260. var user = TBUtils.getThingInfo(thing).user;
  261. var u = getUser(notes.users, user);
  262. var usertag = $(thing).find('.add-user-tag-' + subreddit);
  263. // Only happens if you delete the last note.
  264. if (u === undefined || u.notes.length < 1) {
  265. $(usertag).css('color', '');
  266. $(usertag).text('N');
  267. return;
  268. }
  269. note = u.notes[0].note;
  270. if (note.length > 53)
  271. note = note.substring(0, 50)+"...";
  272. $(usertag).html('<b>' + TBUtils.htmlEncode(note) + '</b>' + ((u.notes.length > 1) ? ' (+' + (u.notes.length - 1) + ')' : ''));
  273. var type = u.notes[0].type;
  274. if (!type) type = 'none';
  275. $(usertag).css('color', TBUtils.getTypeInfo(type).color);
  276. });
  277. }
  278. function run() {
  279. var things = $('div.thing .entry:not(.ut-processed)');
  280. TBUtils.forEachChunked(things, 25, 500, processThing, function () {
  281. TBUtils.forEachChunked(subs, 10, 500, processSub);
  282. });
  283. }
  284. $('body').on('click', '#add-user-tag', function (e) {
  285. var thing = $(e.target).closest('.thing .entry'),
  286. info = TBUtils.getThingInfo(thing),
  287. subreddit = info.subreddit,
  288. user = info.user,
  289. link = squashPermalink(info.permalink);
  290. // Make box & add subreddit radio buttons
  291. // var popup = $(
  292. // '<div class="utagger-popup">\
  293. // <div class="utagger-popup-header">\
  294. // User Notes - <a href="http://reddit.com/u/' + user + '" id="utagger-user-link">/u/' + user + '</a>\
  295. // <span class="close right"><a href="javascript:;">✕</a></span>\
  296. // </div>\
  297. // <div class="utagger-popup-content">\
  298. // <table class="utagger-notes"><tbody><tr>\
  299. // <td class="utagger-notes-td1">Author</td>\
  300. // <td class="utagger-notes-td2">Note</td>\
  301. // <td class="utagger-notes-td3"></td>\
  302. // </tr></tbody></table>\
  303. // <table class="utagger-type"><tbody>\
  304. // <tr>\
  305. // <td><input type="radio" name="type-group" class="utagger-type-input" id="utagger-type-none" value="none" checked/><label for="utagger-type-none" style="color: #369;">None</label></td>\
  306. // </tr>\
  307. // </tbody></table>\
  308. // <span>\
  309. // <input type="text" placeholder="something about the user..." class="utagger-user-note" id="utagger-user-note-input" data-link="' + link + '" data-subreddit="' + subreddit + '" data-user="' + user + '">\
  310. // <br><label><input type="checkbox" class="utagger-include-link" checked /> include link</label>\
  311. // </span>\
  312. // </div>\
  313. // <div class="utagger-popup-footer">\
  314. // <input type="button" class="utagger-save-user" id="utagger-save-user" value="save for /r/' + subreddit + '">\
  315. // </div>\
  316. // </div>'
  317. // )
  318. var popup = TB.ui.popup(
  319. 'User Notes - <a href="http://reddit.com/u/' + user + '" id="utagger-user-link">/u/' + user + '</a>',
  320. [
  321. {
  322. content: '\
  323. <table class="utagger-notes"><tbody><tr>\
  324. <td class="utagger-notes-td1">Author</td>\
  325. <td class="utagger-notes-td2">Note</td>\
  326. <td class="utagger-notes-td3"></td>\
  327. </tr></tbody></table>\
  328. <table class="utagger-type"><tbody>\
  329. <tr>\
  330. <td><input type="radio" name="type-group" class="utagger-type-input" id="utagger-type-none" value="none" checked/><label for="utagger-type-none" style="color: #369;">None</label></td>\
  331. </tr>\
  332. </tbody></table>\
  333. <span>\
  334. <input type="text" placeholder="something about the user..." class="utagger-user-note" id="utagger-user-note-input" data-link="' + link + '" data-subreddit="' + subreddit + '" data-user="' + user + '">\
  335. <br><label><input type="checkbox" class="utagger-include-link" checked /> include link</label>\
  336. </span>',
  337. footer: '\
  338. <input type="button" class="utagger-save-user" id="utagger-save-user" value="save for /r/' + subreddit + '">'
  339. }
  340. ],
  341. '', // meta to inject in popup header; just a placeholder
  342. 'utagger-popup' // class
  343. )
  344. .appendTo('body')
  345. .css({
  346. left: e.pageX - 50,
  347. top: e.pageY - 10,
  348. display: 'block'
  349. });
  350. var $table = popup.find('.utagger-type tr:first');
  351. $(TBUtils.warningType).each(function () {
  352. var info = TBUtils.getTypeInfo(this);
  353. $table.append('<td><input type="radio" name="type-group" class="utagger-type-input" id="utagger-type-' + this + '" value="' + this + '"><label for="utagger-type-' + this + '" style="color: ' + info.color + ';">' + info.text + '</label></td>');
  354. });
  355. TBUtils.readFromWiki(subreddit, 'usernotes', true, function (resp) {
  356. if (!resp || resp === TBUtils.WIKI_PAGE_UNKNOWN || resp === TBUtils.NO_WIKI_PAGE || resp.length < 1) {
  357. TBUtils.noNotes.push(subreddit);
  358. return;
  359. }
  360. resp = convertNotes(resp);
  361. TBUtils.noteCache[subreddit] = resp;
  362. var u = getUser(resp.users, user);
  363. // User has notes
  364. if(u !== undefined) {
  365. popup.find('#utagger-type-' + u.notes[0].type).prop('checked',true);
  366. var i = 0;
  367. $(u.notes).each(function () {
  368. if (!this.type) this.type = 'none';
  369. var info = TBUtils.getTypeInfo(this.type);
  370. var typeSpan = '';
  371. if (info.name) {
  372. typeSpan = '<span style="color: ' + info.color + ';">[' + TBUtils.htmlEncode(info.name) + ']</span> ';
  373. }
  374. popup.find('table.utagger-notes').append('<tr><td class="utagger-notes-td1">' + this.mod + ' <br> <span class="utagger-date" id="utagger-date-' + i + '">' +
  375. new Date(this.time).toLocaleString() + '</span></td><td lass="utagger-notes-td2">' + typeSpan + this.note +
  376. '</td><td class="utagger-notes-td3"><img class="utagger-remove-note" noteid="' + this.time + '" src="data:image/png;base64,' + TBui.iconClose + '" /></td></tr>');
  377. if (this.link) {
  378. popup.find('#utagger-date-' + i).wrap('<a href="' + unsquashPermalink(subreddit, this.link) + '">');
  379. }
  380. i++;
  381. });
  382. }
  383. // No notes on user
  384. else {
  385. popup.find("#utagger-user-note-input").focus();
  386. }
  387. });
  388. });
  389. // 'cancel' button clicked
  390. $('body').on('click', '.utagger-popup .close', function () {
  391. $(this).parents('.utagger-popup').remove();
  392. });
  393. $('body').on('click', '.utagger-save-user, .utagger-remove-note', function (e) {
  394. var popup = $(this).closest('.utagger-popup'),
  395. unote = popup.find('.utagger-user-note'),
  396. subreddit = unote.attr('data-subreddit'),
  397. user = unote.attr('data-user'),
  398. noteid = $(e.target).attr('noteid'),
  399. noteText = unote.val(),
  400. deleteNote = (e.target.className == 'utagger-remove-note'),
  401. type = popup.find('.utagger-type-input:checked').val(),
  402. link = '',
  403. note = TBUtils.note,
  404. notes = TBUtils.usernotes;
  405. if (popup.find('.utagger-include-link').is(':checked')) {
  406. link = unote.attr('data-link');
  407. }
  408. if ((!user || !subreddit || !noteText) && !deleteNote) return;
  409. note = {
  410. note: noteText,
  411. time: new Date().getTime(),
  412. mod: TBUtils.logged,
  413. link: link,
  414. type: type
  415. };
  416. var userNotes = {
  417. notes: []
  418. };
  419. userNotes.notes.push(note);
  420. $(popup).remove();
  421. var noteSkel = {
  422. "ver": TBUtils.notesSchema,
  423. "constants": {},
  424. "users":{}
  425. };
  426. TBUtils.readFromWiki(subreddit, 'usernotes', true, function (resp) {
  427. if (resp === TBUtils.WIKI_PAGE_UNKNOWN) {
  428. return;
  429. }
  430. if (resp === TBUtils.NO_WIKI_PAGE) {
  431. notes = noteSkel;
  432. notes.users[user] = userNotes;
  433. postToWiki(subreddit, notes, 'create usernotes config');
  434. return;
  435. }
  436. // if we got this far, we have valid JSON
  437. notes = resp = convertNotes(resp);
  438. if (notes.corrupted) {
  439. TBUtils.alert('Toolbox found an issue with your usernotes while they were being saved. One or more of your notes appear to be written in the wrong format; to prevent further issues these have been deleted. All is well now.');
  440. }
  441. if (notes) {
  442. var u = getUser(notes.users, user);
  443. if(u !== undefined) {
  444. // Delete.
  445. if (deleteNote) {
  446. $(u.notes).each(function (idx) {
  447. if (this.time == noteid) {
  448. u.notes.splice(idx, 1);
  449. }
  450. });
  451. if (u.notes.length < 1) {
  452. delete notes.users[user];
  453. }
  454. postToWiki(subreddit, notes, 'delete note '+noteid+' on user '+user);
  455. // Add.
  456. } else {
  457. u.notes.unshift(note);
  458. postToWiki(subreddit, notes, 'create new note on user '+user);
  459. }
  460. // Adding a note for previously unknown user
  461. } else if (u === undefined && !deleteNote) {
  462. notes.users[user] = userNotes;
  463. postToWiki(subreddit, notes, 'create new note on new user '+user);
  464. }
  465. } else {
  466. // create new notes object
  467. notes = noteSkel;
  468. notes.users[user] = userNotes;
  469. postToWiki(subreddit, notes, 'create new notes object, add new note on user '+user);
  470. }
  471. });
  472. });
  473. $('body').on('click', '.utagger-cancel-user', function () {
  474. var popup = $(this).closest('.utagger-popup');
  475. $(popup).remove();
  476. });
  477. $('body').on('keyup', '.utagger-user-note', function (event) {
  478. if(event.keyCode == 13) {
  479. $.log("Enter pressed!", true);
  480. var popup = $(this).closest('.utagger-popup');
  481. popup.find('.utagger-save-user').click();
  482. }
  483. });
  484. }
  485. (function () {
  486. // wait for storage
  487. window.addEventListener("TBUtilsLoaded", function () {
  488. $.log("got tbutils");
  489. usernotes();
  490. });
  491. })();