The version of vichan running on lainchan.org
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

455 Zeilen
14KB

  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. bc.slugs = bc.slugs || {};
  93. bc.slugs[thread] = document.location.pathname + document.location.search;
  94. }
  95. st[board] = bc;
  96. storage_save(st);
  97. return is_threadwatched(bc, thread);
  98. };
  99. var construct_watchlist_for = function(board, variant) {
  100. var list = $("<div class='boardlist top cb-menu watch-menu'></div>");
  101. list.attr("data-board", board);
  102. if (storage()[board] && storage()[board].threads)
  103. for (var tid in storage()[board].threads) {
  104. var newposts = "(0)";
  105. if (status && status[board] && status[board].threads && status[board].threads[tid]) {
  106. if (status[board].threads[tid] == -404) {
  107. newposts = "<i class='fa fa-ban-circle'></i>";
  108. }
  109. else {
  110. newposts = "("+status[board].threads[tid]+")";
  111. }
  112. }
  113. var tag;
  114. if (variant == 'desktop') {
  115. tag = $("<a href='"+((storage()[board].slugs && storage()[board].slugs[tid]) || (modRoot+board+"/res/"+tid+".html"))+"'><span>#"+tid+"</span><span class='cb-uri watch-remove'>"+newposts+"</span>");
  116. tag.find(".watch-remove").mouseenter(function() {
  117. this.oldval = $(this).html();
  118. $(this).css("min-width", $(this).width());
  119. $(this).html("<i class='fa fa-minus'></i>");
  120. })
  121. .mouseleave(function() {
  122. $(this).html(this.oldval);
  123. })
  124. }
  125. else if (variant == 'mobile') {
  126. tag = $("<a href='"+((storage()[board].slugs && storage()[board].slugs[tid]) || (modRoot+board+"/res/"+tid+".html"))+"'><span>#"+tid+"</span><span class='cb-uri'>"+newposts+"</span>"
  127. +"<span class='cb-uri watch-remove'><i class='fa fa-minus'></i></span>");
  128. }
  129. tag.attr('data-thread', tid)
  130. .addClass("cb-menuitem")
  131. .appendTo(list)
  132. .find(".watch-remove")
  133. .click(function() {
  134. var b = $(this).parent().parent().attr("data-board");
  135. var t = $(this).parent().attr("data-thread");
  136. toggle_threadwatched(b, t);
  137. $(this).parent().parent().parent().mouseleave();
  138. $(this).parent().remove();
  139. return false;
  140. });
  141. }
  142. return list;
  143. };
  144. var update_pinned = function() {
  145. if (updating_suspended) return;
  146. if (typeof update_title != "undefined") update_title();
  147. var bl = $('.boardlist').first();
  148. $('#watch-pinned, .watch-menu').remove();
  149. var pinned = $('<div id="watch-pinned"></div>').appendTo(bl);
  150. if (device_type == "desktop")
  151. bl.off().on("mouseenter", function() {
  152. updating_suspended = true;
  153. }).on("mouseleave", function() {
  154. updating_suspended = false;
  155. });
  156. var st = storage();
  157. for (var i in st) {
  158. if (is_pinned(st[i])) {
  159. var link;
  160. if (bl.find('[href*="'+modRoot+i+'/index.html"]:not(.cb-menuitem)').length) link = bl.find('[href*="'+modRoot+i+'/"]').first();
  161. else link = $('<a href="'+modRoot+i+'/" class="cb-item cb-cat">/'+i+'/</a>').appendTo(pinned);
  162. if (link[0].origtitle === undefined) {
  163. link[0].origtitle = link.html();
  164. }
  165. else {
  166. link.html(link[0].origtitle);
  167. }
  168. if (st[i].watched) {
  169. link.css("font-weight", "bold");
  170. if (status && status[i] && status[i].new_threads) {
  171. link.html(link.html() + " (" + status[i].new_threads + ")");
  172. }
  173. }
  174. else if (st[i].threads && osize(st[i].threads)) {
  175. link.css("font-style", "italic");
  176. link.attr("data-board", i);
  177. if (status && status[i] && status[i].threads) {
  178. var new_posts = 0;
  179. for (var tid in status[i].threads) {
  180. if (status[i].threads[tid] > 0) {
  181. new_posts += status[i].threads[tid];
  182. }
  183. }
  184. if (new_posts > 0) {
  185. link.html(link.html() + " (" + new_posts + ")");
  186. }
  187. }
  188. if (device_type == "desktop")
  189. link.off().mouseenter(function() {
  190. $('.cb-menu').remove();
  191. var board = $(this).attr("data-board");
  192. var wl = construct_watchlist_for(board, "desktop").appendTo($(this))
  193. .css("top", $(this).position().top
  194. + ($(this).css('padding-top').replace('px', '')|0)
  195. + ($(this).css('padding-bottom').replace('px', '')|0)
  196. + $(this).height())
  197. .css("left", $(this).position().left)
  198. .css("right", "auto")
  199. .css("font-style", "normal");
  200. if (typeof init_hover != "undefined")
  201. wl.find("a.cb-menuitem").each(init_hover);
  202. }).mouseleave(function() {
  203. $('.boardlist .cb-menu').remove();
  204. });
  205. }
  206. }
  207. }
  208. if (device_type == "mobile" && (active_page == 'thread' || active_page == 'index')) {
  209. var board = $('form[name="post"] input[name="board"]').val();
  210. var where = $('div[style="text-align:right"]').first();
  211. $('.watch-menu').remove();
  212. construct_watchlist_for(board, "mobile").css("float", "left").insertBefore(where);
  213. }
  214. };
  215. var fetch_jsons = function() {
  216. if (window_active) check_scroll();
  217. var st = storage();
  218. var sched = 0;
  219. var sched_diff = 2000;
  220. for (var i in st) {
  221. if (st[i].watched) {
  222. (function(i) {
  223. setTimeout(function() {
  224. var r = $.getJSON(configRoot+i+"/threads.json", function(j, x, r) {
  225. handle_board_json(r.board, j);
  226. });
  227. r.board = i;
  228. }, sched);
  229. sched += sched_diff;
  230. })(i);
  231. }
  232. else if (st[i].threads) {
  233. for (var j in st[i].threads) {
  234. (function(i,j) {
  235. setTimeout(function() {
  236. var r = $.getJSON(configRoot+i+"/res/"+j+".json", function(k, x, r) {
  237. handle_thread_json(r.board, r.thread, k);
  238. }).error(function(r) {
  239. if(r.status == 404) handle_thread_404(r.board, r.thread);
  240. });
  241. r.board = i;
  242. r.thread = j;
  243. }, sched);
  244. })(i,j);
  245. sched += sched_diff;
  246. }
  247. }
  248. }
  249. setTimeout(fetch_jsons, sched + sched_diff);
  250. };
  251. var handle_board_json = function(board, json) {
  252. var last_thread;
  253. var new_threads = 0;
  254. var hidden_data = {};
  255. if (localStorage.hiddenthreads) {
  256. hidden_data = JSON.parse(localStorage.hiddenthreads);
  257. }
  258. for (var i in json) {
  259. for (var j in json[i].threads) {
  260. var thread = json[i].threads[j];
  261. if (hidden_data[board]) { // hide threads integration
  262. var cont = false;
  263. for (var k in hidden_data[board]) {
  264. if (parseInt(k) == thread.no) {
  265. cont = true;
  266. break;
  267. }
  268. }
  269. if (cont) continue;
  270. }
  271. if (thread.last_modified > storage()[board].watched / 1000) {
  272. last_thread = thread.no;
  273. new_threads++;
  274. }
  275. }
  276. }
  277. status = status || {};
  278. status[board] = status[board] || {};
  279. if (status[board].last_thread != last_thread || status[board].new_threads != new_threads) {
  280. status[board].last_thread = last_thread;
  281. status[board].new_threads = new_threads;
  282. update_pinned();
  283. }
  284. };
  285. var handle_thread_json = function(board, threadid, json) {
  286. var new_posts = 0;
  287. for (var i in json.posts) {
  288. var post = json.posts[i];
  289. if (post.time > storage()[board].threads[threadid] / 1000) {
  290. new_posts++;
  291. }
  292. }
  293. status = status || {};
  294. status[board] = status[board] || {};
  295. status[board].threads = status[board].threads || {};
  296. if (status[board].threads[threadid] != new_posts) {
  297. status[board].threads[threadid] = new_posts;
  298. update_pinned();
  299. }
  300. };
  301. var handle_thread_404 = function(board, threadid) {
  302. status = status || {};
  303. status[board] = status[board] || {};
  304. status[board].threads = status[board].threads || {};
  305. if (status[board].threads[threadid] != -404) {
  306. status[board].threads[threadid] = -404; //notify 404
  307. update_pinned();
  308. }
  309. };
  310. if (active_page == "thread") {
  311. var board = $('form[name="post"] input[name="board"]').val();
  312. var thread = $('form[name="post"] input[name="thread"]').val();
  313. var boardconfig = storage()[board] || {};
  314. $('hr:first').before('<div id="watch-thread" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
  315. $('#watch-thread a').html(is_threadwatched(boardconfig, thread) ? _("Stop watching this thread") : _("Watch this thread")).click(function() {
  316. $(this).html(toggle_threadwatched(board, thread) ? _("Stop watching this thread") : _("Watch this thread"));
  317. update_pinned();
  318. });
  319. }
  320. if (active_page == "index") {
  321. var board = $('form[name="post"] input[name="board"]').val();
  322. var boardconfig = storage()[board] || {};
  323. $('hr:first').before('<div id="watch-pin" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
  324. $('#watch-pin a').html(is_pinned(boardconfig) ? _("Unpin this board") : _("Pin this board")).click(function() {
  325. $(this).html(toggle_pinned(board) ? _("Unpin this board") : _("Pin this board"));
  326. $('#watch-board a').html(is_boardwatched(boardconfig) ? _("Stop watching this board") : _("Watch this board"));
  327. update_pinned();
  328. });
  329. $('hr:first').before('<div id="watch-board" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
  330. $('#watch-board a').html(is_boardwatched(boardconfig) ? _("Stop watching this board") : _("Watch this board")).click(function() {
  331. $(this).html(toggle_boardwatched(board) ? _("Stop watching this board") : _("Watch this board"));
  332. $('#watch-pin a').html(is_pinned(boardconfig) ? _("Unpin this board") : _("Pin this board"));
  333. update_pinned();
  334. });
  335. }
  336. var check_post = function(frame, post) {
  337. return post.length && $(frame).scrollTop() + $(frame).height() >=
  338. post.position().top + post.height();
  339. }
  340. var check_scroll = function() {
  341. if (!status) return;
  342. var refresh = false;
  343. for(var bid in status) {
  344. if (((status[bid].new_threads && (active_page == "ukko" || active_page == "index")) || status[bid].new_threads == 1)
  345. && check_post(this, $('[data-board="'+bid+'"]#thread_'+status[bid].last_thread))) {
  346. var st = storage()
  347. st[bid].watched = time_loaded;
  348. storage_save(st);
  349. refresh = true;
  350. }
  351. if (!status[bid].threads) continue;
  352. for (var tid in status[bid].threads) {
  353. if(status[bid].threads[tid] && check_post(this, $('[data-board="'+bid+'"]#thread_'+tid))) {
  354. var st = storage();
  355. st[bid].threads[tid] = time_loaded;
  356. storage_save(st);
  357. refresh = true;
  358. }
  359. }
  360. }
  361. return refresh;
  362. };
  363. $(window).scroll(function() {
  364. var refresh = check_scroll();
  365. if (refresh) {
  366. //fetch_jsons();
  367. refresh = false;
  368. }
  369. });
  370. if (typeof add_title_collector != "undefined")
  371. add_title_collector(function() {
  372. if (!status) return 0;
  373. var sum = 0;
  374. for (var bid in status) {
  375. if (status[bid].new_threads) {
  376. sum += status[bid].new_threads;
  377. if (!status[bid].threads) continue;
  378. for (var tid in status[bid].threads) {
  379. if (status[bid].threads[tid] > 0) {
  380. if (auto_reload_enabled && active_page == "thread") {
  381. var board = $('form[name="post"] input[name="board"]').val();
  382. var thread = $('form[name="post"] input[name="thread"]').val();
  383. if (board == bid && thread == tid) continue;
  384. }
  385. sum += status[bid].threads[tid];
  386. }
  387. }
  388. }
  389. }
  390. return sum;
  391. });
  392. update_pinned();
  393. fetch_jsons();
  394. });