PageRenderTime 34ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/js/watch.js

https://gitlab.com/potion/librechan
JavaScript | 451 lines | 372 code | 59 blank | 20 comment | 138 complexity | 0188bb6e6229bb2885eaa58fb523e620 MD5 | raw file
  1. /*
  2. * watch.js - board watch, thread watch and board pinning
  3. * https://github.com/vichan-devel/Tinyboard/blob/master/js/watch.js
  4. *
  5. * Released under the MIT license
  6. * Copyright (c) 2014 Marcin Ɓabanowski <marcin@6irc.net>
  7. *
  8. * Usage:
  9. * $config['api']['enabled'] = true;
  10. * $config['additional_javascript'][] = 'js/jquery.min.js';
  11. * $config['additional_javascript'][] = 'js/mobile-style.js';
  12. * //$config['additional_javascript'][] = 'js/titlebar-notifications.js';
  13. * //$config['additional_javascript'][] = 'js/auto-reload.js';
  14. * //$config['additional_javascript'][] = 'js/hide-threads.js';
  15. * //$config['additional_javascript'][] = 'js/compact-boardlist.js';
  16. * $config['additional_javascript'][] = 'js/watch.js';
  17. *
  18. */
  19. $(function(){
  20. // migrate from old name
  21. if (typeof localStorage.watch == "string") {
  22. localStorage.watch_js = localStorage.watch;
  23. delete localStorage.watch;
  24. }
  25. var window_active = true;
  26. $(window).focus(function() {
  27. window_active = true;
  28. $(window).trigger('scroll');
  29. });
  30. $(window).blur(function() {
  31. window_active = false;
  32. });
  33. var status = {};
  34. time_loaded = Date.now();
  35. var updating_suspended = false;
  36. var storage = function() {
  37. var storage = JSON.parse(localStorage.watch_js !== undefined ? localStorage.watch_js : "{}");
  38. delete storage.undefined; // fix for some bug
  39. return storage;
  40. };
  41. var storage_save = function(s) {
  42. localStorage.watch_js = JSON.stringify(s);
  43. };
  44. var osize = function(o) {
  45. var size = 0;
  46. for (var key in o) {
  47. if (o.hasOwnProperty(key)) size++;
  48. }
  49. return size;
  50. };
  51. var is_pinned = function(boardconfig) {
  52. return boardconfig.pinned || boardconfig.watched || (boardconfig.threads ? osize(boardconfig.threads) : false);
  53. };
  54. var is_boardwatched = function(boardconfig) {
  55. return boardconfig.watched;
  56. };
  57. var is_threadwatched = function(boardconfig, thread) {
  58. return boardconfig && boardconfig.threads && boardconfig.threads[thread];
  59. };
  60. var toggle_pinned = function(board) {
  61. var st = storage();
  62. var bc = st[board] || {};
  63. if (is_pinned(bc)) {
  64. bc.pinned = false;
  65. bc.watched = false;
  66. bc.threads = {};
  67. }
  68. else {
  69. bc.pinned = true;
  70. }
  71. st[board] = bc;
  72. storage_save(st);
  73. return bc.pinned;
  74. };
  75. var toggle_boardwatched = function(board) {
  76. var st = storage();
  77. var bc = st[board] || {};
  78. bc.watched = !is_boardwatched(bc) && Date.now();
  79. st[board] = bc;
  80. storage_save(st);
  81. return bc.watched;
  82. };
  83. var toggle_threadwatched = function(board, thread) {
  84. var st = storage();
  85. var bc = st[board] || {};
  86. if (is_threadwatched(bc, thread)) {
  87. delete bc.threads[thread];
  88. }
  89. else {
  90. bc.threads = bc.threads || {};
  91. bc.threads[thread] = Date.now();
  92. }
  93. st[board] = bc;
  94. storage_save(st);
  95. return is_threadwatched(bc, thread);
  96. };
  97. var construct_watchlist_for = function(board, variant) {
  98. var list = $("<div class='boardlist top cb-menu watch-menu'></div>");
  99. list.attr("data-board", board);
  100. if (storage()[board] && storage()[board].threads)
  101. for (var tid in storage()[board].threads) {
  102. var newposts = "(0)";
  103. if (status && status[board] && status[board].threads && status[board].threads[tid]) {
  104. if (status[board].threads[tid] == -404) {
  105. newposts = "<i class='fa fa-ban-circle'></i>";
  106. }
  107. else {
  108. newposts = "("+status[board].threads[tid]+")";
  109. }
  110. }
  111. var tag;
  112. if (variant == 'desktop') {
  113. tag = $("<a href='"+modRoot+board+"/res/"+tid+".html'><span>#"+tid+"</span><span class='cb-uri watch-remove'>"+newposts+"</span>");
  114. tag.find(".watch-remove").mouseenter(function() {
  115. this.oldval = $(this).html();
  116. $(this).css("min-width", $(this).width());
  117. $(this).html("<i class='fa fa-minus'></i>");
  118. })
  119. .mouseleave(function() {
  120. $(this).html(this.oldval);
  121. })
  122. }
  123. else if (variant == 'mobile') {
  124. tag = $("<a href='"+modRoot+board+"/res/"+tid+".html'><span>#"+tid+"</span><span class='cb-uri'>"+newposts+"</span>"
  125. +"<span class='cb-uri watch-remove'><i class='fa fa-minus'></i></span>");
  126. }
  127. tag.attr('data-thread', tid)
  128. .addClass("cb-menuitem")
  129. .appendTo(list)
  130. .find(".watch-remove")
  131. .click(function() {
  132. var b = $(this).parent().parent().attr("data-board");
  133. var t = $(this).parent().attr("data-thread");
  134. toggle_threadwatched(b, t);
  135. $(this).parent().parent().parent().mouseleave();
  136. $(this).parent().remove();
  137. return false;
  138. });
  139. }
  140. return list;
  141. };
  142. var update_pinned = function() {
  143. if (updating_suspended) return;
  144. if (typeof update_title != "undefined") update_title();
  145. var bl = $('.boardlist').first();
  146. $('#watch-pinned, .watch-menu').remove();
  147. var pinned = $('<div id="watch-pinned"></div>').appendTo(bl);
  148. if (device_type == "desktop")
  149. bl.off().on("mouseenter", function() {
  150. updating_suspended = true;
  151. }).on("mouseleave", function() {
  152. updating_suspended = false;
  153. });
  154. var st = storage();
  155. for (var i in st) {
  156. if (is_pinned(st[i])) {
  157. var link;
  158. if (bl.find('[href*="'+modRoot+i+'/index.html"]:not(.cb-menuitem)').length) link = bl.find('[href*="'+modRoot+i+'/"]').first();
  159. else link = $('<a href="'+modRoot+i+'/" class="cb-item cb-cat">/'+i+'/</a>').appendTo(pinned);
  160. if (link[0].origtitle === undefined) {
  161. link[0].origtitle = link.html();
  162. }
  163. else {
  164. link.html(link[0].origtitle);
  165. }
  166. if (st[i].watched) {
  167. link.css("font-weight", "bold");
  168. if (status && status[i] && status[i].new_threads) {
  169. link.html(link.html() + " (" + status[i].new_threads + ")");
  170. }
  171. }
  172. else if (st[i].threads && osize(st[i].threads)) {
  173. link.css("font-style", "italic");
  174. link.attr("data-board", i);
  175. if (status && status[i] && status[i].threads) {
  176. var new_posts = 0;
  177. for (var tid in status[i].threads) {
  178. if (status[i].threads[tid] > 0) {
  179. new_posts += status[i].threads[tid];
  180. }
  181. }
  182. if (new_posts > 0) {
  183. link.html(link.html() + " (" + new_posts + ")");
  184. }
  185. }
  186. if (device_type == "desktop")
  187. link.off().mouseenter(function() {
  188. $('.cb-menu').remove();
  189. var board = $(this).attr("data-board");
  190. var wl = construct_watchlist_for(board, "desktop").appendTo($(this))
  191. .css("top", $(this).position().top
  192. + ($(this).css('padding-top').replace('px', '')|0)
  193. + ($(this).css('padding-bottom').replace('px', '')|0)
  194. + $(this).height())
  195. .css("left", $(this).position().left)
  196. .css("right", "auto")
  197. .css("font-style", "normal");
  198. if (typeof init_hover != "undefined")
  199. wl.find("a.cb-menuitem").each(init_hover);
  200. }).mouseleave(function() {
  201. $('.boardlist .cb-menu').remove();
  202. });
  203. }
  204. }
  205. }
  206. if (device_type == "mobile" && (active_page == 'thread' || active_page == 'index')) {
  207. var board = $('form[name="post"] input[name="board"]').val();
  208. var where = $('div[style="text-align:right"]').first();
  209. $('.watch-menu').remove();
  210. construct_watchlist_for(board, "mobile").css("float", "left").insertBefore(where);
  211. }
  212. };
  213. var fetch_jsons = function() {
  214. if (window_active) check_scroll();
  215. var st = storage();
  216. var sched = 0;
  217. var sched_diff = 300;
  218. for (var i in st) {
  219. if (st[i].watched) {
  220. (function(i) {
  221. setTimeout(function() {
  222. var r = $.getJSON(configRoot+i+"/threads.json", function(j, x, r) {
  223. handle_board_json(r.board, j);
  224. });
  225. r.board = i;
  226. }, sched);
  227. sched += sched_diff;
  228. })(i);
  229. }
  230. else if (st[i].threads) {
  231. for (var j in st[i].threads) {
  232. (function(i,j) {
  233. setTimeout(function() {
  234. var r = $.getJSON(configRoot+i+"/res/"+j+".json", function(k, x, r) {
  235. handle_thread_json(r.board, r.thread, k);
  236. }).error(function(r) {
  237. if(r.status == 404) handle_thread_404(r.board, r.thread);
  238. });
  239. r.board = i;
  240. r.thread = j;
  241. }, sched);
  242. })(i,j);
  243. sched += sched_diff;
  244. }
  245. }
  246. }
  247. setTimeout(fetch_jsons, sched + sched_diff);
  248. };
  249. var handle_board_json = function(board, json) {
  250. var last_thread;
  251. var new_threads = 0;
  252. var hidden_data = {};
  253. if (localStorage.hiddenthreads) {
  254. hidden_data = JSON.parse(localStorage.hiddenthreads);
  255. }
  256. for (var i in json) {
  257. for (var j in json[i].threads) {
  258. var thread = json[i].threads[j];
  259. if (hidden_data[board]) { // hide threads integration
  260. var cont = false;
  261. for (var k in hidden_data[board]) {
  262. if (parseInt(k) == thread.no) {
  263. cont = true;
  264. break;
  265. }
  266. }
  267. if (cont) continue;
  268. }
  269. if (thread.last_modified > storage()[board].watched / 1000) {
  270. last_thread = thread.no;
  271. new_threads++;
  272. }
  273. }
  274. }
  275. status = status || {};
  276. status[board] = status[board] || {};
  277. if (status[board].last_thread != last_thread || status[board].new_threads != new_threads) {
  278. status[board].last_thread = last_thread;
  279. status[board].new_threads = new_threads;
  280. update_pinned();
  281. }
  282. };
  283. var handle_thread_json = function(board, threadid, json) {
  284. var new_posts = 0;
  285. for (var i in json.posts) {
  286. var post = json.posts[i];
  287. if (post.time > storage()[board].threads[threadid] / 1000) {
  288. new_posts++;
  289. }
  290. }
  291. status = status || {};
  292. status[board] = status[board] || {};
  293. status[board].threads = status[board].threads || {};
  294. if (status[board].threads[threadid] != new_posts) {
  295. status[board].threads[threadid] = new_posts;
  296. update_pinned();
  297. }
  298. };
  299. var handle_thread_404 = function(board, threadid) {
  300. status = status || {};
  301. status[board] = status[board] || {};
  302. status[board].threads = status[board].threads || {};
  303. if (status[board].threads[threadid] != -404) {
  304. status[board].threads[threadid] = -404; //notify 404
  305. update_pinned();
  306. }
  307. };
  308. if (active_page == "thread") {
  309. var board = $('form[name="post"] input[name="board"]').val();
  310. var thread = $('form[name="post"] input[name="thread"]').val();
  311. var boardconfig = storage()[board] || {};
  312. $('hr:first').before('<div id="watch-thread" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
  313. $('#watch-thread a').html(is_threadwatched(boardconfig, thread) ? _("Stop watching this thread") : _("Watch this thread")).click(function() {
  314. $(this).html(toggle_threadwatched(board, thread) ? _("Stop watching this thread") : _("Watch this thread"));
  315. update_pinned();
  316. });
  317. }
  318. if (active_page == "index") {
  319. var board = $('form[name="post"] input[name="board"]').val();
  320. var boardconfig = storage()[board] || {};
  321. $('hr:first').before('<div id="watch-pin" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
  322. $('#watch-pin a').html(is_pinned(boardconfig) ? _("Unpin this board") : _("Pin this board")).click(function() {
  323. $(this).html(toggle_pinned(board) ? _("Unpin this board") : _("Pin this board"));
  324. $('#watch-board a').html(is_boardwatched(boardconfig) ? _("Stop watching this board") : _("Watch this board"));
  325. update_pinned();
  326. });
  327. $('hr:first').before('<div id="watch-board" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
  328. $('#watch-board a').html(is_boardwatched(boardconfig) ? _("Stop watching this board") : _("Watch this board")).click(function() {
  329. $(this).html(toggle_boardwatched(board) ? _("Stop watching this board") : _("Watch this board"));
  330. $('#watch-pin a').html(is_pinned(boardconfig) ? _("Unpin this board") : _("Pin this board"));
  331. update_pinned();
  332. });
  333. }
  334. var check_post = function(frame, post) {
  335. return post.length && $(frame).scrollTop() + $(frame).height() >=
  336. post.position().top + post.height();
  337. }
  338. var check_scroll = function() {
  339. if (!status) return;
  340. var refresh = false;
  341. for(var bid in status) {
  342. if (((status[bid].new_threads && (active_page == "ukko" || active_page == "index")) || status[bid].new_threads == 1)
  343. && check_post(this, $('[data-board="'+bid+'"]#thread_'+status[bid].last_thread))) {
  344. var st = storage()
  345. st[bid].watched = time_loaded;
  346. storage_save(st);
  347. refresh = true;
  348. }
  349. if (!status[bid].threads) continue;
  350. for (var tid in status[bid].threads) {
  351. if(status[bid].threads[tid] && check_post(this, $('[data-board="'+bid+'"]#thread_'+tid))) {
  352. var st = storage();
  353. st[bid].threads[tid] = time_loaded;
  354. storage_save(st);
  355. refresh = true;
  356. }
  357. }
  358. }
  359. return refresh;
  360. };
  361. $(window).scroll(function() {
  362. var refresh = check_scroll();
  363. if (refresh) {
  364. //fetch_jsons();
  365. refresh = false;
  366. }
  367. });
  368. if (typeof add_title_collector != "undefined")
  369. add_title_collector(function() {
  370. if (!status) return 0;
  371. var sum = 0;
  372. for (var bid in status) {
  373. if (status[bid].new_threads) {
  374. sum += status[bid].new_threads;
  375. if (!status[bid].threads) continue;
  376. for (var tid in status[bid].threads) {
  377. if (status[bid].threads[tid] > 0) {
  378. if (auto_reload_enabled && active_page == "thread") {
  379. var board = $('form[name="post"] input[name="board"]').val();
  380. var thread = $('form[name="post"] input[name="thread"]').val();
  381. if (board == bid && thread == tid) continue;
  382. }
  383. sum += status[bid].threads[tid];
  384. }
  385. }
  386. }
  387. }
  388. return sum;
  389. });
  390. update_pinned();
  391. fetch_jsons();
  392. });