From ef38269981099d888a607111ea5a00f481e54889 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Tue, 29 Oct 2013 19:13:34 +0100 Subject: [PATCH 01/49] Initial work for index navigation improvements. --- src/General/Config.coffee | 4 +-- src/General/Header.coffee | 8 +++--- src/General/Main.coffee | 1 + src/Miscellaneous/Index.coffee | 41 +++++++++++++++++++++++++++++ src/Miscellaneous/Keybinds.coffee | 10 ++++--- src/Monitoring/ThreadUpdater.coffee | 4 +-- src/Monitoring/Unread.coffee | 4 +-- 7 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 src/Miscellaneous/Index.coffee diff --git a/src/General/Config.coffee b/src/General/Config.coffee index 1421ff806..4f422357e 100644 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -170,9 +170,9 @@ Config = 'Eqn tags': ['Alt+e', 'Insert eqn tags.'] 'Math tags': ['Alt+m', 'Insert math tags.'] 'Submit QR': ['Alt+s', 'Submit post.'] - # Thread related + # Index/Thread related + 'Update': ['r', 'Refresh the index/thread.'] 'Watch': ['w', 'Watch thread.'] - 'Update': ['r', 'Update the thread.'] # Images 'Expand image': ['Shift+e', 'Expand selected image.'] 'Expand images': ['e', 'Expand all images.'] diff --git a/src/General/Header.coffee b/src/General/Header.coffee index e16001638..7eb6e06fe 100644 --- a/src/General/Header.coffee +++ b/src/General/Header.coffee @@ -246,8 +246,8 @@ Header = hashScroll: -> return unless (hash = @location.hash[1..]) and post = $.id hash return if (Get.postFromRoot post).isHidden - Header.scrollToPost post - scrollToPost: (post) -> + Header.scrollTo post + scrollTo: (post) -> {top} = post.getBoundingClientRect() unless Conf['Bottom header'] headRect = Header.toggle.getBoundingClientRect() @@ -268,8 +268,8 @@ Header = createNotification: (e) -> {type, content, lifetime, cb} = e.detail - notif = new Notice type, content, lifetime - cb notif if cb + notice = new Notice type, content, lifetime + cb notice if cb areNotificationsEnabled: false enableDesktopNotifications: -> diff --git a/src/General/Main.coffee b/src/General/Main.coffee index 9410365c0..2829eb208 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -69,6 +69,7 @@ Main = initFeature 'Polyfill', Polyfill initFeature 'Header', Header initFeature 'Settings', Settings + initFeature 'Index Pager', Index initFeature 'Announcement Hiding', PSAHiding initFeature 'Fourchan thingies', Fourchan initFeature 'Custom CSS', CustomCSS diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee new file mode 100644 index 000000000..06aa5b329 --- /dev/null +++ b/src/Miscellaneous/Index.coffee @@ -0,0 +1,41 @@ +Index = + init: -> + return if g.VIEW isnt 'index' + + update: -> + # return unless navigator.onLine + Index.req?.abort() + Index.notice?.close() + Index.notice = new Notice 'info', 'Refreshing index...' + Index.req = $.ajax "//api.4chan.org/#{g.BOARD}/catalog.json", + onabort: Index.load + onloadend: Index.load + , + whenModified: true + load: (e) -> + {req, notice} = Index + delete Index.req + delete Index.notice + + if e.type is 'abort' + req.onloadend = null + notice.close() + return + + try + Index.parse JSON.parse req.response + catch e + # network error or non-JSON content for example. + notice.setType 'error' + notice.el.lastElementChild.textContent = 'Index refresh failed.' + setTimeout notice.close, 2 * $.SECOND + return + + notice.setType 'success' + notice.el.lastElementChild.textContent = 'Index refreshed!' + setTimeout notice.close, $.SECOND + + Header.scrollTo $.id 'delform' + parse: (pages) -> + pageNum = +window.location.pathname.split('/')[2] + threads = pages[pageNum].threads diff --git a/src/Miscellaneous/Keybinds.coffee b/src/Miscellaneous/Keybinds.coffee index a06a6043b..31a237e9c 100644 --- a/src/Miscellaneous/Keybinds.coffee +++ b/src/Miscellaneous/Keybinds.coffee @@ -58,11 +58,15 @@ Keybinds = Keybinds.tags 'math', target when Conf['Submit QR'] QR.submit() if QR.nodes and !QR.status() - # Thread related + # Index/Thread related + when Conf['Update'] + switch g.VIEW + when 'thread' + ThreadUpdater.update() + when 'index' + Index.update() when Conf['Watch'] ThreadWatcher.toggle thread - when Conf['Update'] - ThreadUpdater.update() # Images when Conf['Expand image'] Keybinds.img threadRoot diff --git a/src/Monitoring/ThreadUpdater.coffee b/src/Monitoring/ThreadUpdater.coffee index 677db825d..03c8b5053 100644 --- a/src/Monitoring/ThreadUpdater.coffee +++ b/src/Monitoring/ThreadUpdater.coffee @@ -145,7 +145,7 @@ ThreadUpdater = return unless navigator.onLine ThreadUpdater.count() ThreadUpdater.set 'timer', '...' - ThreadUpdater.req.abort() if ThreadUpdater.req + ThreadUpdater.req?.abort() url = "//api.4chan.org/#{ThreadUpdater.thread.board}/res/#{ThreadUpdater.thread}.json" ThreadUpdater.req = $.ajax url, onabort: ThreadUpdater.cb.load @@ -258,7 +258,7 @@ ThreadUpdater = if Conf['Bottom Scroll'] window.scrollTo 0, d.body.clientHeight else - Header.scrollToPost nodes[0] + Header.scrollTo nodes[0] # Enable 4chan features. threadID = ThreadUpdater.thread.ID diff --git a/src/Monitoring/Unread.coffee b/src/Monitoring/Unread.coffee index fc7e2940e..2898c6c69 100644 --- a/src/Monitoring/Unread.coffee +++ b/src/Monitoring/Unread.coffee @@ -47,7 +47,7 @@ Unread = # Scroll to the last read post. posts = Object.keys Unread.thread.posts {root} = Unread.thread.posts[posts[posts.length - 1]].nodes - onload = -> Header.scrollToPost root if checkPosition root + onload = -> Header.scrollTo root if checkPosition root checkPosition = (target) -> # Scroll to the target unless we scrolled past it. target.getBoundingClientRect().bottom > doc.clientHeight @@ -102,7 +102,7 @@ Unread = body: post.info.comment icon: Favicon.logo notif.onclick = -> - Header.scrollToPost post.nodes.root + Header.scrollTo post.nodes.root window.focus() notif.onshow = -> setTimeout -> From fecf286d26ad07a5b9641da9543594a0bd7d3232 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Tue, 29 Oct 2013 23:52:49 +0100 Subject: [PATCH 02/49] Make index page refreshing work. --- CHANGELOG.md | 2 ++ src/General/Build.coffee | 24 +++++++++++++++++++++ src/General/Main.coffee | 2 +- src/Miscellaneous/Index.coffee | 38 ++++++++++++++++++++++++++++++---- 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd43be2d1..7f9c77613 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +Index navigation improvements: + - You can now refresh the index page you are on with the same keybind for refreshing threads. Added a keybind to open the catalog search field on index pages. ### 3.11.5 - *2013-10-03* diff --git a/src/General/Build.coffee b/src/General/Build.coffee index 783a774f5..b229c71a7 100644 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -248,3 +248,27 @@ Build = quote.href = "/#{boardID}/res/#{href}" # Fix pathnames container + + thread: (board, data) -> + root = $.el 'div', + className: 'thread' + id: "t#{data.no}" + + for obj in [data].concat data.last_replies or [] + $.add root, if post = g.posts["#{board}.#{obj.no}"] + post.nodes.root + else + Build.postFromObject obj, board.ID + + # build if necessary + if data.omitted_posts + {omitted_posts, omitted_images} = data + html = [] + html.push "#{omitted_posts} post#{if omitted_posts > 1 then 's' else ''}" + html.push "and #{omitted_images} image repl#{if omitted_images > 1 then 'ies' else 'y'}" if omitted_images + html.push "omitted. Click here to view." + $.after root.firstChild, $.el 'span', + className: 'summary' + innerHTML: html.join ' ' + + root diff --git a/src/General/Main.coffee b/src/General/Main.coffee index 2829eb208..604393db8 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -185,7 +185,7 @@ Main = unless errors errors = [] errors.push - message: "Parsing of Post No.#{postRoot.id.match(/\d+/)} failed. Post will be skipped." + message: "Parsing of Post No.#{postRoot.id.match /\d+/} failed. Post will be skipped." error: err Main.handleErrors errors if errors diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index 06aa5b329..a277d76fd 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -3,7 +3,7 @@ Index = return if g.VIEW isnt 'index' update: -> - # return unless navigator.onLine + return unless navigator.onLine Index.req?.abort() Index.notice?.close() Index.notice = new Notice 'info', 'Refreshing index...' @@ -23,8 +23,9 @@ Index = return try - Index.parse JSON.parse req.response - catch e + Index.parse JSON.parse req.response if req.status is 200 + catch err + c.error err.stack # network error or non-JSON content for example. notice.setType 'error' notice.el.lastElementChild.textContent = 'Index refresh failed.' @@ -38,4 +39,33 @@ Index = Header.scrollTo $.id 'delform' parse: (pages) -> pageNum = +window.location.pathname.split('/')[2] - threads = pages[pageNum].threads + dataThr = pages[pageNum].threads + + nodes = [] + threads = [] + posts = [] + for data in dataThr + threadRoot = Build.thread g.BOARD, data + nodes.push threadRoot, $.el 'hr' + unless thread = g.threads["#{g.BOARD}.#{data.no}"] + thread = new Thread data.no, g.BOARD + threads.push thread + for postRoot in $$ '.thread > .postContainer', threadRoot + continue if thread.posts[postRoot.id.match /\d+/] + try + posts.push new Post postRoot, thread, g.BOARD + catch err + # Skip posts that we failed to parse. + unless errors + errors = [] + errors.push + message: "Parsing of Post No.#{postRoot.id.match /\d+/} failed. Post will be skipped." + error: err + Main.handleErrors errors if errors + + Main.callbackNodes Thread, threads + Main.callbackNodes Post, posts + + board = $ '.board' + $.rmAll board + $.add board, nodes From a5d72fe515060fbc3595bd623fcc0bc5e04828ff Mon Sep 17 00:00:00 2001 From: Mayhem Date: Wed, 30 Oct 2013 00:07:00 +0100 Subject: [PATCH 03/49] Build missing [Reply] button. --- src/General/Build.coffee | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/General/Build.coffee b/src/General/Build.coffee index b229c71a7..fb3843804 100644 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -186,6 +186,11 @@ Build = else '' + replyLink = if isOP and g.VIEW is 'index' + "   [Reply]" + else + '' + container = $.el 'div', id: "pc#{postID}" className: "postContainer #{if isOP then 'op' else 'reply'}Container" @@ -233,6 +238,7 @@ Build = else "/#{boardID}/res/#{threadID}#q#{postID}" }' title='Quote this post'>#{postID}" + + replyLink + '' + '' + @@ -266,7 +272,7 @@ Build = html = [] html.push "#{omitted_posts} post#{if omitted_posts > 1 then 's' else ''}" html.push "and #{omitted_images} image repl#{if omitted_images > 1 then 'ies' else 'y'}" if omitted_images - html.push "omitted. Click here to view." + html.push "omitted. Click here to view." $.after root.firstChild, $.el 'span', className: 'summary' innerHTML: html.join ' ' From 1996fc435174951e058ecc2cd7bcc1f7a4d31531 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Wed, 30 Oct 2013 02:45:18 +0100 Subject: [PATCH 04/49] Add all-pages index navigation. Close #1133 --- CHANGELOG.md | 1 + src/General/Build.coffee | 2 +- src/General/Config.coffee | 1 + src/General/Main.coffee | 2 +- src/Miscellaneous/Index.coffee | 36 ++++++++++++++++++++++++++++++++-- 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f9c77613..69308c0cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ Index navigation improvements: - You can now refresh the index page you are on with the same keybind for refreshing threads. + - You can now switch between single-page and all-pages navigation via the "Index Navigation" header sub-menu. Added a keybind to open the catalog search field on index pages. ### 3.11.5 - *2013-10-03* diff --git a/src/General/Build.coffee b/src/General/Build.coffee index fb3843804..b62418e13 100644 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -238,7 +238,7 @@ Build = else "/#{boardID}/res/#{threadID}#q#{postID}" }' title='Quote this post'>#{postID}" + - replyLink + + replyLink + '' + '' + diff --git a/src/General/Config.coffee b/src/General/Config.coffee index 4f422357e..dd3220383 100644 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -139,6 +139,7 @@ Config = #//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/ #//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/ """ + 'Index Mode': 'paged' 'Custom CSS': false Header: 'Header auto-hide': false diff --git a/src/General/Main.coffee b/src/General/Main.coffee index 604393db8..a504f76ef 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -208,7 +208,7 @@ Main = try localStorage.getItem '4chan-settings' catch err - new Notice 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to properly function.', 30 + new Notice 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to operate properly.', 30 Main.disableReports = true $.event '4chanXInitFinished' diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index a277d76fd..dd49f4f37 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -2,6 +2,33 @@ Index = init: -> return if g.VIEW isnt 'index' + label = $.el 'label', + innerHTML: """ + + """ + select = label.firstChild + select.value = Conf['Index Mode'] + $.on select, 'change', $.cb.value + $.on select, 'change', @update + + $.event 'AddMenuEntry', + type: 'header' + el: $.el 'span', + textContent: 'Index Navigation' + order: 90 + subEntries: [el: label] + + $.on d, '4chanXInitFinished', @initReady + + initReady: -> + $.off d, '4chanXInitFinished', Index.initReady + return if Conf['Index Mode'] is 'paged' + Index.update() + update: -> return unless navigator.onLine Index.req?.abort() @@ -38,8 +65,13 @@ Index = Header.scrollTo $.id 'delform' parse: (pages) -> - pageNum = +window.location.pathname.split('/')[2] - dataThr = pages[pageNum].threads + if Conf['Index Mode'] is 'paged' + pageNum = +window.location.pathname.split('/')[2] + dataThr = pages[pageNum].threads + else + dataThr = [] + for page in pages + dataThr.push page.threads... nodes = [] threads = [] From e012cc4d641f9a4a29ed79b39d45cdd98a2a1e6a Mon Sep 17 00:00:00 2001 From: Mayhem Date: Wed, 30 Oct 2013 03:04:18 +0100 Subject: [PATCH 05/49] Hide the page list in non-paged mode. --- src/Miscellaneous/Index.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index dd49f4f37..bcc4c4615 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -101,3 +101,4 @@ Index = board = $ '.board' $.rmAll board $.add board, nodes + $('.pagelist').hidden = Conf['Index Mode'] isnt 'paged' From 6918d67d5d3079bf64a1bd8337c8ed2459262e55 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Wed, 30 Oct 2013 03:10:30 +0100 Subject: [PATCH 06/49] Disable next/previous page keybinds in non-paged mode. --- html/General/Settings-section-Filter-guide.html | 2 +- src/Miscellaneous/Keybinds.coffee | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/html/General/Settings-section-Filter-guide.html b/html/General/Settings-section-Filter-guide.html index ae73a66da..1656d0cb8 100644 --- a/html/General/Settings-section-Filter-guide.html +++ b/html/General/Settings-section-Filter-guide.html @@ -23,7 +23,7 @@ For example: highlight; or highlight:wallpaper;.
  • - Highlighted OPs will have their threads put on top of board pages by default.
    + Highlighted OPs will have their threads put on top of the board index by default.
    For example: top:yes; or top:no;.
  • diff --git a/src/Miscellaneous/Keybinds.coffee b/src/Miscellaneous/Keybinds.coffee index 31a237e9c..464bf76c2 100644 --- a/src/Miscellaneous/Keybinds.coffee +++ b/src/Miscellaneous/Keybinds.coffee @@ -78,9 +78,11 @@ Keybinds = when Conf['Open front page'] $.open "/#{g.BOARD}/#delform" when Conf['Next page'] + return if Conf['Index Mode'] isnt 'paged' if form = $ '.next form' window.location = form.action when Conf['Previous page'] + return if Conf['Index Mode'] isnt 'paged' if form = $ '.prev form' window.location = form.action when Conf['Search form'] From 43567173fc8f855b8668009a3a07d49520a295b9 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Wed, 30 Oct 2013 03:42:49 +0100 Subject: [PATCH 07/49] Use a sub-entry instead of a - - - - Paged' } + { el: $.el 'label', innerHTML: ' All threads' } + ] + for label in subEntry.subEntries + input = label.el.firstChild + input.checked = Conf['Index Mode'] is input.value + $.on input, 'change', $.cb.value + $.on input, 'change', @update + subEntries.push subEntry $.event 'AddMenuEntry', type: 'header' el: $.el 'span', textContent: 'Index Navigation' order: 90 - subEntries: [el: label] + subEntries: subEntries $.on d, '4chanXInitFinished', @initReady From 11c3b0018a87455080edbb2820ba99078b85e43a Mon Sep 17 00:00:00 2001 From: Mayhem Date: Wed, 30 Oct 2013 04:32:31 +0100 Subject: [PATCH 08/49] Fix thread watcher/expansion/hiding buttons after an index refresh. --- src/Miscellaneous/ExpandThread.coffee | 7 +++++++ src/Miscellaneous/Index.coffee | 3 ++- src/Monitoring/ThreadWatcher.coffee | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Miscellaneous/ExpandThread.coffee b/src/Miscellaneous/ExpandThread.coffee index fe2305bdc..dbe736305 100644 --- a/src/Miscellaneous/ExpandThread.coffee +++ b/src/Miscellaneous/ExpandThread.coffee @@ -2,6 +2,8 @@ ExpandThread = init: -> return if g.VIEW isnt 'index' or !Conf['Thread Expansion'] + $.on d, 'IndexRefresh', @onrefresh + Thread.callbacks.push name: 'Thread Expansion' cb: @node @@ -15,6 +17,11 @@ ExpandThread = $.on a, 'click', ExpandThread.cbToggle $.replace span, a + onrefresh: -> + for threadID, thread of g.BOARD.threads + ExpandThread.node.call thread + return + text: (status, posts, files) -> text = [status] text.push "#{posts} post#{if posts > 1 then 's' else ''}" diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index b1db097ec..fa717fd68 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -83,7 +83,7 @@ Index = nodes.push threadRoot, $.el 'hr' unless thread = g.threads["#{g.BOARD}.#{data.no}"] thread = new Thread data.no, g.BOARD - threads.push thread + threads.push thread for postRoot in $$ '.thread > .postContainer', threadRoot continue if thread.posts[postRoot.id.match /\d+/] try @@ -104,3 +104,4 @@ Index = $.rmAll board $.add board, nodes $('.pagelist').hidden = Conf['Index Mode'] isnt 'paged' + $.event 'IndexRefresh' diff --git a/src/Monitoring/ThreadWatcher.coffee b/src/Monitoring/ThreadWatcher.coffee index 54bfb9435..7697ce4cb 100644 --- a/src/Monitoring/ThreadWatcher.coffee +++ b/src/Monitoring/ThreadWatcher.coffee @@ -12,6 +12,7 @@ ThreadWatcher = $.on d, 'QRPostSuccessful', @cb.post $.on d, 'ThreadUpdate', @cb.threadUpdate if g.VIEW is 'thread' $.on d, '4chanXInitFinished', @ready + $.on d, 'IndexRefresh', @refresh now = Date.now() if (@db.data.lastChecked or 0) < now - 2 * $.HOUR From f0d19e6a5c17dbcf943f7ce27e8deba6c5185286 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Wed, 30 Oct 2013 16:49:42 +0100 Subject: [PATCH 09/49] Fix threads highlighted by the filter not being put on top. --- src/Filtering/Filter.coffee | 9 ++------- src/Miscellaneous/Index.coffee | 22 ++++++++++++++++++---- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/Filtering/Filter.coffee b/src/Filtering/Filter.coffee index 1f1387dba..eee0dfae9 100644 --- a/src/Filtering/Filter.coffee +++ b/src/Filtering/Filter.coffee @@ -110,13 +110,8 @@ Filter = # Highlight $.addClass @nodes.root, result.class - if !@isReply and result.top and g.VIEW is 'index' - # Put the highlighted OPs' thread on top of the board page... - thisThread = @nodes.root.parentNode - # ...before the first non highlighted thread. - if firstThread = $ 'div[class="postContainer opContainer"]' - unless firstThread is @nodes.root - $.before firstThread.parentNode, [thisThread, thisThread.nextElementSibling] + if !@isReply and result.top + @thread.isOnTop = true name: (post) -> if 'name' of post.info diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index fa717fd68..a4287e188 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -28,6 +28,8 @@ Index = initReady: -> $.off d, '4chanXInitFinished', Index.initReady + Index.root = $ '.board' + Index.setIndex $$ '.board > .thread, .board > hr', Index.root return if Conf['Index Mode'] is 'paged' Index.update() @@ -100,8 +102,20 @@ Index = Main.callbackNodes Thread, threads Main.callbackNodes Post, posts - board = $ '.board' - $.rmAll board - $.add board, nodes - $('.pagelist').hidden = Conf['Index Mode'] isnt 'paged' + Index.setIndex nodes $.event 'IndexRefresh' + setIndex: (nodes) -> + $.rmAll Index.root + $.add Index.root, Index.sort nodes + $('.pagelist').hidden = Conf['Index Mode'] isnt 'paged' + sort: (nodes) -> + return nodes unless Conf['Filter'] + # Put the highlighted thread on top of the index + # while keeping the original order they appear in. + tops = [] + for threadRoot in nodes by 2 when Get.threadFromRoot(threadRoot).isOnTop + tops.push threadRoot + for top, i in tops + arr = nodes.splice nodes.indexOf(top), 2 + nodes.splice i * 2, 0, arr... + nodes From df62c78ed922fc4a5a791070d003515ecf6afe6e Mon Sep 17 00:00:00 2001 From: Mayhem Date: Wed, 30 Oct 2013 17:25:22 +0100 Subject: [PATCH 10/49] After an index refresh, only scroll down if needed. --- src/General/Header.coffee | 4 ++-- src/Miscellaneous/Index.coffee | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/General/Header.coffee b/src/General/Header.coffee index 7eb6e06fe..12533ca17 100644 --- a/src/General/Header.coffee +++ b/src/General/Header.coffee @@ -247,8 +247,8 @@ Header = return unless (hash = @location.hash[1..]) and post = $.id hash return if (Get.postFromRoot post).isHidden Header.scrollTo post - scrollTo: (post) -> - {top} = post.getBoundingClientRect() + scrollTo: (root) -> + {top} = root.getBoundingClientRect() unless Conf['Bottom header'] headRect = Header.toggle.getBoundingClientRect() top -= headRect.top + headRect.height diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index a4287e188..44db465b6 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -67,7 +67,7 @@ Index = notice.el.lastElementChild.textContent = 'Index refreshed!' setTimeout notice.close, $.SECOND - Header.scrollTo $.id 'delform' + Header.scrollTo Index.root if Index.root.getBoundingClientRect().top < 0 parse: (pages) -> if Conf['Index Mode'] is 'paged' pageNum = +window.location.pathname.split('/')[2] From 66ebbd87f07cf77caf88a35a77b6b77b910af404 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Thu, 31 Oct 2013 00:21:12 +0100 Subject: [PATCH 11/49] Fix thread hiding after an index refresh. --- src/Filtering/ThreadHiding.coffee | 8 ++++++++ src/Miscellaneous/Index.coffee | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Filtering/ThreadHiding.coffee b/src/Filtering/ThreadHiding.coffee index 21bd4c211..34f9a54f2 100644 --- a/src/Filtering/ThreadHiding.coffee +++ b/src/Filtering/ThreadHiding.coffee @@ -4,6 +4,7 @@ ThreadHiding = @db = new DataBoard 'hiddenThreads' @syncCatalog() + $.on d, 'IndexRefresh', @onrefresh Thread.callbacks.push name: 'Thread Hiding' cb: @node @@ -14,6 +15,13 @@ ThreadHiding = return unless Conf['Thread Hiding'] $.prepend @OP.nodes.root, ThreadHiding.makeButton @, 'hide' + onrefresh: -> + for threadID, thread of g.BOARD.threads when thread.isHidden + hasStub = !!thread.stub + ThreadHiding.show thread + ThreadHiding.hide thread, hasStub + return + syncCatalog: -> # Sync hidden threads from the catalog into the index. hiddenThreads = ThreadHiding.db.get diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index 44db465b6..8045030c8 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -99,11 +99,13 @@ Index = error: err Main.handleErrors errors if errors + # Add the threads and
    s in a container to make sure all features work. + $.nodes nodes Main.callbackNodes Thread, threads Main.callbackNodes Post, posts + $.event 'IndexRefresh' Index.setIndex nodes - $.event 'IndexRefresh' setIndex: (nodes) -> $.rmAll Index.root $.add Index.root, Index.sort nodes From c2984275e574f8786577fbb657631d4af2f59fda Mon Sep 17 00:00:00 2001 From: Mayhem Date: Thu, 31 Oct 2013 00:55:45 +0100 Subject: [PATCH 12/49] Add index refresh shortcut in the header bar. Also fix lazy/lame implementation of Header.addShortcut() --- src/General/Header.coffee | 5 ++--- src/Images/ImageExpand.coffee | 2 +- src/Miscellaneous/Index.coffee | 7 +++++++ src/Posting/QR.coffee | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/General/Header.coffee b/src/General/Header.coffee index 12533ca17..7e401e5a9 100644 --- a/src/General/Header.coffee +++ b/src/General/Header.coffee @@ -257,11 +257,10 @@ Header = addShortcut: (el, index) -> shortcut = $.el 'span', className: 'shortcut' + shortcut.dataset.index = index $.add shortcut, el shortcuts = $ '#shortcuts', Header.bar - nodes = [shortcuts.childNodes...] - nodes.splice index, 0, shortcut - $.add shortcuts, nodes + $.add shortcuts, [shortcuts.childNodes...].concat(shortcut).sort (a, b) -> a.dataset.index - b.dataset.index menuToggle: (e) -> Header.menu.toggle e, @, g diff --git a/src/Images/ImageExpand.coffee b/src/Images/ImageExpand.coffee index 17b8066d3..c21bef490 100644 --- a/src/Images/ImageExpand.coffee +++ b/src/Images/ImageExpand.coffee @@ -7,7 +7,7 @@ ImageExpand = title: 'Expand All Images' href: 'javascript:;' $.on @EAI, 'click', ImageExpand.cb.toggleAll - Header.addShortcut @EAI, 2 + Header.addShortcut @EAI, 3 Post.callbacks.push name: 'Image Expansion' diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index 8045030c8..ea978a3fc 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -2,6 +2,13 @@ Index = init: -> return if g.VIEW isnt 'index' + button = $.el 'a', + className: 'index-refresh-shortcut fa fa-refresh' + title: 'Refresh Index' + href: 'javascript:;' + $.on button, 'click', Index.update + Header.addShortcut button, 1 + subEntries = [] subEntry = diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index d0be4c445..124e9a624 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -26,7 +26,7 @@ QR = $.event 'CloseMenu' QR.open() QR.nodes.com.focus() - Header.addShortcut sc, 1 + Header.addShortcut sc, 2 $.on d, 'QRGetSelectedPost', ({detail: cb}) -> cb QR.selected From 21e6902762ec44719c8a75c0ed149880f42f2785 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Thu, 31 Oct 2013 03:13:44 +0100 Subject: [PATCH 13/49] Dereference dead threads. I wonder if this will be enough... --- CHANGELOG.md | 2 +- src/General/Post.coffee | 7 +++++++ src/General/Thread.coffee | 6 ++++++ src/Miscellaneous/Index.coffee | 14 +++++++++++--- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69308c0cb..352ef32a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ Index navigation improvements: - - You can now refresh the index page you are on with the same keybind for refreshing threads. + - You can now refresh the index page you are on with the icon in the header bar or the same keybind for refreshing threads. - You can now switch between single-page and all-pages navigation via the "Index Navigation" header sub-menu. Added a keybind to open the catalog search field on index pages. diff --git a/src/General/Post.coffee b/src/General/Post.coffee index 27811afa5..901405495 100644 --- a/src/General/Post.coffee +++ b/src/General/Post.coffee @@ -193,6 +193,13 @@ class Post quotelink.textContent = quotelink.textContent.replace '\u00A0(Dead)', '' $.rmClass quotelink, 'deadlink' return + + collect: -> + @kill() + delete g.posts[@fullID] + delete @thread.posts[@] + delete @board.posts[@] + addClone: (context) -> new Clone @, context rmClone: (index) -> diff --git a/src/General/Thread.coffee b/src/General/Thread.coffee index 1f15bf714..97502ecd2 100644 --- a/src/General/Thread.coffee +++ b/src/General/Thread.coffee @@ -11,3 +11,9 @@ class Thread kill: -> @isDead = true @timeOfDeath = Date.now() + + collect: -> + for postID, post in @posts + post.collect() + delete g.threads[@fullID] + delete @board.threads[@] diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index ea978a3fc..e06236f2c 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -84,15 +84,17 @@ Index = for page in pages dataThr.push page.threads... - nodes = [] - threads = [] - posts = [] + nodes = [] + threads = [] + liveThreads = [] + posts = [] for data in dataThr threadRoot = Build.thread g.BOARD, data nodes.push threadRoot, $.el 'hr' unless thread = g.threads["#{g.BOARD}.#{data.no}"] thread = new Thread data.no, g.BOARD threads.push thread + liveThreads.push thread for postRoot in $$ '.thread > .postContainer', threadRoot continue if thread.posts[postRoot.id.match /\d+/] try @@ -106,6 +108,7 @@ Index = error: err Main.handleErrors errors if errors + Index.collectDeadThreads liveThreads # Add the threads and
    s in a container to make sure all features work. $.nodes nodes Main.callbackNodes Thread, threads @@ -128,3 +131,8 @@ Index = arr = nodes.splice nodes.indexOf(top), 2 nodes.splice i * 2, 0, arr... nodes + + collectDeadThreads: (liveThreads) -> + for threadID, thread of g.threads when thread not in liveThreads + thread.collect() + return From 553c4757686e6277b9fc4166a04b5746bfd6327e Mon Sep 17 00:00:00 2001 From: Mayhem Date: Thu, 31 Oct 2013 03:42:24 +0100 Subject: [PATCH 14/49] Reduce Index.sort() to 1 loop. --- src/Miscellaneous/Index.coffee | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index e06236f2c..8de6fa1de 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -122,14 +122,14 @@ Index = $('.pagelist').hidden = Conf['Index Mode'] isnt 'paged' sort: (nodes) -> return nodes unless Conf['Filter'] - # Put the highlighted thread on top of the index + # Put the highlighted thread &
    on top of the index # while keeping the original order they appear in. - tops = [] - for threadRoot in nodes by 2 when Get.threadFromRoot(threadRoot).isOnTop - tops.push threadRoot - for top, i in tops - arr = nodes.splice nodes.indexOf(top), 2 - nodes.splice i * 2, 0, arr... + i = offset = 0 + while threadRoot = nodes[i] + if Get.threadFromRoot(threadRoot).isOnTop + nodes.splice offset, 0, nodes.splice(i, 2)... + offset += 2 + i += 2 nodes collectDeadThreads: (liveThreads) -> From 72b38da3af8bb6773260b1478e39ae2f39986358 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Thu, 31 Oct 2013 04:13:35 +0100 Subject: [PATCH 15/49] Regenerate the QR threads list correctly after an index refresh. --- src/Posting/QR.coffee | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index 124e9a624..8830b5472 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -39,11 +39,15 @@ QR = $.on d, 'dragover', QR.dragOver $.on d, 'drop', QR.dropFile $.on d, 'dragstart dragend', QR.drag - $.on d, 'ThreadUpdate', -> - if g.DEAD - QR.abort() - else - QR.status() + switch g.VIEW + when 'index' + $.on d, 'IndexRefresh', QR.generatePostableThreadsList + when 'thread' + $.on d, 'ThreadUpdate', -> + if g.DEAD + QR.abort() + else + QR.status() QR.persist() if Conf['Persistent QR'] @@ -796,6 +800,24 @@ QR = return e.preventDefault() + generatePostableThreadsList: -> + list = QR.nodes.thread + options = [list.firstChild] + for thread of g.BOARD.threads + options.push $.el 'option', + value: thread + textContent: "Thread No.#{thread}" + val = list.value + $.rmAll list + $.add list, options + list.value = val + return unless list.value + # Fix the value if the option disappeared. + list.value = if g.VIEW is 'thread' + g.THREADID + else + 'new' + dialog: -> dialog = UI.dialog 'qr', 'top:0;right:0;', """ <%= grunt.file.read('html/Posting/QR.html').replace(/>\s+<').trim() %> @@ -867,12 +889,6 @@ QR = nodes.flag.dataset.default = '0' $.add nodes.form, nodes.flag - # Make a list of threads. - for thread of g.BOARD.threads - $.add nodes.thread, $.el 'option', - value: thread - textContent: "Thread No.#{thread}" - <% if (type === 'userscript') { %> # XXX Firefox lacks focusin/focusout support. for elm in $$ '*', QR.nodes.el @@ -906,6 +922,7 @@ QR = $.set 'QR Size', @style.cssText <% } %> + QR.generatePostableThreadsList() QR.persona.init() new QR.post true QR.status() From 79644546cef148a67e70280275c295f0d4889358 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Thu, 31 Oct 2013 04:16:47 +0100 Subject: [PATCH 16/49] Only listen to 'IndexRefresh' events when browsing the index. --- src/Monitoring/ThreadWatcher.coffee | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Monitoring/ThreadWatcher.coffee b/src/Monitoring/ThreadWatcher.coffee index 7697ce4cb..2f2809cd4 100644 --- a/src/Monitoring/ThreadWatcher.coffee +++ b/src/Monitoring/ThreadWatcher.coffee @@ -10,9 +10,12 @@ ThreadWatcher = @list = @dialog.lastElementChild $.on d, 'QRPostSuccessful', @cb.post - $.on d, 'ThreadUpdate', @cb.threadUpdate if g.VIEW is 'thread' $.on d, '4chanXInitFinished', @ready - $.on d, 'IndexRefresh', @refresh + switch g.VIEW + when 'index' + $.on d, 'IndexRefresh', @refresh + when 'thread' + $.on d, 'ThreadUpdate', @cb.threadUpdate now = Date.now() if (@db.data.lastChecked or 0) < now - 2 * $.HOUR From 22c561b796a85a093fc2778f84a66472b0f6087f Mon Sep 17 00:00:00 2001 From: Mayhem Date: Thu, 31 Oct 2013 04:32:45 +0100 Subject: [PATCH 17/49] Take into account dead threads in the watcher after an index refresh. --- src/Monitoring/ThreadWatcher.coffee | 11 +++++++++++ src/Posting/QR.coffee | 1 + 2 files changed, 12 insertions(+) diff --git a/src/Monitoring/ThreadWatcher.coffee b/src/Monitoring/ThreadWatcher.coffee index 2f2809cd4..71abca86b 100644 --- a/src/Monitoring/ThreadWatcher.coffee +++ b/src/Monitoring/ThreadWatcher.coffee @@ -13,6 +13,7 @@ ThreadWatcher = $.on d, '4chanXInitFinished', @ready switch g.VIEW when 'index' + $.on d, 'IndexRefresh', @cb.indexUpdate $.on d, 'IndexRefresh', @refresh when 'thread' $.on d, 'ThreadUpdate', @cb.threadUpdate @@ -73,6 +74,16 @@ ThreadWatcher = $.set 'AutoWatch', threadID else if Conf['Auto Watch Reply'] ThreadWatcher.add board.threads[threadID] + indexUpdate: -> + {db} = ThreadWatcher + for threadID, data of db.data.boards[g.BOARD.ID] when threadID not in g.BOARD.threads + if Conf['Auto Prune'] + ThreadWatcher.rm g.BOARD.ID, threadID + else + data.isDead = true + db.data.lastChecked = Date.now() + db.save() + ThreadWatcher.refresh() threadUpdate: (e) -> {thread} = e.detail return unless e.detail[404] and ThreadWatcher.db.get {boardID: thread.board.ID, threadID: thread.ID} diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index 8830b5472..b244a984b 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -801,6 +801,7 @@ QR = e.preventDefault() generatePostableThreadsList: -> + return unless QR.nodes list = QR.nodes.thread options = [list.firstChild] for thread of g.BOARD.threads From 0038f1f0118ebedddaf62990fc2cde0d7ad84c38 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Thu, 31 Oct 2013 05:21:31 +0100 Subject: [PATCH 18/49] Slightly better consistency between "reload"/"refresh"/"update" usage. --- html/Monitoring/ThreadUpdater.html | 2 +- src/General/Settings.coffee | 4 ++-- src/Monitoring/ThreadWatcher.coffee | 9 ++++----- src/Posting/QR.coffee | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/html/Monitoring/ThreadUpdater.html b/html/Monitoring/ThreadUpdater.html index 04a6a17b2..32b13b497 100644 --- a/html/Monitoring/ThreadUpdater.html +++ b/html/Monitoring/ThreadUpdater.html @@ -13,5 +13,5 @@
    - +
    diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee index 17cf7595d..65f2a5094 100644 --- a/src/General/Settings.coffee +++ b/src/General/Settings.coffee @@ -142,7 +142,7 @@ Settings = return div = $.el 'div', - innerHTML: ": Clear manually-hidden threads and posts on all boards. Refresh the page to apply." + innerHTML: ": Clear manually-hidden threads and posts on all boards. Reload the page to apply." button = $ 'button', div hiddenNum = 0 $.get 'hiddenThreads', boards: {}, (item) -> @@ -205,7 +205,7 @@ Settings = try data = JSON.parse e.target.result Settings.loadSettings data - if confirm 'Import successful. Refresh now?' + if confirm 'Import successful. Reload now?' window.location.reload() catch err output.textContent = 'Import failed due to an error.' diff --git a/src/Monitoring/ThreadWatcher.coffee b/src/Monitoring/ThreadWatcher.coffee index 71abca86b..b33e96e71 100644 --- a/src/Monitoring/ThreadWatcher.coffee +++ b/src/Monitoring/ThreadWatcher.coffee @@ -13,10 +13,9 @@ ThreadWatcher = $.on d, '4chanXInitFinished', @ready switch g.VIEW when 'index' - $.on d, 'IndexRefresh', @cb.indexUpdate - $.on d, 'IndexRefresh', @refresh + $.on d, 'IndexRefresh', @cb.onIndexRefresh when 'thread' - $.on d, 'ThreadUpdate', @cb.threadUpdate + $.on d, 'ThreadUpdate', @cb.onThreadRefresh now = Date.now() if (@db.data.lastChecked or 0) < now - 2 * $.HOUR @@ -74,7 +73,7 @@ ThreadWatcher = $.set 'AutoWatch', threadID else if Conf['Auto Watch Reply'] ThreadWatcher.add board.threads[threadID] - indexUpdate: -> + onIndexRefresh: -> {db} = ThreadWatcher for threadID, data of db.data.boards[g.BOARD.ID] when threadID not in g.BOARD.threads if Conf['Auto Prune'] @@ -84,7 +83,7 @@ ThreadWatcher = db.data.lastChecked = Date.now() db.save() ThreadWatcher.refresh() - threadUpdate: (e) -> + onThreadRefresh: (e) -> {thread} = e.detail return unless e.detail[404] and ThreadWatcher.db.get {boardID: thread.board.ID, threadID: thread.ID} # Update 404 status. diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index b244a984b..9f81f22a3 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -701,7 +701,7 @@ QR = imgContainer = $.el 'div', className: 'captcha-img' - title: 'Reload' + title: 'Reload reCAPTCHA' innerHTML: '' input = $.el 'input', className: 'captcha-input field' From 092d9317e0f58057d6045eafa523a2e7f14b9e39 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Thu, 31 Oct 2013 19:10:24 +0100 Subject: [PATCH 19/49] Add sorting options by bump order and creation date. --- CHANGELOG.md | 4 ++ lib/$.coffee | 2 +- src/General/Config.coffee | 4 +- src/Miscellaneous/Index.coffee | 67 +++++++++++++++++++++++----------- 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 352ef32a5..c075422c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Index navigation improvements: - You can now refresh the index page you are on with the icon in the header bar or the same keybind for refreshing threads. - You can now switch between single-page and all-pages navigation via the "Index Navigation" header sub-menu. + - Threads in the index can now be sorted by: + - bump order + - creation date + Added a keybind to open the catalog search field on index pages. ### 3.11.5 - *2013-10-03* diff --git a/lib/$.coffee b/lib/$.coffee index 8b2202304..711159e6c 100644 --- a/lib/$.coffee +++ b/lib/$.coffee @@ -104,7 +104,7 @@ $.rm = do -> (el) -> el.parentNode?.removeChild el $.rmAll = (root) -> # jsperf.com/emptify-element - while node = root.firstChild + for node in [root.childNodes...] # HTMLSelectElement.remove !== Element.remove root.removeChild node return diff --git a/src/General/Config.coffee b/src/General/Config.coffee index dd3220383..95f1f2a48 100644 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -139,8 +139,10 @@ Config = #//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/ #//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/ """ - 'Index Mode': 'paged' 'Custom CSS': false + Index: + 'Index Mode': 'paged' + 'Index Sort': 'bump' Header: 'Header auto-hide': false 'Bottom header': false diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index 8de6fa1de..51aadffc3 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -9,34 +9,44 @@ Index = $.on button, 'click', Index.update Header.addShortcut button, 1 - subEntries = [] - - subEntry = - el: $.el 'span', textContent: 'Index Mode' + modeEntry = + el: $.el 'span', textContent: 'Index mode' subEntries: [ { el: $.el 'label', innerHTML: ' Paged' } { el: $.el 'label', innerHTML: ' All threads' } ] - for label in subEntry.subEntries + for label in modeEntry.subEntries input = label.el.firstChild input.checked = Conf['Index Mode'] is input.value $.on input, 'change', $.cb.value $.on input, 'change', @update - subEntries.push subEntry + + sortEntry = + el: $.el 'span', textContent: 'Sort by' + subEntries: [ + { el: $.el 'label', innerHTML: ' Bump order' } + { el: $.el 'label', innerHTML: ' Creation date' } + ] + for label in sortEntry.subEntries + input = label.el.firstChild + input.checked = Conf['Index Sort'] is input.value + $.on input, 'change', $.cb.value + $.on input, 'change', @resort $.event 'AddMenuEntry', type: 'header' el: $.el 'span', textContent: 'Index Navigation' order: 90 - subEntries: subEntries + subEntries: [modeEntry, sortEntry] $.on d, '4chanXInitFinished', @initReady initReady: -> $.off d, '4chanXInitFinished', Index.initReady Index.root = $ '.board' - Index.setIndex $$ '.board > .thread, .board > hr', Index.root + Index.liveThreads = $$('.board > .thread', Index.root).map Get.threadFromRoot + Index.resort() return if Conf['Index Mode'] is 'paged' Index.update() @@ -84,17 +94,17 @@ Index = for page in pages dataThr.push page.threads... - nodes = [] - threads = [] - liveThreads = [] - posts = [] + nodes = [] + threads = [] + posts = [] + Index.liveThreads = [] for data in dataThr threadRoot = Build.thread g.BOARD, data nodes.push threadRoot, $.el 'hr' unless thread = g.threads["#{g.BOARD}.#{data.no}"] thread = new Thread data.no, g.BOARD threads.push thread - liveThreads.push thread + Index.liveThreads.push thread for postRoot in $$ '.thread > .postContainer', threadRoot continue if thread.posts[postRoot.id.match /\d+/] try @@ -108,7 +118,7 @@ Index = error: err Main.handleErrors errors if errors - Index.collectDeadThreads liveThreads + Index.collectDeadThreads() # Add the threads and
    s in a container to make sure all features work. $.nodes nodes Main.callbackNodes Thread, threads @@ -120,19 +130,32 @@ Index = $.rmAll Index.root $.add Index.root, Index.sort nodes $('.pagelist').hidden = Conf['Index Mode'] isnt 'paged' - sort: (nodes) -> + sort: (unsortedNodes) -> + nodes = [] + switch Conf['Index Sort'] + when 'bump' + for thread in Index.liveThreads + i = unsortedNodes.indexOf thread.OP.nodes.root.parentNode + nodes.push unsortedNodes[i], unsortedNodes[i + 1] + when 'birth' + dates = [] + for threadRoot, i in unsortedNodes by 2 + dates.push Get.threadFromRoot(threadRoot).OP.info.date + unsortedDates = [dates...] + for date in dates.sort((a, b) -> b - a) + i = unsortedDates.indexOf(date) * 2 + nodes.push unsortedNodes[i], unsortedNodes[i + 1] return nodes unless Conf['Filter'] # Put the highlighted thread &
    on top of the index # while keeping the original order they appear in. - i = offset = 0 - while threadRoot = nodes[i] - if Get.threadFromRoot(threadRoot).isOnTop - nodes.splice offset, 0, nodes.splice(i, 2)... - offset += 2 - i += 2 + offset = 0 + for threadRoot, i in nodes by 2 when Get.threadFromRoot(threadRoot).isOnTop + nodes.splice offset++ * 2, 0, nodes.splice(i, 2)... nodes + resort: -> + Index.setIndex $$ '.board > .thread, .board > hr', Index.root collectDeadThreads: (liveThreads) -> - for threadID, thread of g.threads when thread not in liveThreads + for threadID, thread of g.threads when thread not in Index.liveThreads thread.collect() return From d2587e4bb29b5d60499c71bada2b53828f0d5c24 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Fri, 1 Nov 2013 03:46:21 +0100 Subject: [PATCH 20/49] Refactor index generation/sorting. --- src/General/Main.coffee | 2 +- src/Miscellaneous/Index.coffee | 111 ++++++++++++++++----------------- 2 files changed, 55 insertions(+), 58 deletions(-) diff --git a/src/General/Main.coffee b/src/General/Main.coffee index a504f76ef..885b09231 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -170,7 +170,7 @@ Main = # Something might have gone wrong! Main.initStyle() - if board = $ '.board' + if g.VIEW is 'thread' and board = $ '.board' threads = [] posts = [] diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index 51aadffc3..51050bce2 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -19,7 +19,7 @@ Index = input = label.el.firstChild input.checked = Conf['Index Mode'] is input.value $.on input, 'change', $.cb.value - $.on input, 'change', @update + $.on input, 'change', @cb.mode sortEntry = el: $.el 'span', textContent: 'Sort by' @@ -31,7 +31,7 @@ Index = input = label.el.firstChild input.checked = Conf['Index Sort'] is input.value $.on input, 'change', $.cb.value - $.on input, 'change', @resort + $.on input, 'change', @cb.sort $.event 'AddMenuEntry', type: 'header' @@ -45,11 +45,15 @@ Index = initReady: -> $.off d, '4chanXInitFinished', Index.initReady Index.root = $ '.board' - Index.liveThreads = $$('.board > .thread', Index.root).map Get.threadFromRoot - Index.resort() - return if Conf['Index Mode'] is 'paged' Index.update() + cb: + mode: -> + Index.buildIndex() + sort: -> + Index.sort() + Index.buildIndex() + update: -> return unless navigator.onLine Index.req?.abort() @@ -86,27 +90,29 @@ Index = Header.scrollTo Index.root if Index.root.getBoundingClientRect().top < 0 parse: (pages) -> - if Conf['Index Mode'] is 'paged' - pageNum = +window.location.pathname.split('/')[2] - dataThr = pages[pageNum].threads - else - dataThr = [] - for page in pages - dataThr.push page.threads... - - nodes = [] - threads = [] - posts = [] - Index.liveThreads = [] - for data in dataThr - threadRoot = Build.thread g.BOARD, data - nodes.push threadRoot, $.el 'hr' - unless thread = g.threads["#{g.BOARD}.#{data.no}"] - thread = new Thread data.no, g.BOARD + Index.parseThreadList pages + Index.buildAll() + Index.sort() + Index.buildIndex() + parseThreadList: (pages) -> + Index.threadsNumPerPage = pages[0].threads.length + Index.liveThreadData = pages.reduce ((arr, next) -> arr.concat next.threads), [] + Index.liveThreadIDs = Index.liveThreadData.map (data) -> data.no + for threadID, thread of g.BOARD.threads when thread.ID not in Index.liveThreadIDs + thread.collect() + return + buildAll: -> + Index.nodes = [] + threads = [] + posts = [] + for threadData in Index.liveThreadData + threadRoot = Build.thread g.BOARD, threadData + Index.nodes.push threadRoot, $.el 'hr' + unless thread = g.BOARD.threads[threadData.no] + thread = new Thread threadData.no, g.BOARD threads.push thread - Index.liveThreads.push thread - for postRoot in $$ '.thread > .postContainer', threadRoot - continue if thread.posts[postRoot.id.match /\d+/] + postRoots = $$ '.thread > .postContainer', threadRoot + for postRoot in postRoots when postRoot.id.match(/\d+/)[0] not of thread.posts try posts.push new Post postRoot, thread, g.BOARD catch err @@ -118,44 +124,35 @@ Index = error: err Main.handleErrors errors if errors - Index.collectDeadThreads() # Add the threads and
    s in a container to make sure all features work. - $.nodes nodes + $.nodes Index.nodes Main.callbackNodes Thread, threads - Main.callbackNodes Post, posts - $.event 'IndexRefresh' - - Index.setIndex nodes - setIndex: (nodes) -> - $.rmAll Index.root - $.add Index.root, Index.sort nodes - $('.pagelist').hidden = Conf['Index Mode'] isnt 'paged' - sort: (unsortedNodes) -> - nodes = [] + Main.callbackNodes Post, posts + sort: -> switch Conf['Index Sort'] when 'bump' - for thread in Index.liveThreads - i = unsortedNodes.indexOf thread.OP.nodes.root.parentNode - nodes.push unsortedNodes[i], unsortedNodes[i + 1] + sortedThreadIDs = Index.liveThreadIDs when 'birth' - dates = [] - for threadRoot, i in unsortedNodes by 2 - dates.push Get.threadFromRoot(threadRoot).OP.info.date - unsortedDates = [dates...] - for date in dates.sort((a, b) -> b - a) - i = unsortedDates.indexOf(date) * 2 - nodes.push unsortedNodes[i], unsortedNodes[i + 1] - return nodes unless Conf['Filter'] + sortedThreadIDs = [Index.liveThreadIDs...].sort (a, b) -> b - a + Index.sortedNodes = [] + for threadID in sortedThreadIDs + i = Index.liveThreadIDs.indexOf(threadID) * 2 + Index.sortedNodes.push Index.nodes[i], Index.nodes[i + 1] + return unless Conf['Filter'] # Put the highlighted thread &
    on top of the index # while keeping the original order they appear in. offset = 0 - for threadRoot, i in nodes by 2 when Get.threadFromRoot(threadRoot).isOnTop - nodes.splice offset++ * 2, 0, nodes.splice(i, 2)... - nodes - resort: -> - Index.setIndex $$ '.board > .thread, .board > hr', Index.root - - collectDeadThreads: (liveThreads) -> - for threadID, thread of g.threads when thread not in Index.liveThreads - thread.collect() + for threadRoot, i in Index.sortedNodes by 2 when Get.threadFromRoot(threadRoot).isOnTop + Index.sortedNodes.splice offset++ * 2, 0, Index.sortedNodes.splice(i, 2)... return + buildIndex: -> + if Conf['Index Mode'] is 'paged' + pageNum = +window.location.pathname.split('/')[2] + nodesPerPage = Index.threadsNumPerPage * 2 + nodes = Index.sortedNodes.slice nodesPerPage * pageNum, nodesPerPage * (pageNum + 1) + else + nodes = Index.sortedNodes + $.event 'IndexRefresh' + $.rmAll Index.root + $.add Index.root, nodes + $('.pagelist').hidden = Conf['Index Mode'] isnt 'paged' From 29b96a4a07d9eca76fe7c07732ba03aff7510c72 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Fri, 1 Nov 2013 03:51:45 +0100 Subject: [PATCH 21/49] Make the index header shortcut spin while refreshing the index. --- src/Miscellaneous/Index.coffee | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index 51050bce2..edb9e17d9 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -2,12 +2,12 @@ Index = init: -> return if g.VIEW isnt 'index' - button = $.el 'a', + Index.button = $.el 'a', className: 'index-refresh-shortcut fa fa-refresh' title: 'Refresh Index' href: 'javascript:;' - $.on button, 'click', Index.update - Header.addShortcut button, 1 + $.on Index.button, 'click', Index.update + Header.addShortcut Index.button, 1 modeEntry = el: $.el 'span', textContent: 'Index mode' @@ -64,7 +64,9 @@ Index = onloadend: Index.load , whenModified: true + $.addClass Index.button, 'fa-spin' load: (e) -> + $.rmClass Index.button, 'fa-spin' {req, notice} = Index delete Index.req delete Index.notice From 5b6c1df0838d4fca36b80ae7da0f44c9756d216c Mon Sep 17 00:00:00 2001 From: Mayhem Date: Fri, 1 Nov 2013 04:04:34 +0100 Subject: [PATCH 22/49] Load the index asap on page load. --- css/style.css | 5 +++++ src/General/Main.coffee | 2 +- src/Miscellaneous/Index.coffee | 10 +++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/css/style.css b/css/style.css index a67c82043..544b955a0 100644 --- a/css/style.css +++ b/css/style.css @@ -362,6 +362,11 @@ a[href="javascript:;"] { overflow: hidden; } +/* Index */ +:root.index-loading .board { + display: none; +} + /* Announcement Hiding */ :root.hide-announcement #globalMessage, :root.hide-announcement-enabled #toggleMsgBtn { diff --git a/src/General/Main.coffee b/src/General/Main.coffee index 885b09231..0528e4f08 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -69,7 +69,7 @@ Main = initFeature 'Polyfill', Polyfill initFeature 'Header', Header initFeature 'Settings', Settings - initFeature 'Index Pager', Index + initFeature 'Index Generator', Index initFeature 'Announcement Hiding', PSAHiding initFeature 'Fourchan thingies', Fourchan initFeature 'Custom CSS', CustomCSS diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index edb9e17d9..ed9a8cd56 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -40,12 +40,12 @@ Index = order: 90 subEntries: [modeEntry, sortEntry] - $.on d, '4chanXInitFinished', @initReady - - initReady: -> - $.off d, '4chanXInitFinished', Index.initReady - Index.root = $ '.board' + $.addClass doc, 'index-loading' + Index.root = $.el 'div', className: 'board' Index.update() + $.asap (-> $('.board', doc) or d.readyState isnt 'loading'), -> + $.replace $('.board'), Index.root + $.rmClass doc, 'index-loading' cb: mode: -> From 855b7f0173f8aba4828730b65dfc95eb634de552 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Fri, 1 Nov 2013 04:52:21 +0100 Subject: [PATCH 23/49] Add 3 index sorting options: reply count, file count, last reply. --- CHANGELOG.md | 3 +++ src/Miscellaneous/Index.coffee | 13 +++++++++++++ src/Miscellaneous/Keybinds.coffee | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c075422c3..928d719eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Index navigation improvements: - Threads in the index can now be sorted by: - bump order - creation date + - reply count + - file count + - last reply Added a keybind to open the catalog search field on index pages. diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index ed9a8cd56..75b8bbdd1 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -26,6 +26,9 @@ Index = subEntries: [ { el: $.el 'label', innerHTML: ' Bump order' } { el: $.el 'label', innerHTML: ' Creation date' } + { el: $.el 'label', innerHTML: ' Reply count' } + { el: $.el 'label', innerHTML: ' File count' } + { el: $.el 'label', innerHTML: ' Last reply' } ] for label in sortEntry.subEntries input = label.el.firstChild @@ -136,6 +139,16 @@ Index = sortedThreadIDs = Index.liveThreadIDs when 'birth' sortedThreadIDs = [Index.liveThreadIDs...].sort (a, b) -> b - a + when 'replycount' + sortedThreadIDs = [Index.liveThreadData...].sort((a, b) -> b.replies - a.replies).map (data) -> data.no + when 'filecount' + sortedThreadIDs = [Index.liveThreadData...].sort((a, b) -> b.images - a.images).map (data) -> data.no + when 'lastreply' + sortedThreadIDs = [Index.liveThreadData...].sort((a, b) -> + a = a.last_replies[a.last_replies.length - 1] if 'last_replies' of a + b = b.last_replies[b.last_replies.length - 1] if 'last_replies' of b + b.no - a.no + ).map (data) -> data.no Index.sortedNodes = [] for threadID in sortedThreadIDs i = Index.liveThreadIDs.indexOf(threadID) * 2 diff --git a/src/Miscellaneous/Keybinds.coffee b/src/Miscellaneous/Keybinds.coffee index 464bf76c2..c5fb5d59a 100644 --- a/src/Miscellaneous/Keybinds.coffee +++ b/src/Miscellaneous/Keybinds.coffee @@ -7,7 +7,7 @@ Keybinds = init = -> $.off d, '4chanXInitFinished', init - $.on d, 'keydown', Keybinds.keydown + $.on d, 'keydown', Keybinds.keydown for node in $$ '[accesskey]' node.removeAttribute 'accesskey' return From 7e8fd3173370b3d8e5b0c9bd1c70c7ab6304b6f8 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Fri, 1 Nov 2013 13:57:24 +0100 Subject: [PATCH 24/49] Toggle the pagelist visibility via CSS to avoid a crash when the index update is done before the element exists. Also only send the If-Modified-Since HTTP header when we have something significant to send. --- css/style.css | 3 ++- lib/$.coffee | 2 +- src/Miscellaneous/Index.coffee | 6 +++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/css/style.css b/css/style.css index 544b955a0..8e57b3adc 100644 --- a/css/style.css +++ b/css/style.css @@ -363,7 +363,8 @@ a[href="javascript:;"] { } /* Index */ -:root.index-loading .board { +:root.index-loading .board, +:root.index-hide-pagelist .pagelist { display: none; } diff --git a/lib/$.coffee b/lib/$.coffee index 711159e6c..53ede1802 100644 --- a/lib/$.coffee +++ b/lib/$.coffee @@ -45,7 +45,7 @@ $.ajax = do -> type or= form and 'post' or 'get' r.open type, url, !sync if whenModified - r.setRequestHeader 'If-Modified-Since', lastModified[url] or '0' + r.setRequestHeader 'If-Modified-Since', lastModified[url] if url of lastModified $.on r, 'load', -> lastModified[url] = r.getResponseHeader 'Last-Modified' $.extend r, options $.extend r.upload, upCallbacks diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index 75b8bbdd1..986feb182 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -44,6 +44,7 @@ Index = subEntries: [modeEntry, sortEntry] $.addClass doc, 'index-loading' + Index.togglePagelist() Index.root = $.el 'div', className: 'board' Index.update() $.asap (-> $('.board', doc) or d.readyState isnt 'loading'), -> @@ -52,11 +53,15 @@ Index = cb: mode: -> + Index.togglePagelist() Index.buildIndex() sort: -> Index.sort() Index.buildIndex() + togglePagelist: -> + (if Conf['Index Mode'] is 'paged' then $.rmClass else $.addClass) doc, 'index-hide-pagelist' + update: -> return unless navigator.onLine Index.req?.abort() @@ -170,4 +175,3 @@ Index = $.event 'IndexRefresh' $.rmAll Index.root $.add Index.root, nodes - $('.pagelist').hidden = Conf['Index Mode'] isnt 'paged' From c8f7eef912cd285955037467c51c80ca1bb99fb9 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Fri, 1 Nov 2013 15:59:33 +0100 Subject: [PATCH 25/49] Fix and tweak watcher updater after an index refresh. --- src/Miscellaneous/Index.coffee | 4 ++-- src/Monitoring/ThreadWatcher.coffee | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index 986feb182..f75754fcf 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -169,9 +169,9 @@ Index = if Conf['Index Mode'] is 'paged' pageNum = +window.location.pathname.split('/')[2] nodesPerPage = Index.threadsNumPerPage * 2 - nodes = Index.sortedNodes.slice nodesPerPage * pageNum, nodesPerPage * (pageNum + 1) + nodes = Index.sortedNodes.slice nodesPerPage * pageNum, nodesPerPage * (pageNum + 1) else - nodes = Index.sortedNodes + nodes = Index.sortedNodes $.event 'IndexRefresh' $.rmAll Index.root $.add Index.root, nodes diff --git a/src/Monitoring/ThreadWatcher.coffee b/src/Monitoring/ThreadWatcher.coffee index b33e96e71..76abbf521 100644 --- a/src/Monitoring/ThreadWatcher.coffee +++ b/src/Monitoring/ThreadWatcher.coffee @@ -74,14 +74,14 @@ ThreadWatcher = else if Conf['Auto Watch Reply'] ThreadWatcher.add board.threads[threadID] onIndexRefresh: -> - {db} = ThreadWatcher - for threadID, data of db.data.boards[g.BOARD.ID] when threadID not in g.BOARD.threads + {db} = ThreadWatcher + boardID = g.BOARD.ID + for threadID, data of db.data.boards[boardID] when not data.isDead and threadID not of g.BOARD.threads if Conf['Auto Prune'] - ThreadWatcher.rm g.BOARD.ID, threadID + ThreadWatcher.db.delete {boardID, threadID} else data.isDead = true - db.data.lastChecked = Date.now() - db.save() + ThreadWatcher.db.set {boardID, threadID, val: data} ThreadWatcher.refresh() onThreadRefresh: (e) -> {thread} = e.detail @@ -114,7 +114,7 @@ ThreadWatcher = ThreadWatcher.status.textContent = status return if @status isnt 404 if Conf['Auto Prune'] - ThreadWatcher.rm boardID, threadID + ThreadWatcher.db.delete {boardID, threadID} else data.isDead = true ThreadWatcher.db.set {boardID, threadID, val: data} From 2f9e44a7734c18efc32318acc26c3f6642198aaa Mon Sep 17 00:00:00 2001 From: Mayhem Date: Fri, 1 Nov 2013 18:27:37 +0100 Subject: [PATCH 26/49] Update the threads sticky/closed status on each index refresh. Also fix the position of the sticky/closed icon. --- src/General/Build.coffee | 4 +-- src/General/Thread.coffee | 28 +++++++++++++++++++-- src/Miscellaneous/Index.coffee | 5 +++- src/Monitoring/ThreadUpdater.coffee | 39 +++++++++++------------------ 4 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/General/Build.coffee b/src/General/Build.coffee index b62418e13..8d018b8ee 100644 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -227,7 +227,7 @@ Build = "" + emailStart + "#{name or ''}" + tripcode + - capcodeStart + emailEnd + capcode + userID + flag + sticky + closed + + capcodeStart + emailEnd + capcode + userID + flag + ' ' + "#{date} " + "" + @@ -238,7 +238,7 @@ Build = else "/#{boardID}/res/#{threadID}#q#{postID}" }' title='Quote this post'>#{postID}" + - replyLink + + sticky + closed + replyLink + '' + '' + diff --git a/src/General/Thread.coffee b/src/General/Thread.coffee index 97502ecd2..e681983e5 100644 --- a/src/General/Thread.coffee +++ b/src/General/Thread.coffee @@ -3,11 +3,35 @@ class Thread toString: -> @ID constructor: (@ID, @board) -> - @fullID = "#{@board}.#{@ID}" - @posts = {} + @fullID = "#{@board}.#{@ID}" + @posts = {} + @isSticky = false + @isClosed = false + @postLimit = false + @fileLimit = false g.threads[@fullID] = board.threads[@] = @ + setStatus: (type, status) -> + name = "is#{type}" + return if @[name] is status + @[name] = status + return unless @OP + typeLC = type.toLowerCase() + unless status + $.rm $ ".#{typeLC}Icon", @OP.nodes.info + return + icon = $.el 'img', + src: "//static.4chan.org/image/#{typeLC}.gif" + alt: type + title: type + className: "#{typeLC}Icon" + root = if type is 'Closed' and @isSticky + $ '.stickyIcon', @OP.nodes.info + else + $ '[title="Quote this post"]', @OP.nodes.info + $.after root, [$.tn(' '), icon] + kill: -> @isDead = true @timeOfDeath = Date.now() diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index f75754fcf..cdb74fe63 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -118,7 +118,10 @@ Index = for threadData in Index.liveThreadData threadRoot = Build.thread g.BOARD, threadData Index.nodes.push threadRoot, $.el 'hr' - unless thread = g.BOARD.threads[threadData.no] + if thread = g.BOARD.threads[threadData.no] + thread.setStatus 'Sticky', !!threadData.sticky + thread.setStatus 'Closed', !!threadData.closed + else thread = new Thread threadData.no, g.BOARD threads.push thread postRoots = $$ '.thread > .postContainer', threadRoot diff --git a/src/Monitoring/ThreadUpdater.coffee b/src/Monitoring/ThreadUpdater.coffee index 03c8b5053..8e9c68779 100644 --- a/src/Monitoring/ThreadUpdater.coffee +++ b/src/Monitoring/ThreadUpdater.coffee @@ -155,38 +155,27 @@ ThreadUpdater = , whenModified: true - updateThreadStatus: (title, OP) -> - titleLC = title.toLowerCase() - return if ThreadUpdater.thread["is#{title}"] is !!OP[titleLC] - unless ThreadUpdater.thread["is#{title}"] = !!OP[titleLC] - message = if title is 'Sticky' - 'The thread is not a sticky anymore.' + updateThreadStatus: (type, status) -> + return unless hasChanged = ThreadUpdater.thread["is#{type}"] isnt status + ThreadUpdater.thread.setStatus type, status + change = if type is 'Sticky' + if status + 'now a sticky' else - 'The thread is not closed anymore.' - new Notice 'info', message, 30 - $.rm $ ".#{titleLC}Icon", ThreadUpdater.thread.OP.nodes.info - return - message = if title is 'Sticky' - 'The thread is now a sticky.' + 'not a sticky anymore' else - 'The thread is now closed.' - new Notice 'info', message, 30 - icon = $.el 'img', - src: "//static.4chan.org/image/#{titleLC}.gif" - alt: title - title: title - className: "#{titleLC}Icon" - root = $ '[title="Quote this post"]', ThreadUpdater.thread.OP.nodes.info - if title is 'Closed' - root = $('.stickyIcon', ThreadUpdater.thread.OP.nodes.info) or root - $.after root, [$.tn(' '), icon] + if status + 'now closed' + else + 'not closed anymore' + new Notice 'info', "The thread is #{change}.", 30 parse: (postObjects) -> OP = postObjects[0] Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler - ThreadUpdater.updateThreadStatus 'Sticky', OP - ThreadUpdater.updateThreadStatus 'Closed', OP + ThreadUpdater.updateThreadStatus 'Sticky', !!OP.sticky + ThreadUpdater.updateThreadStatus 'Closed', !!OP.closed ThreadUpdater.thread.postLimit = !!OP.bumplimit ThreadUpdater.thread.fileLimit = !!OP.imagelimit From 9c81b6e459edacdd1da1e6d771badde7a85c6442 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Fri, 1 Nov 2013 18:40:36 +0100 Subject: [PATCH 27/49] Put the stickies on top of the index. --- src/Miscellaneous/Index.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Miscellaneous/Index.coffee b/src/Miscellaneous/Index.coffee index cdb74fe63..16fef5eaa 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/Miscellaneous/Index.coffee @@ -161,6 +161,10 @@ Index = for threadID in sortedThreadIDs i = Index.liveThreadIDs.indexOf(threadID) * 2 Index.sortedNodes.push Index.nodes[i], Index.nodes[i + 1] + # Put the sticky threads on top of the index.g + offset = 0 + for threadRoot, i in Index.sortedNodes by 2 when Get.threadFromRoot(threadRoot).isSticky + Index.sortedNodes.splice offset++ * 2, 0, Index.sortedNodes.splice(i, 2)... return unless Conf['Filter'] # Put the highlighted thread &
    on top of the index # while keeping the original order they appear in. From feb8e09c5a80c57cbd1ee55acd32c3a4662e6f1f Mon Sep 17 00:00:00 2001 From: Mayhem Date: Fri, 1 Nov 2013 18:57:17 +0100 Subject: [PATCH 28/49] Support hidpi post icons. --- src/General/Build.coffee | 18 +++++++++++------- src/General/Thread.coffee | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/General/Build.coffee b/src/General/Build.coffee index 8d018b8ee..c225fa3f0 100644 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -60,6 +60,10 @@ Build = isOP = postID is threadID staticPath = '//static.4chan.org/image/' + gifIcon = if window.devicePixelRatio >= 2 + '@2x.gif' + else + '.gif' if email emailStart = '' @@ -82,21 +86,21 @@ Build = capcodeClass = " capcodeAdmin" capcodeStart = " ## Admin" - capcode = " " when 'mod' capcodeClass = " capcodeMod" capcodeStart = " ## Mod" - capcode = " " when 'developer' capcodeClass = " capcodeDeveloper" capcodeStart = " ## Developer" - capcode = " " else @@ -114,11 +118,11 @@ Build = if file?.isDeleted fileHTML = if isOP "
    " + - "File deleted." + + "File deleted." + "
    " else "
    " + - "File deleted." + + "File deleted." + "
    " else if file ext = file.name[-3..] @@ -178,11 +182,11 @@ Build = '' sticky = if isSticky - " Sticky" + " Sticky" else '' closed = if isClosed - " Closed" + " Closed" else '' diff --git a/src/General/Thread.coffee b/src/General/Thread.coffee index e681983e5..a02f4bc72 100644 --- a/src/General/Thread.coffee +++ b/src/General/Thread.coffee @@ -22,7 +22,7 @@ class Thread $.rm $ ".#{typeLC}Icon", @OP.nodes.info return icon = $.el 'img', - src: "//static.4chan.org/image/#{typeLC}.gif" + src: "//static.4chan.org/image/#{typeLC}#{if window.devicePixelRatio >= 2 then '@2x' else ''}.gif" alt: type title: type className: "#{typeLC}Icon" From 47710a7c6e424f8e09afadafbb1eb3d7b644923e Mon Sep 17 00:00:00 2001 From: Mayhem Date: Fri, 1 Nov 2013 22:06:59 +0100 Subject: [PATCH 29/49] Build the pagelist dynamically. --- Gruntfile.coffee | 1 + css/style.css | 1 + html/General/Index-pagelist.html | 11 ++++ src/{Miscellaneous => General}/Index.coffee | 62 +++++++++++++++++---- 4 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 html/General/Index-pagelist.html rename src/{Miscellaneous => General}/Index.coffee (81%) diff --git a/Gruntfile.coffee b/Gruntfile.coffee index e7226450a..7e0db8c07 100644 --- a/Gruntfile.coffee +++ b/Gruntfile.coffee @@ -17,6 +17,7 @@ module.exports = (grunt) -> 'src/General/Header.coffee' 'src/General/Notice.coffee' 'src/General/Settings.coffee' + 'src/General/Index.coffee' 'src/General/Get.coffee' 'src/General/Build.coffee' # Features --> diff --git a/css/style.css b/css/style.css index 8e57b3adc..31f6e7d8b 100644 --- a/css/style.css +++ b/css/style.css @@ -364,6 +364,7 @@ a[href="javascript:;"] { /* Index */ :root.index-loading .board, +:root.index-loading .pagelist, :root.index-hide-pagelist .pagelist { display: none; } diff --git a/html/General/Index-pagelist.html b/html/General/Index-pagelist.html new file mode 100644 index 000000000..d8b1d7c99 --- /dev/null +++ b/html/General/Index-pagelist.html @@ -0,0 +1,11 @@ +
    +
    + diff --git a/src/Miscellaneous/Index.coffee b/src/General/Index.coffee similarity index 81% rename from src/Miscellaneous/Index.coffee rename to src/General/Index.coffee index 16fef5eaa..0915f01c4 100644 --- a/src/Miscellaneous/Index.coffee +++ b/src/General/Index.coffee @@ -25,10 +25,10 @@ Index = el: $.el 'span', textContent: 'Sort by' subEntries: [ { el: $.el 'label', innerHTML: ' Bump order' } + { el: $.el 'label', innerHTML: ' Last reply' } { el: $.el 'label', innerHTML: ' Creation date' } { el: $.el 'label', innerHTML: ' Reply count' } { el: $.el 'label', innerHTML: ' File count' } - { el: $.el 'label', innerHTML: ' Last reply' } ] for label in sortEntry.subEntries input = label.el.firstChild @@ -44,11 +44,16 @@ Index = subEntries: [modeEntry, sortEntry] $.addClass doc, 'index-loading' - Index.togglePagelist() - Index.root = $.el 'div', className: 'board' Index.update() - $.asap (-> $('.board', doc) or d.readyState isnt 'loading'), -> - $.replace $('.board'), Index.root + Index.root = $.el 'div', className: 'board' + Index.pagelist = $.el 'div', + className: 'pagelist' + innerHTML: """ + <%= grunt.file.read('html/General/Index-pagelist.html').replace(/>\s+<').trim() %> + """ + $.asap (-> $('.pagelist', doc) or d.readyState isnt 'loading'), -> + $.replace $('.board'), Index.root + $.replace $('.pagelist'), Index.pagelist $.rmClass doc, 'index-loading' cb: @@ -61,6 +66,39 @@ Index = togglePagelist: -> (if Conf['Index Mode'] is 'paged' then $.rmClass else $.addClass) doc, 'index-hide-pagelist' + buildPagelist: -> + pagesRoot = $ '.pages', Index.pagelist + if pagesRoot.childElementCount isnt Index.pagesNum + nodes = [] + for i in [0..Index.pagesNum - 1] + a = $.el 'a', + textContent: i + href: if i then i else './' + nodes.push $.tn('['), a, $.tn '] ' + $.rmAll pagesRoot + $.add pagesRoot, nodes + Index.setPage() + setPage: -> + pageNum = +window.location.pathname.split('/')[2] + pagesRoot = $ '.pages', Index.pagelist + # Previous/Next buttons + prev = pagesRoot.previousSibling.firstChild + next = pagesRoot.nextSibling.firstChild + href = Math.max pageNum - 1, 0 + prev.href = if href is 0 then './' else href + prev.firstChild.disabled = href is pageNum + href = Math.min pageNum + 1, Index.pagesNum - 1 + next.href = if href is 0 then './' else href + next.firstChild.disabled = href is pageNum + # current page + if strong = $ 'strong', pagesRoot + return if +strong.textContent is pageNum + $.replace strong, strong.firstChild + else + strong = $.el 'strong' + a = pagesRoot.children[pageNum] + $.before a, strong + $.add strong, a update: -> return unless navigator.onLine @@ -104,7 +142,9 @@ Index = Index.buildAll() Index.sort() Index.buildIndex() + Index.buildPagelist() parseThreadList: (pages) -> + Index.pagesNum = pages.length Index.threadsNumPerPage = pages[0].threads.length Index.liveThreadData = pages.reduce ((arr, next) -> arr.concat next.threads), [] Index.liveThreadIDs = Index.liveThreadData.map (data) -> data.no @@ -145,18 +185,18 @@ Index = switch Conf['Index Sort'] when 'bump' sortedThreadIDs = Index.liveThreadIDs - when 'birth' - sortedThreadIDs = [Index.liveThreadIDs...].sort (a, b) -> b - a - when 'replycount' - sortedThreadIDs = [Index.liveThreadData...].sort((a, b) -> b.replies - a.replies).map (data) -> data.no - when 'filecount' - sortedThreadIDs = [Index.liveThreadData...].sort((a, b) -> b.images - a.images).map (data) -> data.no when 'lastreply' sortedThreadIDs = [Index.liveThreadData...].sort((a, b) -> a = a.last_replies[a.last_replies.length - 1] if 'last_replies' of a b = b.last_replies[b.last_replies.length - 1] if 'last_replies' of b b.no - a.no ).map (data) -> data.no + when 'birth' + sortedThreadIDs = [Index.liveThreadIDs...].sort (a, b) -> b - a + when 'replycount' + sortedThreadIDs = [Index.liveThreadData...].sort((a, b) -> b.replies - a.replies).map (data) -> data.no + when 'filecount' + sortedThreadIDs = [Index.liveThreadData...].sort((a, b) -> b.images - a.images).map (data) -> data.no Index.sortedNodes = [] for threadID in sortedThreadIDs i = Index.liveThreadIDs.indexOf(threadID) * 2 From bc72f0f763696f7c12a68d20b1ab68de7315823a Mon Sep 17 00:00:00 2001 From: Mayhem Date: Fri, 1 Nov 2013 23:08:31 +0100 Subject: [PATCH 30/49] Create importHTML function, and use it. --- Gruntfile.coffee | 8 ++++++- src/General/Header.coffee | 4 +--- src/General/Index.coffee | 4 +--- src/General/Settings.coffee | 36 ++++++++--------------------- src/Monitoring/Favicon.coffee | 4 ++-- src/Monitoring/ThreadStats.coffee | 4 +--- src/Monitoring/ThreadUpdater.coffee | 4 +--- src/Monitoring/ThreadWatcher.coffee | 4 +--- src/Posting/QR.coffee | 4 +--- 9 files changed, 24 insertions(+), 48 deletions(-) diff --git a/Gruntfile.coffee b/Gruntfile.coffee index 7e0db8c07..e8dcf07dd 100644 --- a/Gruntfile.coffee +++ b/Gruntfile.coffee @@ -1,11 +1,17 @@ module.exports = (grunt) -> + importHTML = (filename) -> + "\"\"\"#{grunt.file.read("html/#{filename}.html").replace(/^\s+|\s+$ grunt.config 'pkg' + get: -> + pkg = grunt.config 'pkg' + pkg.importHTML = importHTML + pkg enumerable: true ) coffee: diff --git a/src/General/Header.coffee b/src/General/Header.coffee index 7e401e5a9..c94e697ae 100644 --- a/src/General/Header.coffee +++ b/src/General/Header.coffee @@ -2,9 +2,7 @@ Header = init: -> headerEl = $.el 'div', id: 'header' - innerHTML: """ - <%= grunt.file.read('html/General/Header.html').replace(/>\s+<').trim() %> - """ + innerHTML: <%= importHTML('General/Header') %> @bar = $ '#header-bar', headerEl @toggle = $ '#toggle-header-bar', @bar diff --git a/src/General/Index.coffee b/src/General/Index.coffee index 0915f01c4..c930bb3d0 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -48,9 +48,7 @@ Index = Index.root = $.el 'div', className: 'board' Index.pagelist = $.el 'div', className: 'pagelist' - innerHTML: """ - <%= grunt.file.read('html/General/Index-pagelist.html').replace(/>\s+<').trim() %> - """ + innerHTML: <%= importHTML('General/Index-pagelist') %> $.asap (-> $('.pagelist', doc) or d.readyState isnt 'loading'), -> $.replace $('.board'), Index.root $.replace $('.pagelist'), Index.pagelist diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee index 65f2a5094..4c0499e4c 100644 --- a/src/General/Settings.coffee +++ b/src/General/Settings.coffee @@ -61,9 +61,7 @@ Settings = return if Settings.dialog $.event 'CloseMenu' - html = """ - <%= grunt.file.read('html/General/Settings.html').replace(/>\s+<').trim() %> - """ + html = <%= importHTML('General/Settings') %> Settings.dialog = overlay = $.el 'div', id: 'overlay' @@ -113,9 +111,7 @@ Settings = section.scrollTop = 0 main: (section) -> - section.innerHTML = """ - <%= grunt.file.read('html/General/Settings-section-Main.html').replace(/>\s+<').trim() %> - """ + section.innerHTML = <%= importHTML('General/Settings-section-Main') %> $.on $('.export', section), 'click', Settings.export $.on $('.import', section), 'click', Settings.import $.on $('input', section), 'change', Settings.onImport @@ -290,9 +286,7 @@ Settings = data filter: (section) -> - section.innerHTML = """ - <%= grunt.file.read('html/General/Settings-section-Filter.html').replace(/>\s+<').trim() %> - """ + section.innerHTML = <%= importHTML('General/Settings-section-Filter') %> select = $ 'select', section $.on select, 'change', Settings.selectFilter Settings.selectFilter.call select @@ -309,32 +303,24 @@ Settings = $.on ta, 'change', $.cb.value $.add div, ta return - div.innerHTML = """ - <%= grunt.file.read('html/General/Settings-section-Filter-guide.html').replace(/>\s+<').trim() %> - """ + div.innerHTML = <%= importHTML('General/Settings-section-Filter-guide') %> qr: (section) -> - section.innerHTML = """ - <%= grunt.file.read('html/General/Settings-section-QR.html').replace(/>\s+<').trim() %> - """ + section.innerHTML = <%= importHTML('General/Settings-section-QR') %> ta = $ 'textarea', section $.get 'QR.personas', Conf['QR.personas'], (item) -> ta.value = item['QR.personas'] $.on ta, 'change', $.cb.value sauce: (section) -> - section.innerHTML = """ - <%= grunt.file.read('html/General/Settings-section-Sauce.html').replace(/>\s+<').trim() %> - """ + section.innerHTML = <%= importHTML('General/Settings-section-Sauce') %> ta = $ 'textarea', section $.get 'sauces', Conf['sauces'], (item) -> ta.value = item['sauces'] $.on ta, 'change', $.cb.value rice: (section) -> - section.innerHTML = """ - <%= grunt.file.read('html/General/Settings-section-Rice.html').replace(/>\s+<').trim() %> - """ + section.innerHTML = <%= importHTML('General/Settings-section-Rice') %> items = {} inputs = {} for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss'] @@ -395,9 +381,7 @@ Settings = CustomCSS.update() archives: (section) -> - section.innerHTML = """ - <%= grunt.file.read('html/General/Settings-section-Archives.html').replace(/>\s+<').trim() %> - """ + section.innerHTML = <%= importHTML('General/Settings-section-Archives') %> showLastUpdateTime = (time) -> $('time', section).textContent = new Date(time).toLocaleString() @@ -470,9 +454,7 @@ Settings = $.set 'selectedArchives', selectedArchives keybinds: (section) -> - section.innerHTML = """ - <%= grunt.file.read('html/General/Settings-section-Keybinds.html').replace(/>\s+<').trim() %> - """ + section.innerHTML = <%= importHTML('General/Settings-section-Keybinds') %> tbody = $ 'tbody', section items = {} inputs = {} diff --git a/src/Monitoring/Favicon.coffee b/src/Monitoring/Favicon.coffee index 806134034..6085a6f2c 100644 --- a/src/Monitoring/Favicon.coffee +++ b/src/Monitoring/Favicon.coffee @@ -45,5 +45,5 @@ Favicon = Favicon.unread = Favicon.unreadNSFW Favicon.unreadY = Favicon.unreadNSFWY - dead: 'data:image/gif;base64,<%= grunt.file.read("img/favicons/dead.gif", {encoding: "base64"}) %>' - logo: 'data:image/png;base64,<%= grunt.file.read("img/icon128.png", {encoding: "base64"}) %>' + dead: 'data:image/gif;base64,<%= grunt.file.read("img/favicons/dead.gif", {encoding: "base64"}) %>' + logo: 'data:image/png;base64,<%= grunt.file.read("img/icon128.png", {encoding: "base64"}) %>' diff --git a/src/Monitoring/ThreadStats.coffee b/src/Monitoring/ThreadStats.coffee index f80154cae..89d1be0c8 100644 --- a/src/Monitoring/ThreadStats.coffee +++ b/src/Monitoring/ThreadStats.coffee @@ -1,9 +1,7 @@ ThreadStats = init: -> return if g.VIEW isnt 'thread' or !Conf['Thread Stats'] - @dialog = UI.dialog 'thread-stats', 'bottom: 0; left: 0;', """ - <%= grunt.file.read('html/Monitoring/ThreadStats.html').replace(/>\s+<').trim() %> - """ + @dialog = UI.dialog 'thread-stats', 'bottom: 0; left: 0;', <%= importHTML('Monitoring/ThreadStats') %> @postCountEl = $ '#post-count', @dialog @fileCountEl = $ '#file-count', @dialog diff --git a/src/Monitoring/ThreadUpdater.coffee b/src/Monitoring/ThreadUpdater.coffee index 8e9c68779..602f45383 100644 --- a/src/Monitoring/ThreadUpdater.coffee +++ b/src/Monitoring/ThreadUpdater.coffee @@ -7,9 +7,7 @@ ThreadUpdater = checked = if Conf[name] then 'checked' else '' html += "
    " - html = """ - <%= grunt.file.read('html/Monitoring/ThreadUpdater.html').replace(/>\s+<').trim() %> - """ + html = <%= importHTML('Monitoring/ThreadUpdater') %> @dialog = UI.dialog 'updater', 'bottom: 0; right: 0;', html @timer = $ '#update-timer', @dialog diff --git a/src/Monitoring/ThreadWatcher.coffee b/src/Monitoring/ThreadWatcher.coffee index 76abbf521..3b021d5a7 100644 --- a/src/Monitoring/ThreadWatcher.coffee +++ b/src/Monitoring/ThreadWatcher.coffee @@ -3,9 +3,7 @@ ThreadWatcher = 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() %> - """ + @dialog = UI.dialog 'thread-watcher', 'top: 50px; left: 0px;', <%= importHTML('Monitoring/ThreadWatcher') %> @status = $ '#watcher-status', @dialog @list = @dialog.lastElementChild diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index 9f81f22a3..7a8f829ea 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -820,9 +820,7 @@ QR = 'new' dialog: -> - dialog = UI.dialog 'qr', 'top:0;right:0;', """ - <%= grunt.file.read('html/Posting/QR.html').replace(/>\s+<').trim() %> - """ + dialog = UI.dialog 'qr', 'top:0;right:0;', <%= importHTML('Posting/QR') %> QR.nodes = nodes = el: dialog From 0540dcf30f2d5c1b3170f6f708987b7fd0369963 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sat, 2 Nov 2013 00:52:12 +0100 Subject: [PATCH 31/49] Instantaneous index page navigation. Also fix page navigation keybinds. --- CHANGELOG.md | 13 ++++---- css/style.css | 3 +- html/General/Index-pagelist.html | 4 +-- src/General/Index.coffee | 50 ++++++++++++++++++++++++++----- src/Miscellaneous/Keybinds.coffee | 17 ++++++----- 5 files changed, 61 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 928d719eb..bffeab527 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ Index navigation improvements: - You can now refresh the index page you are on with the icon in the header bar or the same keybind for refreshing threads. - - You can now switch between single-page and all-pages navigation via the "Index Navigation" header sub-menu. + - You can now switch between paged and all-threads index modes via the "Index Navigation" header sub-menu. - Threads in the index can now be sorted by: - - bump order - - creation date - - reply count - - file count - - last reply + - Bump order + - Last reply + - Creation date + - Reply count + - File count + - Navigating across index pages is now instantaneous. Added a keybind to open the catalog search field on index pages. diff --git a/css/style.css b/css/style.css index 31f6e7d8b..751f3b772 100644 --- a/css/style.css +++ b/css/style.css @@ -364,8 +364,7 @@ a[href="javascript:;"] { /* Index */ :root.index-loading .board, -:root.index-loading .pagelist, -:root.index-hide-pagelist .pagelist { +:root.index-loading .pagelist { display: none; } diff --git a/html/General/Index-pagelist.html b/html/General/Index-pagelist.html index d8b1d7c99..e6923bb7a 100644 --- a/html/General/Index-pagelist.html +++ b/html/General/Index-pagelist.html @@ -1,11 +1,11 @@
    diff --git a/src/General/Index.coffee b/src/General/Index.coffee index c930bb3d0..13eb065ab 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -2,12 +2,12 @@ Index = init: -> return if g.VIEW isnt 'index' - Index.button = $.el 'a', + @button = $.el 'a', className: 'index-refresh-shortcut fa fa-refresh' title: 'Refresh Index' href: 'javascript:;' - $.on Index.button, 'click', Index.update - Header.addShortcut Index.button, 1 + $.on @button, 'click', @update + Header.addShortcut @button, 1 modeEntry = el: $.el 'span', textContent: 'Index mode' @@ -44,11 +44,15 @@ Index = subEntries: [modeEntry, sortEntry] $.addClass doc, 'index-loading' - Index.update() - Index.root = $.el 'div', className: 'board' - Index.pagelist = $.el 'div', + @update() + @root = $.el 'div', className: 'board' + @pagelist = $.el 'div', className: 'pagelist' + hidden: true innerHTML: <%= importHTML('General/Index-pagelist') %> + Index.currentPage = +window.location.pathname.split('/')[2] + $.on window, 'popstate', @cb.popstate + $.on @pagelist, 'click', @cb.pageNav $.asap (-> $('.pagelist', doc) or d.readyState isnt 'loading'), -> $.replace $('.board'), Index.root $.replace $('.pagelist'), Index.pagelist @@ -61,9 +65,38 @@ Index = sort: -> Index.sort() Index.buildIndex() + popstate: (e) -> + Index.currentPage = +window.location.pathname.split('/')[2] + Index.pageLoad() + pageNav: (e) -> + return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0 + switch e.target.nodeName + when 'BUTTON' + a = e.target.parentNode + when 'A' + a = e.target + else + return + e.preventDefault() + Index.pageNav +a.pathname.split('/')[2] + + scrollToIndex: -> + Header.scrollTo Index.root if Index.root.getBoundingClientRect().top < 0 + + pageNav: (pageNum) -> + return if Index.currentPage is pageNum + history.pushState null, '', if pageNum is 0 then './' else pageNum + Index.currentPage = pageNum + Index.pageLoad() + pageLoad: -> + return unless 'currentPage' of Index # unnecessary popstate on page load + return if Conf['Index Mode'] isnt 'paged' + Index.buildIndex() + Index.setPage() + Index.scrollToIndex() togglePagelist: -> - (if Conf['Index Mode'] is 'paged' then $.rmClass else $.addClass) doc, 'index-hide-pagelist' + Index.pagelist.hidden = Conf['Index Mode'] isnt 'paged' buildPagelist: -> pagesRoot = $ '.pages', Index.pagelist if pagesRoot.childElementCount isnt Index.pagesNum @@ -76,6 +109,7 @@ Index = $.rmAll pagesRoot $.add pagesRoot, nodes Index.setPage() + Index.togglePagelist() setPage: -> pageNum = +window.location.pathname.split('/')[2] pagesRoot = $ '.pages', Index.pagelist @@ -134,7 +168,7 @@ Index = notice.el.lastElementChild.textContent = 'Index refreshed!' setTimeout notice.close, $.SECOND - Header.scrollTo Index.root if Index.root.getBoundingClientRect().top < 0 + Index.scrollToIndex() parse: (pages) -> Index.parseThreadList pages Index.buildAll() diff --git a/src/Miscellaneous/Keybinds.coffee b/src/Miscellaneous/Keybinds.coffee index c5fb5d59a..7ad88b273 100644 --- a/src/Miscellaneous/Keybinds.coffee +++ b/src/Miscellaneous/Keybinds.coffee @@ -74,17 +74,18 @@ Keybinds = Keybinds.img threadRoot, true # Board Navigation when Conf['Front page'] - window.location = "/#{g.BOARD}/0#delform" + if g.VIEW is 'index' + Index.pageNav 0 + else + window.location = "/#{g.BOARD}/" when Conf['Open front page'] - $.open "/#{g.BOARD}/#delform" + $.open "/#{g.BOARD}/" when Conf['Next page'] - return if Conf['Index Mode'] isnt 'paged' - if form = $ '.next form' - window.location = form.action + return unless g.VIEW is 'index' and Conf['Index Mode'] is 'paged' + $('.next a', Index.pagelist).click() when Conf['Previous page'] - return if Conf['Index Mode'] isnt 'paged' - if form = $ '.prev form' - window.location = form.action + return unless g.VIEW is 'index' and Conf['Index Mode'] is 'paged' + $('.prev a', Index.pagelist).click() when Conf['Search form'] $.id('search-btn').click() # Thread Navigation From 6dcf11db6665a7dfc67d43f7969f6190aebda415 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sat, 2 Nov 2013 04:26:08 +0100 Subject: [PATCH 32/49] Don't break Header.hashScroll() for #top and #bottom. --- src/General/Header.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/General/Header.coffee b/src/General/Header.coffee index c94e697ae..2b8465933 100644 --- a/src/General/Header.coffee +++ b/src/General/Header.coffee @@ -242,7 +242,8 @@ Header = $('input[name=boardnav]', settings).focus() hashScroll: -> - return unless (hash = @location.hash[1..]) and post = $.id hash + hash = @location.hash[1..] + return unless /^p\d+$/.test(hash) and post = $.id hash return if (Get.postFromRoot post).isHidden Header.scrollTo post scrollTo: (root) -> From 4c00c6f9aed17e7d36a40c404395291574ecde70 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sat, 2 Nov 2013 13:46:19 +0100 Subject: [PATCH 33/49] Add thread refresh shortcut. --- src/Monitoring/ThreadUpdater.coffee | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Monitoring/ThreadUpdater.coffee b/src/Monitoring/ThreadUpdater.coffee index 602f45383..b35f9502e 100644 --- a/src/Monitoring/ThreadUpdater.coffee +++ b/src/Monitoring/ThreadUpdater.coffee @@ -2,6 +2,13 @@ ThreadUpdater = init: -> return if g.VIEW isnt 'thread' or !Conf['Thread Updater'] + @button = $.el 'a', + className: 'thread-refresh-shortcut fa fa-refresh' + title: 'Refresh Thread' + href: 'javascript:;' + $.on @button, 'click', @update + Header.addShortcut @button, 1 + html = '' for name, conf of Config.updater.checkbox checked = if Conf[name] then 'checked' else '' @@ -79,6 +86,7 @@ ThreadUpdater = ThreadUpdater.interval = @value = val $.cb.value.call @ if e load: (e) -> + $.rmClass ThreadUpdater.button, 'fa-spin' {req} = ThreadUpdater delete ThreadUpdater.req if e.type isnt 'loadend' # timeout or abort @@ -141,6 +149,7 @@ ThreadUpdater = update: -> return unless navigator.onLine + $.addClass ThreadUpdater.button, 'fa-spin' ThreadUpdater.count() ThreadUpdater.set 'timer', '...' ThreadUpdater.req?.abort() From 7fd72676cb0b8cc7fd50cdde89dde1f8004d5118 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sat, 2 Nov 2013 13:56:59 +0100 Subject: [PATCH 34/49] Fix spoiler thumbnails when building threads. --- src/General/Build.coffee | 2 ++ src/Miscellaneous/ExpandComment.coffee | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/General/Build.coffee b/src/General/Build.coffee index c225fa3f0..03cfaa9b1 100644 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -264,6 +264,8 @@ Build = className: 'thread' id: "t#{data.no}" + Build.spoilerRange[board] = data.custom_spoiler + for obj in [data].concat data.last_replies or [] $.add root, if post = g.posts["#{board}.#{obj.no}"] post.nodes.root diff --git a/src/Miscellaneous/ExpandComment.coffee b/src/Miscellaneous/ExpandComment.coffee index 103c891e5..12fee2935 100644 --- a/src/Miscellaneous/ExpandComment.coffee +++ b/src/Miscellaneous/ExpandComment.coffee @@ -32,8 +32,7 @@ ExpandComment = return posts = JSON.parse(req.response).posts - if spoilerRange = posts[0].custom_spoiler - Build.spoilerRange[g.BOARD] = spoilerRange + Build.spoilerRange[g.BOARD] = posts[0].custom_spoiler for postObj in posts break if postObj.no is post.ID From a0d3694b61f8ed5733ea0da406d7ef3e05711e99 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sat, 2 Nov 2013 14:32:54 +0100 Subject: [PATCH 35/49] Fix minor bugs with keybinds/pageload + index. --- src/General/Index.coffee | 19 ++++++++++--------- src/Miscellaneous/Keybinds.coffee | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/General/Index.coffee b/src/General/Index.coffee index 13eb065ab..4b22276ae 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -50,7 +50,7 @@ Index = className: 'pagelist' hidden: true innerHTML: <%= importHTML('General/Index-pagelist') %> - Index.currentPage = +window.location.pathname.split('/')[2] + Index.currentPage = Index.getCurrentPage() $.on window, 'popstate', @cb.popstate $.on @pagelist, 'click', @cb.pageNav $.asap (-> $('.pagelist', doc) or d.readyState isnt 'loading'), -> @@ -66,8 +66,8 @@ Index = Index.sort() Index.buildIndex() popstate: (e) -> - Index.currentPage = +window.location.pathname.split('/')[2] - Index.pageLoad() + pageNum = Index.getCurrentPage() + Index.pageLoad pageNum if Index.currentPage isnt pageNum pageNav: (e) -> return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0 switch e.target.nodeName @@ -83,13 +83,14 @@ Index = scrollToIndex: -> Header.scrollTo Index.root if Index.root.getBoundingClientRect().top < 0 + getCurrentPage: -> + +window.location.pathname.split('/')[2] pageNav: (pageNum) -> return if Index.currentPage is pageNum history.pushState null, '', if pageNum is 0 then './' else pageNum + Index.pageLoad pageNum + pageLoad: (pageNum) -> Index.currentPage = pageNum - Index.pageLoad() - pageLoad: -> - return unless 'currentPage' of Index # unnecessary popstate on page load return if Conf['Index Mode'] isnt 'paged' Index.buildIndex() Index.setPage() @@ -111,7 +112,7 @@ Index = Index.setPage() Index.togglePagelist() setPage: -> - pageNum = +window.location.pathname.split('/')[2] + pageNum = Index.getCurrentPage() pagesRoot = $ '.pages', Index.pagelist # Previous/Next buttons prev = pagesRoot.previousSibling.firstChild @@ -246,9 +247,9 @@ Index = return buildIndex: -> if Conf['Index Mode'] is 'paged' - pageNum = +window.location.pathname.split('/')[2] + pageNum = Index.getCurrentPage() nodesPerPage = Index.threadsNumPerPage * 2 - nodes = Index.sortedNodes.slice nodesPerPage * pageNum, nodesPerPage * (pageNum + 1) + nodes = Index.sortedNodes[nodesPerPage * pageNum ... nodesPerPage * (pageNum + 1)] else nodes = Index.sortedNodes $.event 'IndexRefresh' diff --git a/src/Miscellaneous/Keybinds.coffee b/src/Miscellaneous/Keybinds.coffee index 7ad88b273..d59d771e7 100644 --- a/src/Miscellaneous/Keybinds.coffee +++ b/src/Miscellaneous/Keybinds.coffee @@ -82,10 +82,10 @@ Keybinds = $.open "/#{g.BOARD}/" when Conf['Next page'] return unless g.VIEW is 'index' and Conf['Index Mode'] is 'paged' - $('.next a', Index.pagelist).click() + $('.next button', Index.pagelist).click() when Conf['Previous page'] return unless g.VIEW is 'index' and Conf['Index Mode'] is 'paged' - $('.prev a', Index.pagelist).click() + $('.prev button', Index.pagelist).click() when Conf['Search form'] $.id('search-btn').click() # Thread Navigation From 61cd553a64d949dd2a22b857b301707f5d08ba42 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sat, 2 Nov 2013 15:23:32 +0100 Subject: [PATCH 36/49] Fix/Refactor ExpandThread.coffee --- src/Miscellaneous/ExpandThread.coffee | 139 ++++++++++++-------------- 1 file changed, 65 insertions(+), 74 deletions(-) diff --git a/src/Miscellaneous/ExpandThread.coffee b/src/Miscellaneous/ExpandThread.coffee index dbe736305..c1ca5229b 100644 --- a/src/Miscellaneous/ExpandThread.coffee +++ b/src/Miscellaneous/ExpandThread.coffee @@ -1,25 +1,29 @@ ExpandThread = init: -> return if g.VIEW isnt 'index' or !Conf['Thread Expansion'] - - $.on d, 'IndexRefresh', @onrefresh - + @statuses = {} + $.on d, 'IndexRefresh', @onIndexRefresh Thread.callbacks.push name: 'Thread Expansion' cb: @node node: -> - return unless span = $.x 'following-sibling::span[contains(@class,"summary")][1]', @OP.nodes.root - [posts, files] = span.textContent.match /\d+/g + ExpandThread.setButton @ + + setButton: (thread) -> + return unless span = $.x 'following-sibling::span[contains(@class,"summary")][1]', thread.OP.nodes.root a = $.el 'a', - textContent: ExpandThread.text '+', posts, files + textContent: ExpandThread.text '+', span.textContent.match(/\d+/g)... className: 'summary' href: 'javascript:;' $.on a, 'click', ExpandThread.cbToggle $.replace span, a - onrefresh: -> + onIndexRefresh: -> + for threadID, status of ExpandThread.statuses + status.req?.abort() + delete ExpandThread.statuses[threadID] for threadID, thread of g.BOARD.threads - ExpandThread.node.call thread + ExpandThread.setButton thread return text: (status, posts, files) -> @@ -35,89 +39,76 @@ ExpandThread = toggle: (thread) -> threadRoot = thread.OP.nodes.root.parentNode a = $ '.summary', threadRoot - - switch thread.isExpanded - when false, undefined - for post in $$ '.thread > .postContainer', threadRoot - ExpandComment.expand Get.postFromRoot post - unless a - thread.isExpanded = true - return - thread.isExpanded = 'loading' - [posts, files] = a.textContent.match /\d+/g - a.textContent = ExpandThread.text '...', posts, files - $.cache "//api.4chan.org/#{thread.board}/res/#{thread}.json", -> - ExpandThread.parse @, thread, a - - when 'loading' - thread.isExpanded = false - return unless a - [posts, files] = a.textContent.match /\d+/g - a.textContent = ExpandThread.text '+', posts, files - - when true - thread.isExpanded = false - #goddamit moot - num = if thread.isSticky - 1 - else switch g.BOARD.ID - # XXX boards config - when 'b', 'vg' then 3 - when 't' then 1 - else 5 - posts = $$ ".thread > .replyContainer", threadRoot - for post in [thread.OP.nodes.root].concat posts[-num..] - ExpandComment.contract Get.postFromRoot post - return unless a - postsCount = 0 - filesCount = 0 - for reply in posts[...-num] - if Conf['Quote Inlining'] - # rm clones - inlined.click() while inlined = $ '.inlined', reply - postsCount++ - filesCount++ if 'file' of Get.postFromRoot reply - $.rm reply - a.textContent = ExpandThread.text '+', postsCount, filesCount - return - - parse: (req, thread, a) -> - return if a.textContent[0] is '+' - if req.status not in [200, 304] - a.textContent = "Error #{req.statusText} (#{req.status})" - $.off a, 'click', ExpandThread.cbToggle + if thread.ID of ExpandThread.statuses + ExpandThread.contract thread, a, threadRoot + else + ExpandThread.expand thread, a, threadRoot + expand: (thread, a, threadRoot) -> + ExpandThread.statuses[thread] = status = {} + for post in $$ '.thread > .postContainer', threadRoot + ExpandComment.expand Get.postFromRoot post + return unless a + a.textContent = ExpandThread.text '...', a.textContent.match(/\d+/g)... + status.req = $.cache "//api.4chan.org/#{thread.board}/res/#{thread}.json", -> + delete status.req + ExpandThread.parse @, thread, a + contract: (thread, a, threadRoot) -> + status = ExpandThread.statuses[thread] + delete ExpandThread.statuses[thread] + if status.req + status.req.abort() + a.textContent = ExpandThread.text '+', a.textContent.match(/\d+/g)... if a return - thread.isExpanded = true + num = if thread.isSticky + 1 + else switch g.BOARD.ID + # XXX boards config + when 'b', 'vg' then 3 + when 't' then 1 + else 5 + posts = $$ '.thread > .replyContainer', threadRoot + for post in [thread.OP.nodes.root].concat posts[-num..] + ExpandComment.contract Get.postFromRoot post + return unless a + postsCount = 0 + filesCount = 0 + for reply in posts[...-num] + # rm clones + inlined.click() while inlined = $ '.inlined', reply if Conf['Quote Inlining'] + postsCount++ + filesCount++ if 'file' of Get.postFromRoot reply + $.rm reply + a.textContent = ExpandThread.text '+', postsCount, filesCount + parse: (req, thread, a) -> + if req.status not in [200, 304] + a.textContent = "Error #{req.statusText} (#{req.status})" + return - {posts} = JSON.parse req.response - if spoilerRange = posts.shift().custom_spoiler - Build.spoilerRange[thread.board] = spoilerRange + data = JSON.parse(req.response).posts + Build.spoilerRange[thread.board] = data.shift().custom_spoiler - postsObj = [] + posts = [] postsRoot = [] filesCount = 0 - for reply in posts - if post = thread.posts[reply.no] + for postData in data + if post = thread.posts[postData.no] filesCount++ if 'file' of post postsRoot.push post.nodes.root continue - root = Build.postFromObject reply, thread.board.ID + root = Build.postFromObject postData, thread.board.ID post = new Post root, thread, thread.board - link = $ 'a[title="Highlight this post"]', root - link.href = "res/#{thread}#p#{post}" - link.nextSibling.href = "res/#{thread}#q#{post}" filesCount++ if 'file' of post - postsObj.push post + posts.push post postsRoot.push root - Main.callbackNodes Post, postsObj + Main.callbackNodes Post, posts $.after a, postsRoot - postsCount = postsRoot.length + postsCount = postsRoot.length a.textContent = ExpandThread.text '-', postsCount, filesCount # Enable 4chan features. if Conf['Enable 4chan\'s Extension'] - $.globalEval "Parser.parseThread(#{thread.ID}, 1, #{postsCount})" + $.globalEval "Parser.parseThread(#{thread}, 1, #{postsCount})" else Fourchan.parseThread thread.ID, 1, postsCount From 6460d5e0c18e54a7ed4fcea11c0a4f0cb35bbd84 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sat, 2 Nov 2013 15:55:50 +0100 Subject: [PATCH 37/49] Fix early notices. --- src/General/Header.coffee | 1 + src/General/Notice.coffee | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/General/Header.coffee b/src/General/Header.coffee index 2b8465933..f76da0383 100644 --- a/src/General/Header.coffee +++ b/src/General/Header.coffee @@ -6,6 +6,7 @@ Header = @bar = $ '#header-bar', headerEl @toggle = $ '#toggle-header-bar', @bar + @noticesRoot = $ '#notifications', headerEl @menu = new UI.Menu 'header' menuButton = $.el 'a', diff --git a/src/General/Notice.coffee b/src/General/Notice.coffee index 60b46ff77..90c2e504b 100644 --- a/src/General/Notice.coffee +++ b/src/General/Notice.coffee @@ -19,7 +19,7 @@ class Notice $.on d, 'visibilitychange', @add return $.off d, 'visibilitychange', @add - $.add $.id('notifications'), @el + $.add Header.noticesRoot, @el @el.clientHeight # force reflow @el.style.opacity = 1 setTimeout @close, @timeout * $.SECOND if @timeout From a22356893727c9872bf4908740e481bc46ef7b10 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sat, 2 Nov 2013 16:46:56 +0100 Subject: [PATCH 38/49] Fix pretty-printing on Chrome. --- src/Miscellaneous/Fourchan.coffee | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Miscellaneous/Fourchan.coffee b/src/Miscellaneous/Fourchan.coffee index 64b1f4562..1b78c17de 100644 --- a/src/Miscellaneous/Fourchan.coffee +++ b/src/Miscellaneous/Fourchan.coffee @@ -6,8 +6,9 @@ Fourchan = if board is 'g' $.globalEval """ window.addEventListener('prettyprint', function(e) { - var pre = e.detail; - pre.innerHTML = prettyPrintOne(pre.innerHTML); + window.dispatchEvent(new CustomEvent('prettyprint:cb', { + detail: prettyPrintOne(e.detail) + })); }, false); """ Post.callbacks.push @@ -32,9 +33,11 @@ Fourchan = cb: @math code: -> return if @isClone - for pre in $$ '.prettyprint:not(.prettyprinted)', @nodes.comment - $.event 'prettyprint', pre, window - $.addClass pre, 'prettyprinted' + apply = (e) -> pre.innerHTML = e.detail + $.on window, 'prettyprint:cb', apply + for pre in $$ '.prettyprint', @nodes.comment + $.event 'prettyprint', pre.innerHTML, window + $.off window, 'prettyprint:cb', apply return math: -> return if @isClone or !$ '.math', @nodes.comment From d00597f53d6d9740c79433dfd33623373480969d Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sat, 2 Nov 2013 17:33:39 +0100 Subject: [PATCH 39/49] Remove comment expansion-related code. --- src/General/Config.coffee | 1 - src/General/Main.coffee | 1 - src/Miscellaneous/ExpandComment.coffee | 70 -------------------------- src/Miscellaneous/ExpandThread.coffee | 11 +--- 4 files changed, 2 insertions(+), 81 deletions(-) delete mode 100644 src/Miscellaneous/ExpandComment.coffee diff --git a/src/General/Config.coffee b/src/General/Config.coffee index 95f1f2a48..715822858 100644 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -9,7 +9,6 @@ Config = 'Time Formatting': [true, 'Localize and format timestamps.'] 'Relative Post Dates': [false, 'Display dates like "3 minutes ago". Tooltip shows the timestamp.'] 'File Info Formatting': [true, 'Reformat the file information.'] - 'Comment Expansion': [true, 'Add buttons to expand too long comments.'] 'Thread Expansion': [true, 'Add buttons to expand threads.'] 'Index Navigation': [false, 'Add buttons to navigate between threads.'] 'Reply Navigation': [false, 'Add buttons to navigate to top / bottom of thread.'] diff --git a/src/General/Main.coffee b/src/General/Main.coffee index 0528e4f08..9756b15e2 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -106,7 +106,6 @@ Main = initFeature 'Reveal Spoilers', RevealSpoilers initFeature 'Auto-GIF', AutoGIF initFeature 'Image Hover', ImageHover - initFeature 'Comment Expansion', ExpandComment initFeature 'Thread Expansion', ExpandThread initFeature 'Thread Excerpt', ThreadExcerpt initFeature 'Favicon', Favicon diff --git a/src/Miscellaneous/ExpandComment.coffee b/src/Miscellaneous/ExpandComment.coffee deleted file mode 100644 index 12fee2935..000000000 --- a/src/Miscellaneous/ExpandComment.coffee +++ /dev/null @@ -1,70 +0,0 @@ -ExpandComment = - init: -> - return if g.VIEW isnt 'index' or !Conf['Comment Expansion'] - - Post.callbacks.push - name: 'Comment Expansion' - cb: @node - node: -> - if a = $ '.abbr > a:not([onclick])', @nodes.comment - $.on a, 'click', ExpandComment.cb - cb: (e) -> - e.preventDefault() - ExpandComment.expand Get.postFromNode @ - expand: (post) -> - if post.nodes.longComment and !post.nodes.longComment.parentNode - $.replace post.nodes.shortComment, post.nodes.longComment - post.nodes.comment = post.nodes.longComment - return - return unless a = $ '.abbr > a', post.nodes.comment - a.textContent = "Post No.#{post} Loading..." - $.cache "//api.4chan.org#{a.pathname}.json", -> ExpandComment.parse @, a, post - contract: (post) -> - return unless post.nodes.shortComment - a = $ '.abbr > a', post.nodes.shortComment - a.textContent = 'here' - $.replace post.nodes.longComment, post.nodes.shortComment - post.nodes.comment = post.nodes.shortComment - parse: (req, a, post) -> - {status} = req - if status not in [200, 304] - a.textContent = "Error #{req.statusText} (#{status})" - return - - posts = JSON.parse(req.response).posts - Build.spoilerRange[g.BOARD] = posts[0].custom_spoiler - - for postObj in posts - break if postObj.no is post.ID - if postObj.no isnt post.ID - a.textContent = "Post No.#{post} not found." - return - - {comment} = post.nodes - clone = comment.cloneNode false - clone.innerHTML = postObj.com - for quote in $$ '.quotelink', clone - href = quote.getAttribute 'href' - continue if href[0] is '/' # Cross-board quote, or board link - quote.href = "/#{post.board}/res/#{href}" # Fix pathnames - post.nodes.shortComment = comment - $.replace comment, clone - post.nodes.comment = post.nodes.longComment = clone - post.parseComment() - post.parseQuotes() - if Conf['Resurrect Quotes'] - Quotify.node.call post - if Conf['Quote Previewing'] - QuotePreview.node.call post - if Conf['Quote Inlining'] - QuoteInline.node.call post - if Conf['Mark OP Quotes'] - QuoteOP.node.call post - if Conf['Mark Cross-thread Quotes'] - QuoteCT.node.call post - if g.BOARD.ID is 'g' - Fourchan.code.call post - if g.BOARD.ID is 'sci' - Fourchan.math.call post - if Conf['Linkify'] - Linkify.node.call post diff --git a/src/Miscellaneous/ExpandThread.coffee b/src/Miscellaneous/ExpandThread.coffee index c1ca5229b..fb3a24699 100644 --- a/src/Miscellaneous/ExpandThread.coffee +++ b/src/Miscellaneous/ExpandThread.coffee @@ -38,16 +38,13 @@ ExpandThread = toggle: (thread) -> threadRoot = thread.OP.nodes.root.parentNode - a = $ '.summary', threadRoot + return unless a = $ '.summary', threadRoot if thread.ID of ExpandThread.statuses ExpandThread.contract thread, a, threadRoot else ExpandThread.expand thread, a, threadRoot expand: (thread, a, threadRoot) -> ExpandThread.statuses[thread] = status = {} - for post in $$ '.thread > .postContainer', threadRoot - ExpandComment.expand Get.postFromRoot post - return unless a a.textContent = ExpandThread.text '...', a.textContent.match(/\d+/g)... status.req = $.cache "//api.4chan.org/#{thread.board}/res/#{thread}.json", -> delete status.req @@ -67,13 +64,9 @@ ExpandThread = when 'b', 'vg' then 3 when 't' then 1 else 5 - posts = $$ '.thread > .replyContainer', threadRoot - for post in [thread.OP.nodes.root].concat posts[-num..] - ExpandComment.contract Get.postFromRoot post - return unless a postsCount = 0 filesCount = 0 - for reply in posts[...-num] + for reply in $$('.thread > .replyContainer', threadRoot)[...-num] # rm clones inlined.click() while inlined = $ '.inlined', reply if Conf['Quote Inlining'] postsCount++ From 0eb224e07726c15d86c046540adfe85bd233f5f2 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sat, 2 Nov 2013 18:37:40 +0100 Subject: [PATCH 40/49] Simpler summary creation, tiny Build.thread() optimization. --- css/style.css | 3 +++ src/General/Build.coffee | 29 +++++++++++++++------------ src/General/Index.coffee | 1 - src/Miscellaneous/ExpandThread.coffee | 14 ++++++------- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/css/style.css b/css/style.css index 751f3b772..4bebe551d 100644 --- a/css/style.css +++ b/css/style.css @@ -367,6 +367,9 @@ a[href="javascript:;"] { :root.index-loading .pagelist { display: none; } +.summary { + text-decoration: none; +} /* Announcement Hiding */ :root.hide-announcement #globalMessage, diff --git a/src/General/Build.coffee b/src/General/Build.coffee index 03cfaa9b1..5c75dff8e 100644 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -259,28 +259,31 @@ Build = container + summary: (boardID, threadID, posts, files) -> + text = [] + text.push "#{posts} post#{if posts > 1 then 's' else ''}" + text.push "and #{files} image repl#{if files > 1 then 'ies' else 'y'}" if files + text.push 'omitted.' + $.el 'a', + className: 'summary' + textContent: text.join ' ' + href: "/#{boardID}/res/#{threadID}" thread: (board, data) -> - root = $.el 'div', - className: 'thread' - id: "t#{data.no}" - Build.spoilerRange[board] = data.custom_spoiler + nodes = [] for obj in [data].concat data.last_replies or [] - $.add root, if post = g.posts["#{board}.#{obj.no}"] + nodes.push if post = board.posts[obj.no] post.nodes.root else Build.postFromObject obj, board.ID # build if necessary if data.omitted_posts - {omitted_posts, omitted_images} = data - html = [] - html.push "#{omitted_posts} post#{if omitted_posts > 1 then 's' else ''}" - html.push "and #{omitted_images} image repl#{if omitted_images > 1 then 'ies' else 'y'}" if omitted_images - html.push "omitted. Click here to view." - $.after root.firstChild, $.el 'span', - className: 'summary' - innerHTML: html.join ' ' + nodes.splice 1, 0, Build.summary board.ID, data.no, data.omitted_posts, data.omitted_images + root = $.el 'div', + className: 'thread' + id: "t#{data.no}" + $.add root, nodes root diff --git a/src/General/Index.coffee b/src/General/Index.coffee index 4b22276ae..5b331337a 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -158,7 +158,6 @@ Index = try Index.parse JSON.parse req.response if req.status is 200 catch err - c.error err.stack # network error or non-JSON content for example. notice.setType 'error' notice.el.lastElementChild.textContent = 'Index refresh failed.' diff --git a/src/Miscellaneous/ExpandThread.coffee b/src/Miscellaneous/ExpandThread.coffee index fb3a24699..d9efaa2b0 100644 --- a/src/Miscellaneous/ExpandThread.coffee +++ b/src/Miscellaneous/ExpandThread.coffee @@ -10,13 +10,9 @@ ExpandThread = ExpandThread.setButton @ setButton: (thread) -> - return unless span = $.x 'following-sibling::span[contains(@class,"summary")][1]', thread.OP.nodes.root - a = $.el 'a', - textContent: ExpandThread.text '+', span.textContent.match(/\d+/g)... - className: 'summary' - href: 'javascript:;' + return unless a = $.x 'following-sibling::a[contains(@class,"summary")][1]', thread.OP.nodes.root + a.textContent = ExpandThread.text '+', a.textContent.match(/\d+/g)... $.on a, 'click', ExpandThread.cbToggle - $.replace span, a onIndexRefresh: -> for threadID, status of ExpandThread.statuses @@ -33,7 +29,9 @@ ExpandThread = text.push if status is '-' then 'shown' else 'omitted' text.join(' ') + '.' - cbToggle: -> + cbToggle: (e) -> + return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0 + e.preventDefault() ExpandThread.toggle Get.threadFromNode @ toggle: (thread) -> @@ -92,7 +90,7 @@ ExpandThread = root = Build.postFromObject postData, thread.board.ID post = new Post root, thread, thread.board filesCount++ if 'file' of post - posts.push post + posts.push post postsRoot.push root Main.callbackNodes Post, posts $.after a, postsRoot From 54eaa9adea317c0547966646008f9175fefa9693 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sat, 2 Nov 2013 20:29:06 +0100 Subject: [PATCH 41/49] Disable enhanced index on /f/. --- src/General/Index.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/General/Index.coffee b/src/General/Index.coffee index 5b331337a..336e2c3f9 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -1,6 +1,6 @@ Index = init: -> - return if g.VIEW isnt 'index' + return if g.VIEW isnt 'index' or g.BOARD.ID is 'f' @button = $.el 'a', className: 'index-refresh-shortcut fa fa-refresh' From 81a2b096a1b54954916f11442b146290b9bb36e5 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sun, 3 Nov 2013 01:27:23 +0100 Subject: [PATCH 42/49] Introduce Header.{scrollToIfNeeded,getTopOf,getBottomOf}() and use them. 4chan X - now with proper bottom header support. --- src/General/Header.coffee | 22 +++++++++++++--- src/General/Index.coffee | 4 +-- src/Images/ImageExpand.coffee | 20 ++++++-------- src/Miscellaneous/Keybinds.coffee | 36 +++++++++---------------- src/Miscellaneous/Nav.coffee | 41 +++++++++++++---------------- src/Monitoring/ThreadUpdater.coffee | 3 +-- src/Monitoring/Unread.coffee | 15 +++++------ 7 files changed, 66 insertions(+), 75 deletions(-) diff --git a/src/General/Header.coffee b/src/General/Header.coffee index f76da0383..10359d471 100644 --- a/src/General/Header.coffee +++ b/src/General/Header.coffee @@ -247,12 +247,28 @@ Header = return unless /^p\d+$/.test(hash) and post = $.id hash return if (Get.postFromRoot post).isHidden Header.scrollTo post - scrollTo: (root) -> + scrollTo: (root, down, needed) -> + if down + x = Header.getBottomOf root + window.scrollBy 0, -x unless needed and x >= 0 + else + x = Header.getTopOf root + window.scrollBy 0, x unless needed and x >= 0 + scrollToIfNeeded: (root, down) -> + Header.scrollTo root, down, true + getTopOf: (root) -> {top} = root.getBoundingClientRect() unless Conf['Bottom header'] headRect = Header.toggle.getBoundingClientRect() - top -= headRect.top + headRect.height - window.scrollBy 0, top + top -= headRect.top + headRect.height + top + getBottomOf: (root) -> + {clientHeight} = doc + bottom = clientHeight - root.getBoundingClientRect().bottom + if Conf['Bottom header'] + headRect = Header.toggle.getBoundingClientRect() + bottom -= clientHeight - headRect.bottom + headRect.height + bottom addShortcut: (el, index) -> shortcut = $.el 'span', diff --git a/src/General/Index.coffee b/src/General/Index.coffee index 336e2c3f9..c9a8b5134 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -50,7 +50,7 @@ Index = className: 'pagelist' hidden: true innerHTML: <%= importHTML('General/Index-pagelist') %> - Index.currentPage = Index.getCurrentPage() + @currentPage = @getCurrentPage() $.on window, 'popstate', @cb.popstate $.on @pagelist, 'click', @cb.pageNav $.asap (-> $('.pagelist', doc) or d.readyState isnt 'loading'), -> @@ -81,7 +81,7 @@ Index = Index.pageNav +a.pathname.split('/')[2] scrollToIndex: -> - Header.scrollTo Index.root if Index.root.getBoundingClientRect().top < 0 + Header.scrollToIfNeeded Index.root getCurrentPage: -> +window.location.pathname.split('/')[2] diff --git a/src/Images/ImageExpand.coffee b/src/Images/ImageExpand.coffee index c21bef490..60382bb73 100644 --- a/src/Images/ImageExpand.coffee +++ b/src/Images/ImageExpand.coffee @@ -45,7 +45,7 @@ ImageExpand = continue unless file and file.isImage and doc.contains post.nodes.root if ImageExpand.on and (!Conf['Expand spoilers'] and file.isSpoiler or - Conf['Expand from here'] and file.thumb.getBoundingClientRect().top < 0) + Conf['Expand from here'] and Header.getTopOf(file.thumb) < 0) continue $.queueTask func, post return @@ -60,13 +60,10 @@ ImageExpand = # Scroll back to the thumbnail when contracting the image # to avoid being left miles away from the relevant post. - rect = post.nodes.root.getBoundingClientRect() - if rect.top < 0 - y = rect.top - unless Conf['Bottom header'] - headRect = Header.toggle.getBoundingClientRect() - y -= headRect.top + headRect.height - if rect.left < 0 + top = Header.getTopOf post.nodes.root + if top < 0 + y = top + if post.nodes.root.getBoundingClientRect().left < 0 x = -window.scrollX window.scrollBy x, y if x or y ImageExpand.contract post @@ -104,13 +101,12 @@ ImageExpand = $.addClass post.nodes.root, 'expanded-image' $.rmClass post.file.thumb, 'expanding' return - prev = post.nodes.root.getBoundingClientRect() + {bottom} = post.nodes.root.getBoundingClientRect() $.queueTask -> $.addClass post.nodes.root, 'expanded-image' $.rmClass post.file.thumb, 'expanding' - return unless prev.top + prev.height <= 0 - curr = post.nodes.root.getBoundingClientRect() - window.scrollBy 0, curr.height - prev.height + curr.top - prev.top + return unless bottom <= 0 + window.scrollBy 0, post.nodes.root.getBoundingClientRect().bottom - bottom error: -> post = Get.postFromNode @ diff --git a/src/Miscellaneous/Keybinds.coffee b/src/Miscellaneous/Keybinds.coffee index d59d771e7..00ee147cd 100644 --- a/src/Miscellaneous/Keybinds.coffee +++ b/src/Miscellaneous/Keybinds.coffee @@ -183,43 +183,31 @@ Keybinds = location.href = url hl: (delta, thread) -> + postEl = $ '.reply.highlight', thread + unless delta - if postEl = $ '.reply.highlight', thread - $.rmClass postEl, 'highlight' + $.rmClass postEl, 'highlight' if postEl return - if Conf['Bottom header'] - topMargin = 0 - else - headRect = Header.toggle.getBoundingClientRect() - topMargin = headRect.top + headRect.height - if postEl = $ '.reply.highlight', thread - $.rmClass postEl, 'highlight' - rect = postEl.getBoundingClientRect() - if rect.bottom >= topMargin and rect.top <= doc.clientHeight # We're at least partially visible + + if postEl + {height} = postEl.getBoundingClientRect() + if Header.getTopOf(postEl) >= -height and Header.getBottomOf(postEl) >= -height # We're at least partially visible root = postEl.parentNode axe = if delta is +1 'following' else 'preceding' - next = $.x "#{axe}-sibling::div[contains(@class,'replyContainer')][1]/child::div[contains(@class,'reply')]", root - unless next - @focus postEl - return - return unless g.VIEW is 'thread' or $.x('ancestor::div[parent::div[@class="board"]]', next) is thread - rect = next.getBoundingClientRect() - if rect.top < 0 or rect.bottom > doc.clientHeight - if delta is -1 - window.scrollBy 0, rect.top - topMargin - else - next.scrollIntoView false + return unless next = $.x "#{axe}-sibling::div[contains(@class,'replyContainer')][1]/child::div[contains(@class,'reply')]", root + Header.scrollToIfNeeded next, delta is +1 @focus next + $.rmClass postEl, 'highlight' return + $.rmClass postEl, 'highlight' replies = $$ '.reply', thread replies.reverse() if delta is -1 for reply in replies - rect = reply.getBoundingClientRect() - if delta is +1 and rect.top >= topMargin or delta is -1 and rect.bottom <= doc.clientHeight + if delta is +1 and Header.getTopOf(reply) > 0 or delta is -1 and Header.getBottomOf(reply) > 0 @focus reply return diff --git a/src/Miscellaneous/Nav.coffee b/src/Miscellaneous/Nav.coffee index 2b7e23354..3620080f0 100644 --- a/src/Miscellaneous/Nav.coffee +++ b/src/Miscellaneous/Nav.coffee @@ -38,29 +38,24 @@ Nav = else Nav.scroll +1 - getThread: (full) -> - if Conf['Bottom header'] - topMargin = 0 - else - headRect = Header.toggle.getBoundingClientRect() - topMargin = headRect.top + headRect.height - threads = $$('.thread').filter (thread) -> - thread = Get.threadFromRoot thread - !(thread.isHidden and !thread.stub) - for thread, i in threads - rect = thread.getBoundingClientRect() - if rect.bottom > topMargin # not scrolled past - return if full then [threads, thread, i, rect, topMargin] else thread + getThread: -> + for threadRoot in $$ '.thread' + thread = Get.threadFromRoot threadRoot + continue if thread.isHidden and !thread.stub + if Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height # not scrolled past + return threadRoot return $ '.board' scroll: (delta) -> - [threads, thread, i, rect, topMargin] = Nav.getThread true - top = rect.top - topMargin - - # unless we're not at the beginning of the current thread - # (and thus wanting to move to beginning) - # or we're above the first thread and don't want to skip it - if (delta is -1 and top > -5) or (delta is +1 and top < 5) - top = threads[i + delta]?.getBoundingClientRect().top - topMargin - - window.scrollBy 0, top + thread = Nav.getThread() + axe = if delta is +1 + 'following' + else + 'preceding' + if next = $.x "#{axe}-sibling::div[contains(@class,'thread')][1]", thread + # Unless we're not at the beginning of the current thread, + # and thus wanting to move to beginning, + # or we're above the first thread and don't want to skip it. + top = Header.getTopOf thread + thread = next if delta is +1 and top < 5 or delta is -1 and top > -5 + Header.scrollTo thread diff --git a/src/Monitoring/ThreadUpdater.coffee b/src/Monitoring/ThreadUpdater.coffee index b35f9502e..0ef1d04bf 100644 --- a/src/Monitoring/ThreadUpdater.coffee +++ b/src/Monitoring/ThreadUpdater.coffee @@ -246,8 +246,7 @@ ThreadUpdater = ThreadUpdater.lastPost = posts[count - 1].ID Main.callbackNodes Post, posts - scroll = Conf['Auto Scroll'] and ThreadUpdater.scrollBG() and - ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25 + scroll = Conf['Auto Scroll'] and ThreadUpdater.scrollBG() and Header.getBottomOf(ThreadUpdater.root) > -25 $.add ThreadUpdater.root, nodes sendEvent() if scroll diff --git a/src/Monitoring/Unread.coffee b/src/Monitoring/Unread.coffee index 2898c6c69..9ca6ce402 100644 --- a/src/Monitoring/Unread.coffee +++ b/src/Monitoring/Unread.coffee @@ -42,18 +42,16 @@ Unread = while root = $.x 'preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root break unless (post = Get.postFromRoot root).isHidden return unless root - onload = -> root.scrollIntoView false if checkPosition root + down = true else # Scroll to the last read post. posts = Object.keys Unread.thread.posts {root} = Unread.thread.posts[posts[posts.length - 1]].nodes - onload = -> Header.scrollTo root if checkPosition root - checkPosition = (target) -> - # Scroll to the target unless we scrolled past it. - target.getBoundingClientRect().bottom > doc.clientHeight # Prevent the browser to scroll back to # the previous scroll location on page load. - $.on window, 'load', onload + $.on window, 'load', -> + # Scroll to the target unless we scrolled past it. + Header.scrollTo root, down if Header.getBottomOf(root) < 0 sync: -> lastReadPost = Unread.db.get @@ -102,7 +100,7 @@ Unread = body: post.info.comment icon: Favicon.logo notif.onclick = -> - Header.scrollTo post.nodes.root + Header.scrollToIfNeeded post.nodes.root, true window.focus() notif.onshow = -> setTimeout -> @@ -132,9 +130,8 @@ Unread = read: (e) -> return if d.hidden or !Unread.posts.length - height = doc.clientHeight for post, i in Unread.posts - break if post.nodes.root.getBoundingClientRect().bottom > height # post is not completely read + break if Header.getBottomOf(post.nodes.root) <= 0 # post is not completely read return unless i Unread.lastReadPost = Unread.posts.splice(0, i)[i - 1].ID From a1a12aaf232be8428ed3987c174a31cbf0a5b02b Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sun, 3 Nov 2013 13:57:55 +0100 Subject: [PATCH 43/49] Make sure posts are read when scrolling via next reply keybind. --- CHANGELOG.md | 2 +- src/Monitoring/Unread.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bffeab527..125b5b3bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ Index navigation improvements: - - You can now refresh the index page you are on with the icon in the header bar or the same keybind for refreshing threads. + - You can now refresh the index page you are on with the refresh shortcut in the header bar or the same keybind for refreshing threads. - You can now switch between paged and all-threads index modes via the "Index Navigation" header sub-menu. - Threads in the index can now be sorted by: - Bump order diff --git a/src/Monitoring/Unread.coffee b/src/Monitoring/Unread.coffee index 9ca6ce402..8e13c463e 100644 --- a/src/Monitoring/Unread.coffee +++ b/src/Monitoring/Unread.coffee @@ -131,7 +131,7 @@ Unread = read: (e) -> return if d.hidden or !Unread.posts.length for post, i in Unread.posts - break if Header.getBottomOf(post.nodes.root) <= 0 # post is not completely read + break if Header.getBottomOf(post.nodes.root) < -1 # post is not completely read return unless i Unread.lastReadPost = Unread.posts.splice(0, i)[i - 1].ID From b8dac7145676908ec1755e6afeadfca4490a86e3 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sun, 3 Nov 2013 13:58:52 +0100 Subject: [PATCH 44/49] Bumping should checkout the main branch before anything else. --- Gruntfile.coffee | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Gruntfile.coffee b/Gruntfile.coffee index e8dcf07dd..9f48729d7 100644 --- a/Gruntfile.coffee +++ b/Gruntfile.coffee @@ -85,9 +85,10 @@ module.exports = (grunt) -> stdout: true stderr: true failOnError: true + checkout: + command: 'git checkout <%= pkg.meta.mainBranch %>' commit: command: """ - git checkout <%= pkg.meta.mainBranch %> git commit -am "Release <%= pkg.meta.name %> v<%= pkg.version %>." git tag -a <%= pkg.version %> -m "<%= pkg.meta.name %> v<%= pkg.version %>." git tag -af stable-v3 -m "<%= pkg.meta.name %> v<%= pkg.version %>." @@ -151,9 +152,9 @@ module.exports = (grunt) -> ] grunt.registerTask 'release', ['shell:commit', 'shell:push', 'build-crx', 'compress:crx'] - grunt.registerTask 'patch', ['bump', 'updcl:3', 'release'] - grunt.registerTask 'minor', ['bump:minor', 'updcl:2', 'release'] - grunt.registerTask 'major', ['bump:major', 'updcl:1', 'release'] + grunt.registerTask 'patch', ['shell:checkout', 'bump', 'updcl:3', 'release'] + grunt.registerTask 'minor', ['shell:checkout', 'bump:minor', 'updcl:2', 'release'] + grunt.registerTask 'major', ['shell:checkout', 'bump:major', 'updcl:1', 'release'] grunt.registerTask 'updcl', 'Update the changelog', (headerLevel) -> headerPrefix = new Array(+headerLevel + 1).join '#' From bddb032dac47663cfd7a759aac8b8ca7d810f028 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sun, 3 Nov 2013 16:47:22 +0100 Subject: [PATCH 45/49] We only have to look for one thread when visiting thread pages. --- src/General/Index.coffee | 3 +-- src/General/Main.coffee | 31 +++++++++++++------------------ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/General/Index.coffee b/src/General/Index.coffee index c9a8b5134..5946e0e5f 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -202,8 +202,7 @@ Index = posts.push new Post postRoot, thread, g.BOARD catch err # Skip posts that we failed to parse. - unless errors - errors = [] + errors = [] unless errors errors.push message: "Parsing of Post No.#{postRoot.id.match /\d+/} failed. Post will be skipped." error: err diff --git a/src/General/Main.coffee b/src/General/Main.coffee index 9756b15e2..e0b9abc8b 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -169,26 +169,21 @@ Main = # Something might have gone wrong! Main.initStyle() - if g.VIEW is 'thread' and board = $ '.board' - threads = [] - posts = [] - - for threadRoot in $$ '.board > .thread', board - thread = new Thread +threadRoot.id[1..], g.BOARD - threads.push thread - for postRoot in $$ '.thread > .postContainer', threadRoot - try - posts.push new Post postRoot, thread, g.BOARD - catch err - # Skip posts that we failed to parse. - unless errors - errors = [] - errors.push - message: "Parsing of Post No.#{postRoot.id.match /\d+/} failed. Post will be skipped." - error: err + if g.VIEW is 'thread' and threadRoot = $ '.thread' + thread = new Thread +threadRoot.id[1..], g.BOARD + posts = [] + for postRoot in $$ '.thread > .postContainer', threadRoot + try + posts.push new Post postRoot, thread, g.BOARD + catch err + # Skip posts that we failed to parse. + errors = [] unless errors + errors.push + message: "Parsing of Post No.#{postRoot.id.match /\d+/} failed. Post will be skipped." + error: err Main.handleErrors errors if errors - Main.callbackNodes Thread, threads + Main.callbackNodes Thread, [thread] Main.callbackNodes Post, posts if $.hasClass d.body, 'fourchan_x' From aeea95564cdda3cdbd2555ab7e621b6bca1e6a3c Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sun, 3 Nov 2013 16:58:50 +0100 Subject: [PATCH 46/49] Don't need to reprettyprint inside of threads. --- src/Miscellaneous/Fourchan.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miscellaneous/Fourchan.coffee b/src/Miscellaneous/Fourchan.coffee index 1b78c17de..6f290d662 100644 --- a/src/Miscellaneous/Fourchan.coffee +++ b/src/Miscellaneous/Fourchan.coffee @@ -35,7 +35,7 @@ Fourchan = return if @isClone apply = (e) -> pre.innerHTML = e.detail $.on window, 'prettyprint:cb', apply - for pre in $$ '.prettyprint', @nodes.comment + for pre in $$ '.prettyprint:not(.prettyprinted)', @nodes.comment $.event 'prettyprint', pre.innerHTML, window $.off window, 'prettyprint:cb', apply return From bc0f5fcc3b59558bb382aea0381eb49e78355c1a Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sun, 3 Nov 2013 17:14:01 +0100 Subject: [PATCH 47/49] Avoid creating new thread root+
    when they already exist. --- src/Filtering/ThreadHiding.coffee | 6 ++---- src/General/Build.coffee | 15 +++++++++++---- src/General/Index.coffee | 6 +++--- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Filtering/ThreadHiding.coffee b/src/Filtering/ThreadHiding.coffee index 34f9a54f2..55fd4dd77 100644 --- a/src/Filtering/ThreadHiding.coffee +++ b/src/Filtering/ThreadHiding.coffee @@ -16,10 +16,8 @@ ThreadHiding = $.prepend @OP.nodes.root, ThreadHiding.makeButton @, 'hide' onrefresh: -> - for threadID, thread of g.BOARD.threads when thread.isHidden - hasStub = !!thread.stub - ThreadHiding.show thread - ThreadHiding.hide thread, hasStub + for threadID, thread of g.BOARD.threads when thread.isHidden and thread.stub + $.prepend thread.OP.nodes.root.parentNode, thread.stub return syncCatalog: -> diff --git a/src/General/Build.coffee b/src/General/Build.coffee index 5c75dff8e..74b40338b 100644 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -271,6 +271,16 @@ Build = thread: (board, data) -> Build.spoilerRange[board] = data.custom_spoiler + if (OP = board.posts[data.no]) and parent = OP.nodes.root.parentNode + root = parent + hr = parent.nextElementSibling + $.rmAll root + else + root = $.el 'div', + className: 'thread' + id: "t#{data.no}" + hr = $.el 'hr' + nodes = [] for obj in [data].concat data.last_replies or [] nodes.push if post = board.posts[obj.no] @@ -282,8 +292,5 @@ Build = if data.omitted_posts nodes.splice 1, 0, Build.summary board.ID, data.no, data.omitted_posts, data.omitted_images - root = $.el 'div', - className: 'thread' - id: "t#{data.no}" $.add root, nodes - root + [root, hr] diff --git a/src/General/Index.coffee b/src/General/Index.coffee index 5946e0e5f..5f66c0f40 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -188,8 +188,8 @@ Index = threads = [] posts = [] for threadData in Index.liveThreadData - threadRoot = Build.thread g.BOARD, threadData - Index.nodes.push threadRoot, $.el 'hr' + [threadRoot, hr] = Build.thread g.BOARD, threadData + Index.nodes.push threadRoot, hr if thread = g.BOARD.threads[threadData.no] thread.setStatus 'Sticky', !!threadData.sticky thread.setStatus 'Closed', !!threadData.closed @@ -250,6 +250,6 @@ Index = nodes = Index.sortedNodes[nodesPerPage * pageNum ... nodesPerPage * (pageNum + 1)] else nodes = Index.sortedNodes - $.event 'IndexRefresh' $.rmAll Index.root + $.event 'IndexRefresh' $.add Index.root, nodes From 11371f1f40a6670c0496bb67da1b73e356bd3cdc Mon Sep 17 00:00:00 2001 From: Mayhem Date: Sun, 3 Nov 2013 17:59:25 +0100 Subject: [PATCH 48/49] Add sub-menu preview in the changelog. --- CHANGELOG.md | 3 ++- img/changelog/3.12.0/0.png | Bin 0 -> 18853 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 img/changelog/3.12.0/0.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 125b5b3bd..733165a61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ Index navigation improvements: - You can now refresh the index page you are on with the refresh shortcut in the header bar or the same keybind for refreshing threads. - - You can now switch between paged and all-threads index modes via the "Index Navigation" header sub-menu. + - You can now switch between paged and all-threads index modes via the "Index Navigation" header sub-menu:
    + ![index navigation](img/changelog/3.12.0/0.png) - Threads in the index can now be sorted by: - Bump order - Last reply diff --git a/img/changelog/3.12.0/0.png b/img/changelog/3.12.0/0.png new file mode 100644 index 0000000000000000000000000000000000000000..cb17e29c0a44ec4b4692eac9843ccad2fed1ec97 GIT binary patch literal 18853 zcmZs@1yqz>*Z7Sf-O?ZoDI(G(HI$U3bhpwa-O}CCAQIAzbT>$M_s~7$5W_ck-#+j2 zzW;A67i)l-bM3SDK0AJAW2mCMBqkau8XO!PrnJ<1B{(?vDLA-iNGR~IpZxxqKmi9Q zZ6*C)M8);l;qsybzAAj*ncJ!1wRzp|g{0Cdi&bYptp$Kk{46Z|z|T*caixc`v+N6z z#hIVFO14?VdwgTrIh^WkKj~Mj(m?}WuLfvMKC?@|LVbox$|~G0WayJun3IsgIV{UA z6I6c5V9CzAoS&bfypwe7Am4NwJTlmRiVc&*T=)j|H30VV&kIUV*ms@CurKQfaBw)l ze_wn(#XSN<_ofrtSO}&zEk1P0WC}sMuePpRD$XEF=}Sc)Tog{)-|yFqW}V}(O!!fM5#f^91 zJ3fx1({GucNXg!4RGEm~r_A3P>1BiQj;r<$Tei^^bytAECjIxY{p8eIJw~@=e0(8W zf7WkX&b$AG&eZI{`KV*&z1vxIO`Gp9oc1|+7SS_S8x@W;exYQ<4 zUauF-b)+3>5Ht){2YRmbb{6&CXMcg&k#cz5xNX)sy<=Sefp71Wt=YNwAy$0RYliJ* zg#6=d^W&~Qp`c}>lGFLV{$*>{I0gU8!+sFicSmC2%UK(na#<_LHT?`Qfdeg^)CuAB zx-Qkj2gH)Nh4*Bst9jnE^3VO~@dugr1l~XBJw0uCJ!hu2D;&~U*<3}7L8ylHzO7D( zU0-}Fx-Z6-XUuST0b5S!c1>)tv5Ov$@XqLjYA2)Ye4)$BRIPMmV#P7E$7?Krg8xlk zC+rCp4(F}sG}3}@yYewF(&Ep@CO^P@^?tQe@-~uS`vC4kHDmT9R@dR}Oa!YJXnYn+ zGB1yWqw``$JJX&Rm_Ey##_dz0yGN){fAZ@*`p=TUwofkmJ5-^dtd8258*G@!Ur*0C z#@9v$Y|344-3S{qjrgX55@}Ee1F|6C68!QQK~xV{|kwL)%y_xd8kn8}*SfL$L+s%IR{5BabK&~tPwPqf9}gEC(QYeY zc>}q6O;$cl8XisOBQ;LJw?`j(nH0V}BCwcu@zkEAb(~jNTsD;+t#;EtIRj-*XWv?D zYKOI^G_m{9gKt!9H@RN*^t$S;k%jmz2WQn3BdsLa<+c^s#;cn&7M|7S zFZ$Bq@mqr6)>G)^h|`QH&GxU+DL2;Q{xp!t4cym=E~_N{dv-`+Z80~k9^7W}K+$#W z```@c#-Jd(eY9Xr6W!DWiK*c<_-y#?y22@&!`r#itPF0CnZ4=m_>$`nrGqb0*R$!D zdWmEn1ZZOgPyldnc?(e>xhv-efkNglui*qQP8rkc4w-MO8uDkoLoYz1{)W9i?}h+RMnjPA(^|-By;=|P z?>yk~HqjkmJqStbtM7K{O-*O7t|430V^Sm-ErkfhAaXGxF$ z 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 Date: Sun, 3 Nov 2013 18:55:58 +0100 Subject: [PATCH 49/49] Index optimization: only build/load last replies of visible threads. --- src/Filtering/ThreadHiding.coffee | 8 +++-- src/General/Build.coffee | 18 +++-------- src/General/Index.coffee | 52 +++++++++++++++++++++++-------- 3 files changed, 49 insertions(+), 29 deletions(-) diff --git a/src/Filtering/ThreadHiding.coffee b/src/Filtering/ThreadHiding.coffee index 55fd4dd77..80c11b1d2 100644 --- a/src/Filtering/ThreadHiding.coffee +++ b/src/Filtering/ThreadHiding.coffee @@ -16,8 +16,12 @@ ThreadHiding = $.prepend @OP.nodes.root, ThreadHiding.makeButton @, 'hide' onrefresh: -> - for threadID, thread of g.BOARD.threads when thread.isHidden and thread.stub - $.prepend thread.OP.nodes.root.parentNode, thread.stub + for threadID, thread of g.BOARD.threads when thread.isHidden + root = thread.OP.nodes.root.parentNode + if thread.stub + $.prepend root, thread.stub + else + threadRoot.nextElementSibling.hidden = true return syncCatalog: -> diff --git a/src/General/Build.coffee b/src/General/Build.coffee index 74b40338b..c621afa3c 100644 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -271,26 +271,16 @@ Build = thread: (board, data) -> Build.spoilerRange[board] = data.custom_spoiler - if (OP = board.posts[data.no]) and parent = OP.nodes.root.parentNode - root = parent - hr = parent.nextElementSibling + if (OP = board.posts[data.no]) and root = OP.nodes.root.parentNode $.rmAll root else root = $.el 'div', className: 'thread' id: "t#{data.no}" - hr = $.el 'hr' - nodes = [] - for obj in [data].concat data.last_replies or [] - nodes.push if post = board.posts[obj.no] - post.nodes.root - else - Build.postFromObject obj, board.ID - - # build if necessary + nodes = [if OP then OP.nodes.root else Build.postFromObject data, board.ID] if data.omitted_posts - nodes.splice 1, 0, Build.summary board.ID, data.no, data.omitted_posts, data.omitted_images + nodes.push Build.summary board.ID, data.no, data.omitted_posts, data.omitted_images $.add root, nodes - [root, hr] + root diff --git a/src/General/Index.coffee b/src/General/Index.coffee index 5f66c0f40..5e0713f62 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -158,6 +158,7 @@ Index = try Index.parse JSON.parse req.response if req.status is 200 catch err + c.error 'Index failure:', err.stack # network error or non-JSON content for example. notice.setType 'error' notice.el.lastElementChild.textContent = 'Index refresh failed.' @@ -171,7 +172,7 @@ Index = Index.scrollToIndex() parse: (pages) -> Index.parseThreadList pages - Index.buildAll() + Index.buildThreads() Index.sort() Index.buildIndex() Index.buildPagelist() @@ -183,35 +184,59 @@ Index = for threadID, thread of g.BOARD.threads when thread.ID not in Index.liveThreadIDs thread.collect() return - buildAll: -> + buildThreads: -> Index.nodes = [] threads = [] posts = [] for threadData in Index.liveThreadData - [threadRoot, hr] = Build.thread g.BOARD, threadData - Index.nodes.push threadRoot, hr + threadRoot = Build.thread g.BOARD, threadData + Index.nodes.push threadRoot, $.el 'hr' if thread = g.BOARD.threads[threadData.no] thread.setStatus 'Sticky', !!threadData.sticky thread.setStatus 'Closed', !!threadData.closed else thread = new Thread threadData.no, g.BOARD threads.push thread - postRoots = $$ '.thread > .postContainer', threadRoot - for postRoot in postRoots when postRoot.id.match(/\d+/)[0] not of thread.posts + # postRoots = $$ '.thread > .postContainer', threadRoot + # for postRoot in postRoots when postRoot.id.match(/\d+/)[0] not of thread.posts + OPRoot = $ '.opContainer', threadRoot + continue if OPRoot.id.match(/\d+/)[0] of thread.posts + try + posts.push new Post OPRoot, thread, g.BOARD + catch err + # Skip posts that we failed to parse. + Main.handleErrors + message: "Parsing of Post No.#{postRoot.id.match /\d+/} failed. Post will be skipped." + error: err + + # Add the threads and
    s in a container to make sure all features work. + $.nodes Index.nodes + Main.callbackNodes Thread, threads + Main.callbackNodes Post, posts + buildReplies: (threadRoots) -> + posts = [] + for threadRoot in threadRoots by 2 + thread = Get.threadFromRoot threadRoot + i = Index.liveThreadIDs.indexOf thread.ID + continue unless lastReplies = Index.liveThreadData[i].last_replies + nodes = [] + for data in lastReplies + if post = thread.posts[data.no] + nodes.push post.nodes.root + continue + nodes.push node = Build.postFromObject data, thread.board.ID try - posts.push new Post postRoot, thread, g.BOARD + posts.push new Post node, thread, thread.board catch err # Skip posts that we failed to parse. errors = [] unless errors errors.push message: "Parsing of Post No.#{postRoot.id.match /\d+/} failed. Post will be skipped." error: err - Main.handleErrors errors if errors + $.add threadRoot, nodes - # Add the threads and
    s in a container to make sure all features work. - $.nodes Index.nodes - Main.callbackNodes Thread, threads - Main.callbackNodes Post, posts + Main.handleErrors errors if errors + Main.callbackNodes Post, posts sort: -> switch Conf['Index Sort'] when 'bump' @@ -232,7 +257,7 @@ Index = for threadID in sortedThreadIDs i = Index.liveThreadIDs.indexOf(threadID) * 2 Index.sortedNodes.push Index.nodes[i], Index.nodes[i + 1] - # Put the sticky threads on top of the index.g + # Put the sticky threads on top of the index. offset = 0 for threadRoot, i in Index.sortedNodes by 2 when Get.threadFromRoot(threadRoot).isSticky Index.sortedNodes.splice offset++ * 2, 0, Index.sortedNodes.splice(i, 2)... @@ -251,5 +276,6 @@ Index = else nodes = Index.sortedNodes $.rmAll Index.root + Index.buildReplies nodes $.event 'IndexRefresh' $.add Index.root, nodes