From abb8128afde994f621f1a3ab088ccca9496f3da4 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Sun, 17 Feb 2013 16:52:15 +0100 Subject: [PATCH] Add Thread Watcher. --- 4chan_x.user.js | 123 ++++++++++++++++++++++++++++++++++++++++++-- src/config.coffee | 4 +- src/features.coffee | 97 +++++++++++++++++++++++++++++++++- src/main.coffee | 1 + 4 files changed, 216 insertions(+), 9 deletions(-) diff --git a/4chan_x.user.js b/4chan_x.user.js index 40348d1a1..2830868c5 100644 --- a/4chan_x.user.js +++ b/4chan_x.user.js @@ -43,7 +43,7 @@ */ (function() { - var $, $$, Anonymize, ArchiveLink, AutoGIF, Board, Build, Clone, Conf, Config, DeleteLink, DownloadLink, ExpandComment, ExpandThread, Favicon, FileInfo, Filter, Get, Header, ImageExpand, ImageHover, Main, Menu, Notification, Polyfill, Post, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Recursive, Redirect, RelativeDates, ReplyHiding, ReportLink, RevealSpoilers, Sauce, Settings, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, Time, UI, Unread, d, doc, g, + var $, $$, Anonymize, ArchiveLink, AutoGIF, Board, Build, Clone, Conf, Config, DeleteLink, DownloadLink, ExpandComment, ExpandThread, Favicon, FileInfo, Filter, Get, Header, ImageExpand, ImageHover, Main, Menu, Notification, Polyfill, Post, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Recursive, Redirect, RelativeDates, ReplyHiding, ReportLink, RevealSpoilers, Sauce, Settings, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, d, doc, g, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; @@ -93,8 +93,8 @@ 'Thread Excerpt': [true, 'Show an excerpt of the thread in the tab title.'], 'Thread Stats': [true, 'Display reply and image count.'], 'Thread Watcher': [true, 'Bookmark threads.'], - 'Auto Watch': [true, 'Automatically watch threads that you start.'], - 'Auto Watch Reply': [false, 'Automatically watch threads that you reply to.'] + 'Auto Watch': [true, 'Automatically watch threads you start.'], + 'Auto Watch Reply': [false, 'Automatically watch threads you reply to.'] }, Posting: { 'Quick Reply': [true, 'WMD.'], @@ -1112,7 +1112,7 @@ var link, settings; link = $.el('a', { className: 'settings-link', - textContent: '4chan X Settings', + textContent: '4chan X Alpha Settings', href: 'javascript:;' }); $.on(link, 'click', Settings.open); @@ -4231,7 +4231,7 @@ this.postCountEl = $('#post-count', this.dialog); this.fileCountEl = $('#file-count', this.dialog); this.fileLimit = (function() { - switch (g.BOARD) { + switch (g.BOARD.ID) { case 'a': case 'b': case 'v': @@ -4563,6 +4563,118 @@ } }; + ThreadWatcher = { + init: function() { + if (g.VIEW === 'catalog' || !Conf['Thread Watcher']) { + return; + } + this.dialog = UI.dialog('watcher', 'top: 50px; left: 0px;', '
Thread Watcher
'); + $.on(d, 'QRPostSuccessful', this.cb.post); + $.on(d, '4chanXInitFinished', this.ready); + $.sync('WatchedThreads', this.refresh); + return Thread.prototype.callbacks.push({ + name: 'Thread Watcher', + cb: this.node + }); + }, + node: function() { + var favicon, op; + op = this.posts[this]; + favicon = $.el('img', { + className: 'favicon' + }); + $.on(favicon, 'click', ThreadWatcher.cb.toggle); + $.before($('input', op.nodes.post), favicon); + if (g.VIEW === 'thread' && this.ID === $.get('AutoWatch', 0)) { + ThreadWatcher.watch(this); + return $["delete"]('AutoWatch'); + } + }, + ready: function() { + ThreadWatcher.refresh(); + return $.add(d.body, ThreadWatcher.dialog); + }, + refresh: function(watched) { + var ID, board, div, favicon, id, link, nodes, op, props, thread, x, _ref, _ref1; + watched || (watched = $.get('WatchedThreads', {})); + nodes = [$('.move', ThreadWatcher.dialog)]; + for (board in watched) { + _ref = watched[board]; + for (id in _ref) { + props = _ref[id]; + x = $.el('a', { + textContent: '×', + href: 'javascript:;' + }); + $.on(x, 'click', ThreadWatcher.cb.x); + link = $.el('a', props); + link.title = link.textContent; + div = $.el('div'); + $.add(div, [x, $.tn(' '), link]); + nodes.push(div); + } + } + ThreadWatcher.dialog.innerHTML = ''; + $.add(ThreadWatcher.dialog, nodes); + watched = watched[g.BOARD] || {}; + _ref1 = g.BOARD.threads; + for (ID in _ref1) { + thread = _ref1[ID]; + op = thread.posts[thread]; + favicon = $('.favicon', op.nodes.post); + favicon.src = ID in watched ? Favicon["default"] : Favicon.empty; + } + }, + cb: { + toggle: function() { + return ThreadWatcher.toggle(Get.postFromNode(this).thread); + }, + x: function() { + var thread; + thread = this.nextElementSibling.pathname.split('/'); + return ThreadWatcher.unwatch(thread[1], thread[3]); + }, + post: function(e) { + var postID, threadID, _ref; + _ref = e.detail, postID = _ref.postID, threadID = _ref.threadID; + if (threadID === '0') { + if (Conf['Auto Watch']) { + return $.set('AutoWatch', +postID); + } + } else if (Conf['Auto Watch Reply']) { + return ThreadWatcher.watch(g.BOARD.threads[threadID]); + } + } + }, + toggle: function(thread) { + var op; + op = thread.posts[thread]; + if ($('.favicon', op.nodes.post).src === Favicon.empty) { + return ThreadWatcher.watch(thread); + } else { + return ThreadWatcher.unwatch(thread.board, thread.ID); + } + }, + unwatch: function(board, threadID) { + var watched; + watched = $.get('WatchedThreads', {}); + delete watched[board][threadID]; + ThreadWatcher.refresh(watched); + return $.set('WatchedThreads', watched); + }, + watch: function(thread) { + var watched, _name; + watched = $.get('WatchedThreads', {}); + watched[_name = thread.board] || (watched[_name] = {}); + watched[thread.board][thread] = { + href: "/" + thread.board + "/res/" + thread, + textContent: Get.threadExcerpt(thread) + }; + ThreadWatcher.refresh(watched); + return $.set('WatchedThreads', watched); + } + }; + QR = { init: function() { var link; @@ -5945,6 +6057,7 @@ initFeature('Unread', Unread); initFeature('Thread Stats', ThreadStats); initFeature('Thread Updater', ThreadUpdater); + initFeature('Thread Watcher', ThreadWatcher); console.timeEnd('All initializations'); $.on(d, '4chanMainInit', Main.initStyle); return $.ready(Main.initReady); diff --git a/src/config.coffee b/src/config.coffee index ce99e0512..46973074d 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -39,8 +39,8 @@ Config = 'Thread Excerpt': [true, 'Show an excerpt of the thread in the tab title.'] 'Thread Stats': [true, 'Display reply and image count.'] 'Thread Watcher': [true, 'Bookmark threads.'] - 'Auto Watch': [true, 'Automatically watch threads that you start.'] - 'Auto Watch Reply': [false, 'Automatically watch threads that you reply to.'] + 'Auto Watch': [true, 'Automatically watch threads you start.'] + 'Auto Watch Reply': [false, 'Automatically watch threads you reply to.'] Posting: 'Quick Reply': [true, 'WMD.'] 'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.'] diff --git a/src/features.coffee b/src/features.coffee index e4f51e2d3..6ddf28429 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -119,7 +119,7 @@ Settings = # 4chan X settings link link = $.el 'a', className: 'settings-link' - textContent: '4chan X Settings' + textContent: '<%= meta.name %> Settings' href: 'javascript:;' $.on link, 'click', Settings.open $.event 'AddMenuEntry', @@ -2686,7 +2686,7 @@ ThreadStats = @postCountEl = $ '#post-count', @dialog @fileCountEl = $ '#file-count', @dialog @fileLimit = # XXX boards config, need up to date data on this, check browser - switch g.BOARD + switch g.BOARD.ID when 'a', 'b', 'v', 'co', 'mlp' 251 when 'vg' @@ -2950,3 +2950,96 @@ ThreadUpdater = newPosts: posts deletedPosts: deletedPosts deletedFiles: deletedFiles + +ThreadWatcher = + init: -> + return if g.VIEW is 'catalog' or !Conf['Thread Watcher'] + @dialog = UI.dialog 'watcher', 'top: 50px; left: 0px;', + '
Thread Watcher
' + + $.on d, 'QRPostSuccessful', @cb.post + $.on d, '4chanXInitFinished', @ready + $.sync 'WatchedThreads', @refresh + + Thread::callbacks.push + name: 'Thread Watcher' + cb: @node + + node: -> + op = @posts[@] + favicon = $.el 'img', + className: 'favicon' + $.on favicon, 'click', ThreadWatcher.cb.toggle + $.before $('input', op.nodes.post), favicon + if g.VIEW is 'thread' and @ID is $.get 'AutoWatch', 0 + ThreadWatcher.watch @ + $.delete 'AutoWatch' + + ready: -> + ThreadWatcher.refresh() + $.add d.body, ThreadWatcher.dialog + + refresh: (watched) -> + watched or= $.get 'WatchedThreads', {} + nodes = [$('.move', ThreadWatcher.dialog)] + for board of watched + for id, props of watched[board] + x = $.el 'a', + textContent: '×' + href: 'javascript:;' + $.on x, 'click', ThreadWatcher.cb.x + link = $.el 'a', props + link.title = link.textContent + + div = $.el 'div' + $.add div, [x, $.tn(' '), link] + nodes.push div + + ThreadWatcher.dialog.innerHTML = '' + $.add ThreadWatcher.dialog, nodes + + watched = watched[g.BOARD] or {} + for ID, thread of g.BOARD.threads + op = thread.posts[thread] + favicon = $ '.favicon', op.nodes.post + favicon.src = if ID of watched + Favicon.default + else + Favicon.empty + return + + cb: + toggle: -> + ThreadWatcher.toggle Get.postFromNode(@).thread + x: -> + thread = @nextElementSibling.pathname.split '/' + ThreadWatcher.unwatch thread[1], thread[3] + post: (e) -> + {postID, threadID} = e.detail + if threadID is '0' + if Conf['Auto Watch'] + $.set 'AutoWatch', +postID + else if Conf['Auto Watch Reply'] + ThreadWatcher.watch g.BOARD.threads[threadID] + + toggle: (thread) -> + op = thread.posts[thread] + if $('.favicon', op.nodes.post).src is Favicon.empty + ThreadWatcher.watch thread + else + ThreadWatcher.unwatch thread.board, thread.ID + + unwatch: (board, threadID) -> + watched = $.get 'WatchedThreads', {} + delete watched[board][threadID] + ThreadWatcher.refresh watched + $.set 'WatchedThreads', watched + + watch: (thread) -> + watched = $.get 'WatchedThreads', {} + watched[thread.board] or= {} + watched[thread.board][thread] = + href: "/#{thread.board}/res/#{thread}" + textContent: Get.threadExcerpt thread + ThreadWatcher.refresh watched + $.set 'WatchedThreads', watched diff --git a/src/main.coffee b/src/main.coffee index 2bbdbc2e2..a6fe0a1fc 100644 --- a/src/main.coffee +++ b/src/main.coffee @@ -343,6 +343,7 @@ Main = initFeature 'Unread', Unread initFeature 'Thread Stats', ThreadStats initFeature 'Thread Updater', ThreadUpdater + initFeature 'Thread Watcher', ThreadWatcher console.timeEnd 'All initializations' $.on d, '4chanMainInit', Main.initStyle