From 4037133eb5132f954e42abcf72de2450b9b8c75f Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sat, 10 Aug 2013 19:49:25 +0200 Subject: [PATCH 01/15] Remove useless comment. --- src/Quotelinks/QuoteYou.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Quotelinks/QuoteYou.coffee b/src/Quotelinks/QuoteYou.coffee index acc4d2e64..7955373d9 100644 --- a/src/Quotelinks/QuoteYou.coffee +++ b/src/Quotelinks/QuoteYou.coffee @@ -8,7 +8,6 @@ QuoteYou = name: 'Mark Quotes of You' cb: @node node: -> - # Stop there if it's a clone. return if @isClone for quotelink in @nodes.quotelinks when QR.db.get Get.postDataFromLink quotelink $.add quotelink, $.tn QuoteYou.text From fbe724a174d5bf0a42f67df5f6d4ef9a871d1c19 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sat, 10 Aug 2013 23:50:04 +0200 Subject: [PATCH 02/15] Fix #1162. --- src/General/Build.coffee | 58 ++++++++++++++++---------- src/Miscellaneous/ExpandComment.coffee | 5 +++ 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/General/Build.coffee b/src/General/Build.coffee index 0d39bc343..0cbe5929f 100644 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -27,7 +27,7 @@ Build = date: data.now dateUTC: data.time comment: data.com - capReps: data.capcode_replies + capcodeReplies: data.capcode_replies # thread status isSticky: !!data.sticky isClosed: !!data.closed @@ -55,7 +55,7 @@ Build = postID, threadID, boardID name, capcode, tripcode, uniqueID, email, subject, flagCode, flagName, date, dateUTC isSticky, isClosed - comment, capReps + comment, capcodeReplies file } = o isOP = postID is threadID @@ -187,26 +187,6 @@ Build = else '' - capcodeReplies = '' - if capReps - generateCapcodeReplies = (capcodeType, array) -> - "#{ - switch capcodeType - when 'admin' - 'Administrator' - when 'mod' - 'Moderator' - when 'developer' - 'Developer' - } Repl#{if array.length > 1 then 'ies' else 'y'}: #{ - array.map (ID) -> - ">>#{ID}" - .join ' ' - }
" - for capcodeType, array of capReps - capcodeReplies += generateCapcodeReplies capcodeType, array - capcodeReplies = "

#{capcodeReplies}" - container = $.el 'div', id: "pc#{postID}" className: "postContainer #{if isOP then 'op' else 'reply'}Container" @@ -259,7 +239,7 @@ Build = (if isOP then '' else fileHTML) + - "
#{comment or ''}#{capcodeReplies}
" + + "
#{comment or ''}
" + '' @@ -268,4 +248,36 @@ Build = continue if href[0] is '/' # Cross-board quote, or board link quote.href = "/#{boardID}/res/#{href}" # Fix pathnames + Build.capcodeReplies {boardID, threadID, root: container, capcodeReplies} + container + + capcodeReplies: ({boardID, threadID, bq, root, capcodeReplies}) -> + return unless capcodeReplies + + generateCapcodeReplies = (capcodeType, array) -> + "#{ + switch capcodeType + when 'admin' + 'Administrator' + when 'mod' + 'Moderator' + when 'developer' + 'Developer' + } Repl#{if array.length > 1 then 'ies' else 'y'}: #{ + array.map (ID) -> + ">>#{ID}" + .join ' ' + }
" + html = [] + for capcodeType, array of capcodeReplies + html.push generateCapcodeReplies capcodeType, array + + bq or= $ 'blockquote', root + $.add bq, [ + $.el 'br' + $.el 'br' + $.el 'span', + className: 'capcodeReplies' + innerHTML: html.join '' + ] diff --git a/src/Miscellaneous/ExpandComment.coffee b/src/Miscellaneous/ExpandComment.coffee index 14e6a4d48..723bbd771 100644 --- a/src/Miscellaneous/ExpandComment.coffee +++ b/src/Miscellaneous/ExpandComment.coffee @@ -49,6 +49,11 @@ ExpandComment = href = quote.getAttribute 'href' continue if href[0] is '/' # Cross-board quote, or board link quote.href = "/#{post.board}/res/#{href}" # Fix pathnames + Build.capcodeReplies + boardID: post.board.ID + threadID: post.thread.ID + bq: clone + capcodeReplies: postObj.capcode_replies post.nodes.shortComment = comment $.replace comment, clone post.nodes.comment = post.nodes.longComment = clone From 05a460d500df48d0ff9b3f169b41ed50127b16f1 Mon Sep 17 00:00:00 2001 From: Wohlfe Date: Sat, 10 Aug 2013 21:47:54 -0400 Subject: [PATCH 03/15] Removed images from some boards. --- json/archives.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/json/archives.json b/json/archives.json index 95ac1a37a..efde782d3 100644 --- a/json/archives.json +++ b/json/archives.json @@ -51,7 +51,7 @@ "https": true, "software": "foolfuuka", "boards": ["adv", "asp", "cm", "e", "i", "lgbt", "n", "o", "p", "pol", "s", "s4s", "t", "trv", "y"], - "files": ["adv", "asp", "cm", "e", "i", "lgbt", "n", "o", "p", "s", "s4s", "t", "trv", "y"] + "files": ["cm", "e", "i", "n", "o", "p", "s", "trv", "y"] }, { "uid": 12, "name": "fap archive", From ffd98de1109d39020afe0fc9cd8220bdcdceec9d Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sun, 11 Aug 2013 13:09:25 +0200 Subject: [PATCH 04/15] Polyfill canvas toBlob(). --- lib/polyfill.coffee | 10 ++++++++++ src/Posting/QR.coffee | 14 +------------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/polyfill.coffee b/lib/polyfill.coffee index 5d9ddc124..3cec1905a 100644 --- a/lib/polyfill.coffee +++ b/lib/polyfill.coffee @@ -1,6 +1,16 @@ Polyfill = init: -> + Polyfill.toBlob() Polyfill.visibility() + toBlob: -> + HTMLCanvasElement::toBlob or= (cb) -> + data = atob @toDataURL()[22..] + # DataUrl to Binary code from Aeosynth's 4chan X repo + l = data.length + ui8a = new Uint8Array l + for i in [0...l] + ui8a[i] = data.charCodeAt i + cb new Blob [ui8a], type: 'image/png' visibility: -> # page visibility API return unless 'webkitHidden' of document diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index fc7b0357b..791802066 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -595,21 +595,9 @@ QR = cv.width = img.width = width cv.getContext('2d').drawImage img, 0, 0, width, height URL.revokeObjectURL fileURL - applyBlob = (blob) => + cv.toBlob (blob) => @URL = URL.createObjectURL blob @nodes.el.style.backgroundImage = "url(#{@URL})" - if cv.toBlob - cv.toBlob applyBlob - return - data = atob cv.toDataURL().split(',')[1] - - # DataUrl to Binary code from Aeosynth's 4chan X repo - l = data.length - ui8a = new Uint8Array l - for i in [0...l] - ui8a[i] = data.charCodeAt i - - applyBlob new Blob [ui8a], type: 'image/png' fileURL = URL.createObjectURL @file img.src = fileURL From b2ec75510ba3aaec5a39f84794958f2ddbf20438 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sun, 11 Aug 2013 17:10:07 +0200 Subject: [PATCH 05/15] I couldn't find the exact cause, I suspect people are just adblocking random elements and expect not to break things. --- src/General/Header.coffee | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/General/Header.coffee b/src/General/Header.coffee index 4c76991b1..47588d98c 100644 --- a/src/General/Header.coffee +++ b/src/General/Header.coffee @@ -89,17 +89,13 @@ Header = if a = $ "a[href*='/#{g.BOARD}/']", nav a.className = 'current' fullBoardList = $ '#full-board-list', Header.bar - # XXX Getting weird reports here of - # "Header" initialization crashed. TypeError: Cannot read property 'innerHTML' of null - # Let's try to find the cause. - if nav - fullBoardList.innerHTML = nav.innerHTML - $.rm $ '#navtopright', fullBoardList - btn = $.el 'span', - className: 'hide-board-list-button brackets-wrap' - innerHTML: ' - ' - $.on btn, 'click', Header.toggleBoardList - $.add fullBoardList, btn + fullBoardList.innerHTML = nav.innerHTML + $.rm $ '#navtopright', fullBoardList + btn = $.el 'span', + className: 'hide-board-list-button brackets-wrap' + innerHTML: ' - ' + $.on btn, 'click', Header.toggleBoardList + $.add fullBoardList, btn Header.setCustomNav Conf['Custom Board Navigation'] Header.generateBoardList Conf['boardnav'] From e0cb19ac53f625cd6433ba73ea98d31660f58239 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sun, 11 Aug 2013 18:02:22 +0200 Subject: [PATCH 06/15] $.xhr will now deal with `Last-Modified` and `If-Modified-Since` headers itself. --- lib/$.coffee | 28 +++++++++++++++++----------- src/Monitoring/ThreadStats.coffee | 4 +--- src/Monitoring/ThreadUpdater.coffee | 11 ++--------- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/lib/$.coffee b/lib/$.coffee index 7e25bb5f2..0fc239340 100644 --- a/lib/$.coffee +++ b/lib/$.coffee @@ -36,17 +36,23 @@ $.extend = (object, properties) -> for key, val of properties object[key] = val return -$.ajax = (url, options, extra={}) -> - {type, headers, upCallbacks, form, sync} = extra - r = new XMLHttpRequest() - type or= form and 'post' or 'get' - r.open type, url, !sync - for key, val of headers - r.setRequestHeader key, val - $.extend r, options - $.extend r.upload, upCallbacks - r.send form - r +$.ajax = do -> + # Status Code 304: Not modified + # With the `If-Modified-Since` header we only receive the HTTP headers and no body for 304 responses. + # This saves a lot of bandwidth and CPU time for both the users and the servers. + lastModified = {} + (url, options, extra={}) -> + {type, whenModified, upCallbacks, form, sync} = extra + r = new XMLHttpRequest() + type or= form and 'post' or 'get' + r.open type, url, !sync + if whenModified + r.setRequestHeader 'If-Modified-Since', lastModified[url] or '0' + $.on r, 'load', -> lastModified[url] = r.getResponseHeader 'Last-Modified' + $.extend r, options + $.extend r.upload, upCallbacks + r.send form + r $.cache = do -> reqs = {} (url, cb, options) -> diff --git a/src/Monitoring/ThreadStats.coffee b/src/Monitoring/ThreadStats.coffee index cfe1658a7..6f35f6224 100644 --- a/src/Monitoring/ThreadStats.coffee +++ b/src/Monitoring/ThreadStats.coffee @@ -8,7 +8,6 @@ ThreadStats = @postCountEl = $ '#post-count', @dialog @fileCountEl = $ '#file-count', @dialog @pageCountEl = $ '#page-count', @dialog - @lastModified = '0' Thread::callbacks.push name: 'Thread Stats' @@ -41,9 +40,8 @@ ThreadStats = return setTimeout ThreadStats.fetchPage, 2 * $.MINUTE $.ajax "//api.4chan.org/#{ThreadStats.thread.board}/threads.json", onload: ThreadStats.onThreadsLoad, - headers: 'If-Modified-Since': ThreadStats.lastModified + whenModified: true onThreadsLoad: -> - ThreadStats.lastModified = @getResponseHeader 'Last-Modified' return if @status isnt 200 pages = JSON.parse @response for page in pages diff --git a/src/Monitoring/ThreadUpdater.coffee b/src/Monitoring/ThreadUpdater.coffee index ceb1a68bd..146027c65 100644 --- a/src/Monitoring/ThreadUpdater.coffee +++ b/src/Monitoring/ThreadUpdater.coffee @@ -25,7 +25,6 @@ ThreadUpdater = ThreadUpdater.root = @OP.nodes.root.parentNode ThreadUpdater.lastPost = +ThreadUpdater.root.lastElementChild.id.match(/\d+/)[0] ThreadUpdater.outdateCount = 0 - ThreadUpdater.lastModified = '0' for input in $$ 'input', ThreadUpdater.dialog if input.type is 'checkbox' @@ -95,7 +94,6 @@ ThreadUpdater = when 200 g.DEAD = false ThreadUpdater.parse JSON.parse(req.response).posts - ThreadUpdater.lastModified = req.getResponseHeader 'Last-Modified' ThreadUpdater.set 'timer', ThreadUpdater.getInterval() when 404 g.DEAD = true @@ -108,12 +106,7 @@ ThreadUpdater = thread: ThreadUpdater.thread else ThreadUpdater.outdateCount++ - ThreadUpdater.set 'timer', ThreadUpdater.getInterval() - ### - Status Code 304: Not modified - By sending the `If-Modified-Since` header we get a proper status code, and no response. - This saves bandwidth for both the user and the servers and avoid unnecessary computation. - ### + ThreadUpdater.set 'timer', ThreadUpdater.getInterval() [text, klass] = if req.status is 304 [null, null] else @@ -159,7 +152,7 @@ ThreadUpdater = ThreadUpdater.req.abort() url = "//api.4chan.org/#{ThreadUpdater.thread.board}/res/#{ThreadUpdater.thread}.json" ThreadUpdater.req = $.ajax url, onloadend: ThreadUpdater.cb.load, - headers: 'If-Modified-Since': ThreadUpdater.lastModified + whenModified: true updateThreadStatus: (title, OP) -> titleLC = title.toLowerCase() From 35cfa595bfb02e81389e9308df9c4dae137b1def Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sun, 11 Aug 2013 23:41:49 +0200 Subject: [PATCH 07/15] Shave a line. --- src/Miscellaneous/ExpandComment.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Miscellaneous/ExpandComment.coffee b/src/Miscellaneous/ExpandComment.coffee index 723bbd771..8d87de91e 100644 --- a/src/Miscellaneous/ExpandComment.coffee +++ b/src/Miscellaneous/ExpandComment.coffee @@ -10,8 +10,7 @@ ExpandComment = $.on a, 'click', ExpandComment.cb cb: (e) -> e.preventDefault() - post = Get.postFromNode @ - ExpandComment.expand post + ExpandComment.expand Get.postFromNode @ expand: (post) -> if post.nodes.longComment and !post.nodes.longComment.parentNode $.replace post.nodes.shortComment, post.nodes.longComment From 6d36518ea9b683ec631abb7a51b9b63941e75a9b Mon Sep 17 00:00:00 2001 From: Mayhem Date: Mon, 12 Aug 2013 17:26:41 +0200 Subject: [PATCH 08/15] Remove the `Check for Updates` setting. It's not useful enough to justify keeping it. --- CHANGELOG.md | 3 +++ src/General/Config.coffee | 1 - src/General/Main.coffee | 47 ------------------------------------- src/General/Settings.coffee | 1 - 4 files changed, 3 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b04ffcf..9f7c26991 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +- Removed the `Check for Updates` setting: + - Your browser/userscript manager should handle updates itself automatically. + ## 3.6.0 - *2013-08-10* - **New feature**: `Show Dice Roll` diff --git a/src/General/Config.coffee b/src/General/Config.coffee index 7cbc64d18..7277f3897 100644 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -13,7 +13,6 @@ Config = 'Index Navigation': [false, 'Add buttons to navigate between threads.'] 'Reply Navigation': [false, 'Add buttons to navigate to top / bottom of thread.'] 'Show Dice Roll': [true, 'Show dice that were entered into the email field.'] - 'Check for Updates': [true, 'Notify when updated versions of <%= meta.name %> are available.'] 'Filtering': 'Anonymize': [false, 'Make everyone Anonymous.'] 'Filter': [true, 'Self-moderation placebo.'] diff --git a/src/General/Main.coffee b/src/General/Main.coffee index 92a543676..eb10afe7f 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -210,7 +210,6 @@ Main = <% } %> $.event '4chanXInitFinished' - Main.checkUpdate() callbackNodes: (klass, nodes) -> # get the nodes' length only once @@ -244,52 +243,6 @@ Main = obj.callback.isAddon = true Klass::callbacks.push obj.callback - checkUpdate: -> - return unless Conf['Check for Updates'] and Main.isThisPageLegit() - # Check for updates after 7 days since the last update. - # After that, check for updates every day if we still haven't updated. - now = Date.now() - freq = 7 * $.DAY - items = - lastupdate: 0 - lastchecked: 0 - $.get items, (items) -> - if items.lastupdate > now - freq or items.lastchecked > now - $.DAY - return - $.ajax '<%= meta.page %><%= meta.buildsPath %>version', onload: -> - return unless @status is 200 - version = @response - return unless /^\d\.\d+\.\d+$/.test version - if g.VERSION is version - # Don't check for updates too frequently if there wasn't one in a 'long' time. - $.set 'lastupdate', now - return - $.set 'lastchecked', now - # dmichnowicz - # 19 juin 2013 09:39:28 - # innerHTML is not allowed in conjunction with external resources: [...] - # - # mayhemydg - # 19 juin 2013 09:43:14 - # Will it be good if I make sure to match a version number? - # - # dmichnowicz - # 19 juin 2013 09:46:12 - # Yes. - # - # mayhemydg - # 19 juin 2013 13:04:30 - # Well actually there is nothing to fix, the following code makes sure this is a - # valid version number and precedes the innerHTML part: [see /^\d\.\d+\.\d+$/.test] - # - # dmichnowicz - # 21 juin 2013 04:52:37 - # OK but anyway please remove that line of code. - el = $.el 'span', - textContent: "Update: <%= meta.name %> v#{version} is out, get it " - el.innerHTML += ' target=_blank>here.' - new Notification 'info', el, 120 - handleErrors: (errors) -> unless errors instanceof Array error = errors diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee index 660d78f78..e5241d37d 100644 --- a/src/General/Settings.coffee +++ b/src/General/Settings.coffee @@ -38,7 +38,6 @@ Settings = $.set archives: Conf['archives'] lastarchivecheck: now - lastupdate: now previousversion: g.VERSION Settings.addSection 'Main', Settings.main From 88b6f97fced6f98e63795c73afd6026ecae44eb1 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Mon, 12 Aug 2013 17:30:00 +0200 Subject: [PATCH 09/15] Drop archives.json update period from 4 to 2 days. --- src/Archive/Redirect.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Archive/Redirect.coffee b/src/Archive/Redirect.coffee index 1da3f9f8a..9fb542457 100644 --- a/src/Archive/Redirect.coffee +++ b/src/Archive/Redirect.coffee @@ -31,7 +31,7 @@ Redirect = now = Date.now() # Update the list of archives every 4 days. # The list is also updated when 4chan X gets updated. - return if lastarchivecheck > now - 4 * $.DAY + return if lastarchivecheck > now - 2 * $.DAY $.ajax '<%= meta.page %>json/archives.json', onload: -> return unless @status is 200 Conf['archives'] = JSON.parse @response From 6261160891a982c5e7e16b2d0d72b33bccd35bc9 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Mon, 12 Aug 2013 19:20:16 +0200 Subject: [PATCH 10/15] Start the Thread Watcher rewrite. #99 Fix #1112. --- CHANGELOG.md | 9 + css/style.css | 49 ++++-- html/Monitoring/ThreadWatcher.html | 5 + src/General/Config.coffee | 6 +- src/General/DataBoard.coffee | 16 +- src/General/Settings.coffee | 6 +- src/Images/ImageExpand.coffee | 19 +-- src/Monitoring/ThreadWatcher.coffee | 251 ++++++++++++++++++++-------- 8 files changed, 256 insertions(+), 105 deletions(-) create mode 100644 html/Monitoring/ThreadWatcher.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f7c26991..002e98a51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +- **Thread Watcher** rewrite: + - It is now possible to open all watched threads via the `Open all threads` button in the Thread Watcher's menu. + - Added the `Current Board` setting to switch between showing watched threads from the current board or all boards, disabled by default. + - About dead (404'd) threads: + - Dead threads will be typographically indicated with a strikethrough. + - Dead threads will directly link to the corresponding archive when possible. + - A button to prune all 404'd threads from the list is now available. + - The current thread is now highlighted in the list of watched threads. + - Watching the current thread can be done in the Header's menu too. - Removed the `Check for Updates` setting: - Your browser/userscript manager should handle updates itself automatically. diff --git a/css/style.css b/css/style.css index 28dfb5bed..b29c6769c 100644 --- a/css/style.css +++ b/css/style.css @@ -83,13 +83,13 @@ a[href="javascript:;"] { #qr { z-index: 30; } -#watcher:hover { +#thread-watcher:hover { z-index: 20; } #header { z-index: 10; } -#watcher { +#thread-watcher { z-index: 5; } @@ -432,28 +432,40 @@ a.hide-announcement { } /* Thread Watcher */ -#watcher { - padding-bottom: 3px; +#thread-watcher { + max-width: 200px; + min-width: 150px; + padding: 3px; position: absolute; +} +#thread-watcher > div:first-child { + display: -webkit-flex; + display: flex; + -webkit-align-items: center; + align-items: center; +} +#thread-watcher .move { + -webkit-flex: 1; + flex: 1; +} +#watched-threads:not(:hover) { + max-height: 150px; overflow: hidden; +} +#watched-threads div { + overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; } -#watcher:not(:hover) { - max-height: 220px; +#watched-threads .current { + font-weight: 700; } -#watcher > .move { - padding-top: 3px; -} -#watcher > div { - max-width: 200px; - overflow: hidden; - padding-left: 3px; - padding-right: 3px; - text-overflow: ellipsis; -} -#watcher a { +#watched-threads a { text-decoration: none; } +#watched-threads .dead-thread a[title] { + text-decoration: line-through; +} /* Thread Stats */ #thread-stats { @@ -878,6 +890,9 @@ a.hide-announcement { text-decoration: none; white-space: nowrap; } +.entry.disabled { + color: graytext !important; +} .entry.has-submenu { padding-right: 20px; } diff --git a/html/Monitoring/ThreadWatcher.html b/html/Monitoring/ThreadWatcher.html new file mode 100644 index 000000000..d3878c4cd --- /dev/null +++ b/html/Monitoring/ThreadWatcher.html @@ -0,0 +1,5 @@ +
+ Thread Watcher + [] +
+
diff --git a/src/General/Config.coffee b/src/General/Config.coffee index 7277f3897..23e36d461 100644 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -46,8 +46,6 @@ Config = 'Thread Excerpt': [true, 'Show an excerpt of the thread in the tab title.'] 'Thread Stats': [true, 'Display reply, image, and page count.'] 'Thread Watcher': [true, 'Bookmark threads.'] - 'Auto Watch': [true, 'Automatically watch threads you start.'] - 'Auto Watch Reply': [false, 'Automatically watch threads you reply to.'] 'Posting': 'Quick Reply': [true, 'All-in-one form to reply, create threads, automate dumping and more.'] 'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.'] @@ -78,6 +76,10 @@ Config = 'Fit height': [false, ''] 'Expand spoilers': [false, 'Expand all images along with spoilers.'] 'Expand from here': [true, 'Expand all images only from current position to thread end.'] + threadWatcher: + 'Current Board': [false, 'Only show watched threads from the current board.'] + 'Auto Watch': [true, 'Automatically watch threads you start.'] + 'Auto Watch Reply': [false, 'Automatically watch threads you reply to.'] filter: name: """ # Filter any namefags: diff --git a/src/General/DataBoard.coffee b/src/General/DataBoard.coffee index bbdbbccac..aa6ef84f8 100644 --- a/src/General/DataBoard.coffee +++ b/src/General/DataBoard.coffee @@ -1,10 +1,10 @@ -DataBoards = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts'] +DataBoards = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads'] class DataBoard - constructor: (@key, sync) -> + constructor: (@key, sync, dontClean) -> @data = Conf[key] $.sync key, @onSync.bind @ - @clean() + @clean() unless dontClean return unless sync # Chrome also fires the onChanged callback on the current tab, # so we only start syncing when we're ready. @@ -13,6 +13,8 @@ class DataBoard @sync = sync $.on d, '4chanXInitFinished', init + save: -> + $.set @key, @data delete: ({boardID, threadID, postID}) -> if postID delete @data.boards[boardID][threadID][postID] @@ -22,7 +24,7 @@ class DataBoard @deleteIfEmpty {boardID} else delete @data.boards[boardID] - $.set @key, @data + @save() deleteIfEmpty: ({boardID, threadID}) -> if threadID unless Object.keys(@data.boards[boardID][threadID]).length @@ -37,7 +39,7 @@ class DataBoard (@data.boards[boardID] or= {})[threadID] = val else @data.boards[boardID] = val - $.set @key, @data + @save() get: ({boardID, threadID, postID, defaultValue}) -> if board = @data.boards[boardID] unless threadID @@ -65,7 +67,7 @@ class DataBoard for boardID of @data.boards @ajaxClean boardID - $.set @key, @data + @save() ajaxClean: (boardID) -> $.cache "//api.4chan.org/#{boardID}/threads.json", (e) => if e.target.status is 404 @@ -80,7 +82,7 @@ class DataBoard threads[thread.no] = board[thread.no] @data.boards[boardID] = threads @deleteIfEmpty {boardID} - $.set @key, @data + @save() onSync: (data) -> @data = data or boards: {} diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee index e5241d37d..89ad940ca 100644 --- a/src/General/Settings.coffee +++ b/src/General/Settings.coffee @@ -169,7 +169,6 @@ Settings = data = version: g.VERSION date: now - Conf['WatchedThreads'] = {} for db in DataBoards Conf[db] = boards: {} # Make sure to export the most recent data. @@ -279,7 +278,10 @@ Settings = for key, val of Config.hotkeys when key of data.Conf data.Conf[key] = data.Conf[key].replace(/ctrl|alt|meta/g, (s) -> "#{s[0].toUpperCase()}#{s[1..]}").replace /(^|.+\+)[A-Z]$/g, (s) -> "Shift+#{s[0...-1]}#{s[-1..].toLowerCase()}" - data.Conf.WatchedThreads = data.WatchedThreads + data.Conf['WatchedThreads'] = data.WatchedThreads + if data.Conf['WatchedThreads'] + data.Conf['watchedThreads'] = boards: ThreadWatcher.convert data.Conf['WatchedThreads'] + delete data.Conf['WatchedThreads'] $.set data.Conf convertSettings: (data, map) -> for prevKey, newKey of map diff --git a/src/Images/ImageExpand.coffee b/src/Images/ImageExpand.coffee index 9a4d8dd3f..6a3a06f20 100644 --- a/src/Images/ImageExpand.coffee +++ b/src/Images/ImageExpand.coffee @@ -159,8 +159,8 @@ ImageExpand = {createSubEntry} = ImageExpand.menu subEntries = [] - for key, conf of Config.imageExpansion - subEntries.push createSubEntry key, conf + for name, conf of Config.imageExpansion + subEntries.push createSubEntry name, conf[1] $.event 'AddMenuEntry', type: 'header' @@ -168,15 +168,14 @@ ImageExpand = order: 80 subEntries: subEntries - createSubEntry: (type, config) -> + createSubEntry: (name, desc) -> label = $.el 'label', - innerHTML: " #{type}" + innerHTML: " #{name}" + title: desc input = label.firstElementChild - if type in ['Fit width', 'Fit height'] + if name in ['Fit width', 'Fit height'] $.on input, 'change', ImageExpand.cb.setFitness - if config - label.title = config[1] - input.checked = Conf[type] - $.event 'change', null, input - $.on input, 'change', $.cb.checked + input.checked = Conf[name] + $.event 'change', null, input + $.on input, 'change', $.cb.checked el: label diff --git a/src/Monitoring/ThreadWatcher.coffee b/src/Monitoring/ThreadWatcher.coffee index 8fbd3d757..1629209d9 100644 --- a/src/Monitoring/ThreadWatcher.coffee +++ b/src/Monitoring/ThreadWatcher.coffee @@ -1,99 +1,216 @@ ThreadWatcher = init: -> - return if g.VIEW is 'catalog' or !Conf['Thread Watcher'] - @dialog = UI.dialog 'watcher', 'top: 50px; left: 0px;', - '
Thread Watcher
' + return if !Conf['Thread Watcher'] + @db = new DataBoard 'watchedThreads', @refresh, true + @dialog = UI.dialog 'thread-watcher', 'top: 50px; left: 0px;', """ + <%= grunt.file.read('html/Monitoring/ThreadWatcher.html').replace(/>\s+<').trim() %> + """ + @list = @dialog.lastElementChild + + @menuInit() + @addHeaderMenuEntry() + + $.on $('.menu-button', @dialog), 'click', @cb.menuToggle $.on d, 'QRPostSuccessful', @cb.post + $.on d, 'ThreadUpdate', @cb.threadUpdate if g.VIEW is 'thread' $.on d, '4chanXInitFinished', @ready - $.sync 'WatchedThreads', @refresh + + # XXX tmp conversion from old to new format + $.get 'WatchedThreads', null, ({WatchedThreads}) -> + return unless WatchedThreads + for boardID, threads of ThreadWatcher.convert WatchedThreads + for threadID, data of threads + ThreadWatcher.db.set {boardID, threadID, val: data} + $.delete 'WatchedThreads' Thread::callbacks.push name: 'Thread Watcher' cb: @node node: -> - favicon = $.el 'img', - className: 'favicon' - $.on favicon, 'click', ThreadWatcher.cb.toggle - $.before $('input', @OP.nodes.post), favicon - return if g.VIEW isnt 'thread' - $.get 'AutoWatch', 0, (item) => - return if item['AutoWatch'] isnt @ID - ThreadWatcher.watch @ + toggler = $.el 'img', + className: 'watcher-toggler' + $.on toggler, 'click', ThreadWatcher.cb.toggle + $.before $('input', @OP.nodes.post), toggler + + return if g.VIEW isnt 'thread' or !Conf['Auto Watch'] + $.get 'AutoWatch', 0, ({AutoWatch}) => + return if AutoWatch isnt @ID + ThreadWatcher.add @ $.delete 'AutoWatch' + menuInit: -> + ThreadWatcher.menu = new UI.Menu 'thread watcher' + + # `Open all` entry + entry = + type: 'thread watcher' + el: $.el 'a', + textContent: 'Open all threads' + href: 'javascript:;' + open: -> + (if ThreadWatcher.list.firstElementChild then $.rmClass else $.addClass) @el, 'disabled' + true + $.event 'AddMenuEntry', entry + $.on entry.el, 'click', ThreadWatcher.cb.openAll + + # `Prune 404'd threads` entry + entry = + type: 'thread watcher' + el: $.el 'a', + textContent: 'Prune 404\'d threads' + href: 'javascript:;' + open: -> + (if $('.dead-thread', ThreadWatcher.list) then $.rmClass else $.addClass) @el, 'disabled' + true + $.event 'AddMenuEntry', entry + $.on entry.el, 'click', ThreadWatcher.cb.pruneDeads + + # `Settings` entries: + subEntries = [] + for name, conf of Config.threadWatcher + subEntries.push ThreadWatcher.createSubEntry name, conf[1] + $.event 'AddMenuEntry', + type: 'thread watcher' + el: $.el 'span', textContent: 'Settings' + subEntries: subEntries + createSubEntry: (name, desc) -> + entry = + type: 'thread watcher' + el: $.el 'label', + innerHTML: " #{name}" + title: desc + input = entry.el.firstElementChild + input.checked = Conf[name] + $.on input, 'change', $.cb.checked + $.on input, 'change', ThreadWatcher.refresh if name is 'Current Board' + entry + addHeaderMenuEntry: -> + return if g.VIEW isnt 'thread' + ThreadWatcher.entryEl = $.el 'a', href: 'javascript:;' + entry = + type: 'header' + el: ThreadWatcher.entryEl + order: 60 + $.event 'AddMenuEntry', entry + $.on entry.el, 'click', -> ThreadWatcher.toggle g.threads["#{g.BOARD}.#{g.THREADID}"] + ready: -> $.off d, '4chanXInitFinished', ThreadWatcher.ready return unless Main.isThisPageLegit() ThreadWatcher.refresh() $.add d.body, ThreadWatcher.dialog - refresh: (watched) -> - unless watched - $.get 'WatchedThreads', {}, (item) -> - ThreadWatcher.refresh item['WatchedThreads'] - return - 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 - - $.rmAll ThreadWatcher.dialog - $.add ThreadWatcher.dialog, nodes - - watched = watched[g.BOARD] or {} - for ID, thread of g.BOARD.threads - favicon = $ '.favicon', thread.OP.nodes.post - favicon.src = if ID of watched - Favicon.default - else - Favicon.empty - return - cb: + menuToggle: (e) -> + ThreadWatcher.menu.toggle e, @, ThreadWatcher + openAll: -> + return if $.hasClass @, 'disabled' + for a in $$ 'a[title]', ThreadWatcher.list + $.open a.href + $.event 'CloseMenu' + pruneDeads: -> + return if $.hasClass @, 'disabled' + for boardID, threads of ThreadWatcher.db.data.boards + if Conf['Current Board'] and boardID isnt g.BOARD.ID + continue + for threadID, data of threads + continue unless data.isDead + delete threads[threadID] + ThreadWatcher.db.deleteIfEmpty {boardID} + ThreadWatcher.db.save() + ThreadWatcher.refresh() + $.event 'CloseMenu' toggle: -> ThreadWatcher.toggle Get.postFromNode(@).thread - x: -> - thread = @nextElementSibling.pathname.split '/' - ThreadWatcher.unwatch thread[1], thread[3] + rm: -> + [boardID, threadID] = @parentNode.dataset.fullid.split '.' + ThreadWatcher.rm boardID, +threadID post: (e) -> {board, postID, threadID} = e.detail if postID is threadID if Conf['Auto Watch'] $.set 'AutoWatch', threadID else if Conf['Auto Watch Reply'] - ThreadWatcher.watch board.threads[threadID] + ThreadWatcher.add board.threads[threadID] + threadUpdate: (e) -> + # Update 404 status. + return unless e.detail[404] + {thread} = e.detail + return unless ThreadWatcher.db.get {boardID: thread.board.ID, threadID: thread.ID} + ThreadWatcher.add thread + + refresh: -> + nodes = [] + for boardID, threads of ThreadWatcher.db.data.boards + if Conf['Current Board'] and boardID isnt g.BOARD.ID + continue + for threadID, data of threads + x = $.el 'a', + textContent: '×' + href: 'javascript:;' + $.on x, 'click', ThreadWatcher.cb.rm + + if data.isDead + href = Redirect.to 'thread', {boardID, threadID} + link = $.el 'a', + href: href or "/#{boardID}/res/#{threadID}" + textContent: data.excerpt + title: data.excerpt + + nodes.push div = $.el 'div' + div.setAttribute 'data-fullid', "#{boardID}.#{threadID}" + $.addClass div, 'dead-thread' if data.isDead + $.add div, [x, $.tn(' '), link] + + list = ThreadWatcher.dialog.lastElementChild + $.rmAll list + $.add list, nodes + + if g.VIEW is 'thread' + {entryEl} = ThreadWatcher + if div = $ "div[data-fullid='#{g.BOARD}.#{g.THREADID}']", list + $.addClass div, 'current' + $.addClass entryEl, 'unwatch-thread' + $.rmClass entryEl, 'watch-thread' + entryEl.textContent = 'Unwatch thread' + else + $.addClass entryEl, 'watch-thread' + $.rmClass entryEl, 'unwatch-thread' + entryEl.textContent = 'Watch thread' + + for threadID, thread of g.BOARD.threads + toggler = $ '.watcher-toggler', thread.OP.nodes.post + toggler.src = if ThreadWatcher.db.get {boardID: thread.board.ID, threadID} + Favicon.default + else + Favicon.empty + return toggle: (thread) -> - if $('.favicon', thread.OP.nodes.post).src is Favicon.empty - ThreadWatcher.watch thread + boardID = thread.board.ID + threadID = thread.ID + if ThreadWatcher.db.get {boardID, threadID} + ThreadWatcher.rm boardID, threadID else - ThreadWatcher.unwatch thread.board, thread.ID + ThreadWatcher.add thread + add: (thread) -> + data = excerpt: Get.threadExcerpt thread + if thread.isDead + data.isDead = true + ThreadWatcher.db.set + boardID: thread.board.ID + threadID: thread.ID + val: data + ThreadWatcher.refresh() + rm: (boardID, threadID) -> + ThreadWatcher.db.delete {boardID, threadID} + ThreadWatcher.refresh() - unwatch: (board, threadID) -> - $.get 'WatchedThreads', {}, (item) -> - watched = item['WatchedThreads'] - delete watched[board][threadID] - delete watched[board] unless Object.keys(watched[board]).length - ThreadWatcher.refresh watched - $.set 'WatchedThreads', watched - - watch: (thread) -> - $.get 'WatchedThreads', {}, (item) -> - watched = item['WatchedThreads'] - watched[thread.board] or= {} - watched[thread.board][thread] = - href: "/#{thread.board}/res/#{thread}" - textContent: Get.threadExcerpt thread - ThreadWatcher.refresh watched - $.set 'WatchedThreads', watched + convert: (oldFormat) -> + newFormat = {} + for boardID, threads of oldFormat + for threadID, data of threads + (newFormat[boardID] or= {})[threadID] = excerpt: data.textContent + newFormat From 940202150af900775c9418ede45caf5ac4cf378c Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 6 Jun 2013 10:33:19 +1000 Subject: [PATCH 11/15] Update CSS to reflect new Thread Watcher changes --- css/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css/style.css b/css/style.css index b29c6769c..18191422f 100644 --- a/css/style.css +++ b/css/style.css @@ -36,7 +36,7 @@ .move { cursor: move; } -label, .favicon { +label, .watcher-toggler { cursor: pointer; } a[href="javascript:;"] { From a1d238b2336731f990ba04a60404253724d754b6 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Tue, 11 Jun 2013 14:21:01 +0200 Subject: [PATCH 12/15] Finish Thread Watcher rewrite. #99 --- CHANGELOG.md | 5 +- src/General/Config.coffee | 1 + src/General/Main.coffee | 1 + src/Monitoring/ThreadWatcher.coffee | 278 ++++++++++++++++------------ 4 files changed, 169 insertions(+), 116 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 002e98a51..883682af1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ -- **Thread Watcher** rewrite: +- **Thread Watcher** improvements: - It is now possible to open all watched threads via the `Open all threads` button in the Thread Watcher's menu. - Added the `Current Board` setting to switch between showing watched threads from the current board or all boards, disabled by default. - About dead (404'd) threads: - Dead threads will be typographically indicated with a strikethrough. - - Dead threads will directly link to the corresponding archive when possible. + - Dead threads will directly link to the corresponding archive when available. - A button to prune all 404'd threads from the list is now available. + - Added the `Auto Prune` setting to automatically prune 404'd threads, disabled by default. - The current thread is now highlighted in the list of watched threads. - Watching the current thread can be done in the Header's menu too. - Removed the `Check for Updates` setting: diff --git a/src/General/Config.coffee b/src/General/Config.coffee index 23e36d461..2270444ec 100644 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -80,6 +80,7 @@ Config = 'Current Board': [false, 'Only show watched threads from the current board.'] 'Auto Watch': [true, 'Automatically watch threads you start.'] 'Auto Watch Reply': [false, 'Automatically watch threads you reply to.'] + 'Auto Prune': [false, 'Automatically prune 404\'d threads.'] filter: name: """ # Filter any namefags: diff --git a/src/General/Main.coffee b/src/General/Main.coffee index eb10afe7f..039ad745d 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -112,6 +112,7 @@ Main = initFeature 'Thread Stats', ThreadStats initFeature 'Thread Updater', ThreadUpdater initFeature 'Thread Watcher', ThreadWatcher + initFeature 'Thread Watcher (Menu)', ThreadWatcher.menu initFeature 'Index Navigation', Nav initFeature 'Keybinds', Keybinds initFeature 'Show Dice Roll', Dice diff --git a/src/Monitoring/ThreadWatcher.coffee b/src/Monitoring/ThreadWatcher.coffee index 1629209d9..98193166e 100644 --- a/src/Monitoring/ThreadWatcher.coffee +++ b/src/Monitoring/ThreadWatcher.coffee @@ -8,14 +8,16 @@ ThreadWatcher = """ @list = @dialog.lastElementChild - @menuInit() - @addHeaderMenuEntry() - - $.on $('.menu-button', @dialog), 'click', @cb.menuToggle $.on d, 'QRPostSuccessful', @cb.post $.on d, 'ThreadUpdate', @cb.threadUpdate if g.VIEW is 'thread' $.on d, '4chanXInitFinished', @ready + now = Date.now() + if (@db.data.lastChecked or 0) < now - 2 * $.HOUR + @db.data.lastChecked = now + ThreadWatcher.fetchAllStatus() + @db.save() + # XXX tmp conversion from old to new format $.get 'WatchedThreads', null, ({WatchedThreads}) -> return unless WatchedThreads @@ -27,97 +29,36 @@ ThreadWatcher = Thread::callbacks.push name: 'Thread Watcher' cb: @node - node: -> toggler = $.el 'img', className: 'watcher-toggler' $.on toggler, 'click', ThreadWatcher.cb.toggle $.before $('input', @OP.nodes.post), toggler - - return if g.VIEW isnt 'thread' or !Conf['Auto Watch'] - $.get 'AutoWatch', 0, ({AutoWatch}) => - return if AutoWatch isnt @ID - ThreadWatcher.add @ - $.delete 'AutoWatch' - - menuInit: -> - ThreadWatcher.menu = new UI.Menu 'thread watcher' - - # `Open all` entry - entry = - type: 'thread watcher' - el: $.el 'a', - textContent: 'Open all threads' - href: 'javascript:;' - open: -> - (if ThreadWatcher.list.firstElementChild then $.rmClass else $.addClass) @el, 'disabled' - true - $.event 'AddMenuEntry', entry - $.on entry.el, 'click', ThreadWatcher.cb.openAll - - # `Prune 404'd threads` entry - entry = - type: 'thread watcher' - el: $.el 'a', - textContent: 'Prune 404\'d threads' - href: 'javascript:;' - open: -> - (if $('.dead-thread', ThreadWatcher.list) then $.rmClass else $.addClass) @el, 'disabled' - true - $.event 'AddMenuEntry', entry - $.on entry.el, 'click', ThreadWatcher.cb.pruneDeads - - # `Settings` entries: - subEntries = [] - for name, conf of Config.threadWatcher - subEntries.push ThreadWatcher.createSubEntry name, conf[1] - $.event 'AddMenuEntry', - type: 'thread watcher' - el: $.el 'span', textContent: 'Settings' - subEntries: subEntries - createSubEntry: (name, desc) -> - entry = - type: 'thread watcher' - el: $.el 'label', - innerHTML: " #{name}" - title: desc - input = entry.el.firstElementChild - input.checked = Conf[name] - $.on input, 'change', $.cb.checked - $.on input, 'change', ThreadWatcher.refresh if name is 'Current Board' - entry - addHeaderMenuEntry: -> - return if g.VIEW isnt 'thread' - ThreadWatcher.entryEl = $.el 'a', href: 'javascript:;' - entry = - type: 'header' - el: ThreadWatcher.entryEl - order: 60 - $.event 'AddMenuEntry', entry - $.on entry.el, 'click', -> ThreadWatcher.toggle g.threads["#{g.BOARD}.#{g.THREADID}"] - ready: -> $.off d, '4chanXInitFinished', ThreadWatcher.ready return unless Main.isThisPageLegit() ThreadWatcher.refresh() $.add d.body, ThreadWatcher.dialog + return unless Conf['Auto Watch'] + $.get 'AutoWatch', 0, ({AutoWatch}) -> + return unless thread = g.BOARD.threads[AutoWatch] + ThreadWatcher.add thread + $.delete 'AutoWatch' + cb: - menuToggle: (e) -> - ThreadWatcher.menu.toggle e, @, ThreadWatcher openAll: -> return if $.hasClass @, 'disabled' for a in $$ 'a[title]', ThreadWatcher.list $.open a.href $.event 'CloseMenu' + checkThreads: -> + return if $.hasClass @, 'disabled' + ThreadWatcher.fetchAllStatus() pruneDeads: -> return if $.hasClass @, 'disabled' - for boardID, threads of ThreadWatcher.db.data.boards - if Conf['Current Board'] and boardID isnt g.BOARD.ID - continue - for threadID, data of threads - continue unless data.isDead - delete threads[threadID] + for {boardID, threadID, data} in ThreadWatcher.getAll() when data.isDead + delete ThreadWatcher.db.data.boards[boardID][threadID] ThreadWatcher.db.deleteIfEmpty {boardID} ThreadWatcher.db.save() ThreadWatcher.refresh() @@ -125,7 +66,7 @@ ThreadWatcher = toggle: -> ThreadWatcher.toggle Get.postFromNode(@).thread rm: -> - [boardID, threadID] = @parentNode.dataset.fullid.split '.' + [boardID, threadID] = @parentNode.dataset.fullID.split '.' ThreadWatcher.rm boardID, +threadID post: (e) -> {board, postID, threadID} = e.detail @@ -135,57 +76,77 @@ ThreadWatcher = else if Conf['Auto Watch Reply'] ThreadWatcher.add board.threads[threadID] threadUpdate: (e) -> - # Update 404 status. - return unless e.detail[404] {thread} = e.detail - return unless ThreadWatcher.db.get {boardID: thread.board.ID, threadID: thread.ID} + return unless e.detail[404] and ThreadWatcher.db.get {boardID: thread.board.ID, threadID: thread.ID} + # Update 404 status. ThreadWatcher.add thread - refresh: -> - nodes = [] + fetchAllStatus: -> + # XXX need visual feedback + for thread in ThreadWatcher.getAll() + ThreadWatcher.fetchStatus thread + return + fetchStatus: ({boardID, threadID, data}) -> + return if data.isDead + $.ajax "//api.4chan.org/#{boardID}/res/#{threadID}.json", + onload: -> + return if @status isnt 404 + if Conf['Auto Prune'] + ThreadWatcher.rm boardID, threadID + else + data.isDead = true + ThreadWatcher.db.set {boardID, threadID, val: data} + ThreadWatcher.refresh() + , + type: 'head' + + getAll: -> + all = [] for boardID, threads of ThreadWatcher.db.data.boards if Conf['Current Board'] and boardID isnt g.BOARD.ID continue for threadID, data of threads - x = $.el 'a', - textContent: '×' - href: 'javascript:;' - $.on x, 'click', ThreadWatcher.cb.rm + all.push {boardID, threadID, data} + all - if data.isDead - href = Redirect.to 'thread', {boardID, threadID} - link = $.el 'a', - href: href or "/#{boardID}/res/#{threadID}" - textContent: data.excerpt - title: data.excerpt + makeLine: (boardID, threadID, data) -> + x = $.el 'a', + textContent: '×' + href: 'javascript:;' + $.on x, 'click', ThreadWatcher.cb.rm - nodes.push div = $.el 'div' - div.setAttribute 'data-fullid', "#{boardID}.#{threadID}" - $.addClass div, 'dead-thread' if data.isDead - $.add div, [x, $.tn(' '), link] + if data.isDead + href = Redirect.to 'thread', {boardID, threadID} + link = $.el 'a', + href: href or "/#{boardID}/res/#{threadID}" + textContent: data.excerpt + title: data.excerpt - list = ThreadWatcher.dialog.lastElementChild + div = $.el 'div' + fullID = "#{boardID}.#{threadID}" + div.dataset.fullID = fullID + $.addClass div, 'current' if g.VIEW is 'thread' and fullID is "#{g.BOARD}.#{g.THREADID}" + $.addClass div, 'dead-thread' if data.isDead + $.add div, [x, $.tn(' '), link] + div + refresh: -> + nodes = [] + for {boardID, threadID, data} in ThreadWatcher.getAll() + nodes.push ThreadWatcher.makeLine boardID, threadID, data + + {list} = ThreadWatcher $.rmAll list $.add list, nodes - if g.VIEW is 'thread' - {entryEl} = ThreadWatcher - if div = $ "div[data-fullid='#{g.BOARD}.#{g.THREADID}']", list - $.addClass div, 'current' - $.addClass entryEl, 'unwatch-thread' - $.rmClass entryEl, 'watch-thread' - entryEl.textContent = 'Unwatch thread' - else - $.addClass entryEl, 'watch-thread' - $.rmClass entryEl, 'unwatch-thread' - entryEl.textContent = 'Watch thread' - for threadID, thread of g.BOARD.threads toggler = $ '.watcher-toggler', thread.OP.nodes.post toggler.src = if ThreadWatcher.db.get {boardID: thread.board.ID, threadID} Favicon.default else Favicon.empty + + for refresher in ThreadWatcher.menu.refreshers + refresher() return toggle: (thread) -> @@ -196,13 +157,16 @@ ThreadWatcher = else ThreadWatcher.add thread add: (thread) -> - data = excerpt: Get.threadExcerpt thread + data = {} + boardID = thread.board.ID + threadID = thread.ID if thread.isDead + if Conf['Auto Prune'] and ThreadWatcher.db.get {boardID, threadID} + ThreadWatcher.rm boardID, threadID + return data.isDead = true - ThreadWatcher.db.set - boardID: thread.board.ID - threadID: thread.ID - val: data + data.excerpt = Get.threadExcerpt thread + ThreadWatcher.db.set {boardID, threadID, val: data} ThreadWatcher.refresh() rm: (boardID, threadID) -> ThreadWatcher.db.delete {boardID, threadID} @@ -214,3 +178,89 @@ ThreadWatcher = for threadID, data of threads (newFormat[boardID] or= {})[threadID] = excerpt: data.textContent newFormat + + menu: + refreshers: [] + init: -> + return if !Conf['Thread Watcher'] + menu = new UI.Menu 'thread watcher' + $.on $('.menu-button', ThreadWatcher.dialog), 'click', (e) -> + menu.toggle e, @, ThreadWatcher + @addHeaderMenuEntry() + @addMenuEntries() + + addHeaderMenuEntry: -> + return if g.VIEW isnt 'thread' + entryEl = $.el 'a', + href: 'javascript:;' + $.event 'AddMenuEntry', + type: 'header' + el: entryEl + order: 60 + $.on entryEl, 'click', -> ThreadWatcher.toggle g.threads["#{g.BOARD}.#{g.THREADID}"] + @refreshers.push -> + [addClass, rmClass, text] = if $ '.current', ThreadWatcher.list + ['unwatch-thread', 'watch-thread', 'Unwatch thread'] + else + ['watch-thread', 'unwatch-thread', 'Watch thread'] + $.addClass entryEl, addClass + $.rmClass entryEl, rmClass + entryEl.textContent = text + + addMenuEntries: -> + entries = [] + + # `Open all` entry + entries.push + cb: ThreadWatcher.cb.openAll + entry: + type: 'thread watcher' + el: $.el 'a', + textContent: 'Open all threads' + refresh: -> (if ThreadWatcher.list.firstElementChild then $.rmClass else $.addClass) @el, 'disabled' + + # `Check 404'd threads` entry + entries.push + cb: ThreadWatcher.cb.checkThreads + entry: + type: 'thread watcher' + el: $.el 'a', + textContent: 'Check 404\'d threads' + refresh: -> (if $('div:not(.dead-thread)', ThreadWatcher.list) then $.rmClass else $.addClass) @el, 'disabled' + + # `Prune 404'd threads` entry + entries.push + cb: ThreadWatcher.cb.pruneDeads + entry: + type: 'thread watcher' + el: $.el 'a', + textContent: 'Prune 404\'d threads' + refresh: -> (if $('.dead-thread', ThreadWatcher.list) then $.rmClass else $.addClass) @el, 'disabled' + + # `Settings` entries: + subEntries = [] + for name, conf of Config.threadWatcher + subEntries.push @createSubEntry name, conf[1] + entries.push + entry: + type: 'thread watcher' + el: $.el 'span', + textContent: 'Settings' + subEntries: subEntries + + for {entry, cb, refresh} in entries + entry.el.href = 'javascript:;' if entry.el.nodeName is 'A' + $.on entry.el, 'click', cb if cb + @refreshers.push refresh.bind entry if refresh + $.event 'AddMenuEntry', entry + createSubEntry: (name, desc) -> + entry = + type: 'thread watcher' + el: $.el 'label', + innerHTML: " #{name}" + title: desc + input = entry.el.firstElementChild + input.checked = Conf[name] + $.on input, 'change', $.cb.checked + $.on input, 'change', ThreadWatcher.refresh if name is 'Current Board' + entry From 30d0cc8718d007c08db5db4148dfdfffda90f54d Mon Sep 17 00:00:00 2001 From: Mayhem Date: Mon, 12 Aug 2013 19:27:46 +0200 Subject: [PATCH 13/15] Typo. --- src/Archive/Redirect.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Archive/Redirect.coffee b/src/Archive/Redirect.coffee index 9fb542457..24de31b4f 100644 --- a/src/Archive/Redirect.coffee +++ b/src/Archive/Redirect.coffee @@ -29,7 +29,7 @@ Redirect = update: (cb) -> $.get 'lastarchivecheck', 0, ({lastarchivecheck}) -> now = Date.now() - # Update the list of archives every 4 days. + # Update the list of archives every 2 days. # The list is also updated when 4chan X gets updated. return if lastarchivecheck > now - 2 * $.DAY $.ajax '<%= meta.page %>json/archives.json', onload: -> From a92afa5679c9c330522b5d91709a40835195ee2e Mon Sep 17 00:00:00 2001 From: Mayhem Date: Mon, 12 Aug 2013 21:41:13 +0200 Subject: [PATCH 14/15] Add fetching status at the top of the thread watcher. #99 --- css/style.css | 6 ++++++ html/Monitoring/ThreadWatcher.html | 2 +- src/Monitoring/ThreadWatcher.coffee | 20 +++++++++++++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/css/style.css b/css/style.css index 18191422f..32819ef69 100644 --- a/css/style.css +++ b/css/style.css @@ -448,6 +448,12 @@ a.hide-announcement { -webkit-flex: 1; flex: 1; } +#watcher-status:not(:empty)::before { + content: "("; +} +#watcher-status:not(:empty)::after { + content: ")"; +} #watched-threads:not(:hover) { max-height: 150px; overflow: hidden; diff --git a/html/Monitoring/ThreadWatcher.html b/html/Monitoring/ThreadWatcher.html index d3878c4cd..8ea19b356 100644 --- a/html/Monitoring/ThreadWatcher.html +++ b/html/Monitoring/ThreadWatcher.html @@ -1,5 +1,5 @@
- Thread Watcher + Thread Watcher []
diff --git a/src/Monitoring/ThreadWatcher.coffee b/src/Monitoring/ThreadWatcher.coffee index 98193166e..ff36315c5 100644 --- a/src/Monitoring/ThreadWatcher.coffee +++ b/src/Monitoring/ThreadWatcher.coffee @@ -6,7 +6,8 @@ ThreadWatcher = @dialog = UI.dialog 'thread-watcher', 'top: 50px; left: 0px;', """ <%= grunt.file.read('html/Monitoring/ThreadWatcher.html').replace(/>\s+<').trim() %> """ - @list = @dialog.lastElementChild + @status = $ '#watcher-status', @dialog + @list = @dialog.lastElementChild $.on d, 'QRPostSuccessful', @cb.post $.on d, 'ThreadUpdate', @cb.threadUpdate if g.VIEW is 'thread' @@ -81,15 +82,28 @@ ThreadWatcher = # Update 404 status. ThreadWatcher.add thread + fetchCount: + fetched: 0 + fetching: 0 fetchAllStatus: -> - # XXX need visual feedback + ThreadWatcher.status.textContent = '...' for thread in ThreadWatcher.getAll() ThreadWatcher.fetchStatus thread return fetchStatus: ({boardID, threadID, data}) -> return if data.isDead + {fetchCount} = ThreadWatcher + fetchCount.fetching++ $.ajax "//api.4chan.org/#{boardID}/res/#{threadID}.json", - onload: -> + onloadend: -> + fetchCount.fetched++ + if fetchCount.fetched is fetchCount.fetching + fetchCount.fetched = 0 + fetchCount.fetching = 0 + status = '' + else + status = "#{Math.round fetchCount.fetched / fetchCount.fetching * 100}%" + ThreadWatcher.status.textContent = status return if @status isnt 404 if Conf['Auto Prune'] ThreadWatcher.rm boardID, threadID From f264140ace962d8234fe33eb6d6cedcd003a92f4 Mon Sep 17 00:00:00 2001 From: Wohlfe Date: Mon, 12 Aug 2013 17:15:38 -0400 Subject: [PATCH 15/15] Added /d/. --- json/archives.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/json/archives.json b/json/archives.json index efde782d3..647889b55 100644 --- a/json/archives.json +++ b/json/archives.json @@ -50,8 +50,8 @@ "http": true, "https": true, "software": "foolfuuka", - "boards": ["adv", "asp", "cm", "e", "i", "lgbt", "n", "o", "p", "pol", "s", "s4s", "t", "trv", "y"], - "files": ["cm", "e", "i", "n", "o", "p", "s", "trv", "y"] + "boards": ["adv", "asp", "cm", "d", "e", "i", "lgbt", "n", "o", "p", "pol", "s", "s4s", "t", "trv", "y"], + "files": ["cm", "d", "e", "i", "n", "o", "p", "s", "trv", "y"] }, { "uid": 12, "name": "fap archive",