From 466c723ddaf740c0782b1be67baf8afd0745f87d Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 23 Apr 2013 18:52:25 +0200 Subject: [PATCH 01/25] Add -replace. #1059 Code from @seaweedchan --- CHANGELOG.md | 1 + src/features.coffee | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54cf98731..2e1bba0b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Added the `board-replace` setting to Custom Board Navigation ricing. - Added the option `Cooldown Prediction`, enabled by default. - Added the option `Hide Unread Count at (0)`, disabled by default. diff --git a/src/features.coffee b/src/features.coffee index 590e25bd7..5ef541518 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -91,7 +91,7 @@ Header = $.rmAll list return unless text as = $$('#full-board-list a', Header.bar)[0...-2] # ignore the Settings and Home links - nodes = text.match(/[\w@]+(-(all|title|full|index|catalog|text:"[^"]+"))*|[^\w@]+/g).map (t) -> + nodes = text.match(/[\w@]+(-(all|title|replace|full|index|catalog|text:"[^"]+"))*|[^\w@]+/g).map (t) -> if /^[^\w@]/.test t return $.tn t if /^toggle-all/.test t @@ -110,6 +110,9 @@ Header = a = a.cloneNode true if /-title/.test t a.textContent = a.title + else if /-replace/.test t + if $.hasClass a, 'current' + a.textContent = a.title else if /-full/.test t a.textContent = "/#{board}/ - #{a.title}" else if /-(index|catalog|text)/.test t @@ -633,6 +636,7 @@ Settings =
In the following, board can translate to a board ID (a, b, etc...), the current board (current), or the Status/Twitter link (status, @).
Board link: board
Title link: board-title
+
Board link (Replace with title when on that board): board-replace
Full text link: board-full
Custom text link: board-text:"VIP Board"
Index-only link: board-index
From 21224b4f188afb993dd1fce61ebacf17526a1e84 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 23 Apr 2013 19:45:13 +0200 Subject: [PATCH 02/25] Move the show announcement button to the header's menu. #1059 --- CHANGELOG.md | 1 + src/features.coffee | 32 +++++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e1bba0b3..56c10344b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- The button to show a hidden announcement is now inside the header's menu. - Added the `board-replace` setting to Custom Board Navigation ricing. - Added the option `Cooldown Prediction`, enabled by default. - Added the option `Hide Unread Count at (0)`, disabled by default. diff --git a/src/features.coffee b/src/features.coffee index 5ef541518..7a19b85a8 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -790,6 +790,20 @@ PSAHiding = $.addClass doc, 'hide-announcement' + entry = + type: 'header' + el: $.el 'a', + textContent: 'Show announcement.' + className: 'show-announcement' + href: 'javascript:;' + order: 50 + open: -> + if $.id('globalMessage')?.hidden + return true + false + $.event 'AddMenuEntry', entry + + $.on entry.el, 'click', PSAHiding.toggle $.on d, '4chanXInitFinished', @setup setup: -> $.off d, '4chanXInitFinished', PSAHiding.setup @@ -799,12 +813,12 @@ PSAHiding = return PSAHiding.btn = btn = $.el 'a', - title: 'Toggle announcement.' - innerHTML: '' + innerHTML: '[ - ]' + title: 'Hide announcement.' + className: 'hide-announcement' href: 'javascript:;' $.on btn, 'click', PSAHiding.toggle - text = PSAHiding.trim psa $.get 'hiddenPSAs', [], (item) -> PSAHiding.sync item['hiddenPSAs'] $.before psa, btn @@ -817,19 +831,19 @@ PSAHiding = $.get 'hiddenPSAs', [], ({hiddenPSAs}) -> if hide hiddenPSAs.push text + hiddenPSAs = hiddenPSAs[-5..] else + $.event 'CloseMenu' i = hiddenPSAs.indexOf text hiddenPSAs.splice i, 1 - hiddenPSAs = hiddenPSAs[-5..] PSAHiding.sync hiddenPSAs $.set 'hiddenPSAs', hiddenPSAs sync: (hiddenPSAs) -> - {btn} = PSAHiding - psa = $.id 'globalMessage' - [psa.hidden, btn.firstChild.textContent, btn.className] = if PSAHiding.trim(psa) in hiddenPSAs - [true, '[\u00A0+\u00A0]', 'show-announcement'] + psa = $.id 'globalMessage' + psa.hidden = PSAHiding.btn.hidden = if PSAHiding.trim(psa) in hiddenPSAs + true else - [false, '[\u00A0-\u00A0]', 'hide-announcement'] + false trim: (psa) -> psa.textContent.replace(/\W+/g, '').toLowerCase() From 9edade6f3c0f35192e100478f0c0a55f0579c04b Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 23 Apr 2013 22:18:50 +0200 Subject: [PATCH 03/25] Make the Custom Board Nav setting toggle-able and syncing. --- src/config.coffee | 9 ++--- src/features.coffee | 83 ++++++++++++++++++++++++++------------------- 2 files changed, 54 insertions(+), 38 deletions(-) diff --git a/src/config.coffee b/src/config.coffee index 713982f9c..1e69eb096 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -2,7 +2,6 @@ Config = main: 'Miscellaneous': 'Enable 4chan\'s Extension': [false, 'Compatibility between <%= meta.name %> and 4chan\'s inline extension is NOT guaranteed.'] - 'Custom Board Navigation': [true, 'Show custom links instead of the full board list'] 'Announcement Hiding': [true, 'Add button to hide 4chan announcements.'] '404 Redirect': [true, 'Redirect dead threads and images.'] 'Keybinds': [true, 'Bind actions to keyboard shortcuts.'] @@ -141,9 +140,11 @@ Config = '#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/' ].join '\n' 'Custom CSS': false - 'Bottom header': false - 'Header auto-hide': false - 'Header catalog links': false + Header: + 'Header catalog links': false + 'Header auto-hide': false + 'Bottom header': false + 'Custom Board Navigation': true boardnav: '[current-title / toggle-all]' time: '%m/%d/%y(%a)%H:%M:%S' backlink: '>>%id' diff --git a/src/features.coffee b/src/features.coffee index 7a19b85a8..9ac2880b2 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -24,20 +24,24 @@ Header = $.on window, 'load hashchange', Header.hashScroll $.on d, 'CreateNotification', @createNotification - catalogToggler = $.el 'label', - innerHTML: ' Use catalog board links' headerToggler = $.el 'label', innerHTML: ' Auto-hide header' barPositionToggler = $.el 'label', innerHTML: ' Bottom header' + catalogToggler = $.el 'label', + innerHTML: ' Use catalog board links' + customNavToggler = $.el 'label', + innerHTML: ' Custom board navigation' - @catalogToggler = catalogToggler.firstElementChild @headerToggler = headerToggler.firstElementChild @barPositionToggler = barPositionToggler.firstElementChild + @catalogToggler = catalogToggler.firstElementChild + @customNavToggler = customNavToggler.firstElementChild - $.on @catalogToggler, 'change', @toggleCatalogLinks $.on @headerToggler, 'change', @toggleBarVisibility $.on @barPositionToggler, 'change', @toggleBarPosition + $.on @catalogToggler, 'change', @toggleCatalogLinks + $.on @customNavToggler, 'change', @toggleCustomNav @setBarVisibility Conf['Header auto-hide'] @setBarPosition Conf['Bottom header'] @@ -50,9 +54,10 @@ Header = el: $.el 'span', textContent: 'Header' order: 105 subEntries: [ - {el: catalogToggler} {el: headerToggler} {el: barPositionToggler} + {el: catalogToggler} + {el: customNavToggler} ] $.asap (-> d.body), -> @@ -67,27 +72,24 @@ Header = if a = $ "a[href*='/#{g.BOARD}/']", nav a.className = 'current' fullBoardList = $ '#full-board-list', Header.bar - $.add fullBoardList, [nav.childNodes...] + fullBoardList.innerHTML = nav.innerHTML + $.rm $ '#navtopright', fullBoardList + btn = $.el 'span', + className: 'hide-board-list-button brackets-wrap' + innerHTML: ' - ' + $.on btn, 'click', Header.toggleBoardList + $.add fullBoardList, btn - if Conf['Custom Board Navigation'] - Header.generateBoardList Conf['boardnav'] - $.sync 'boardnav', Header.generateBoardList - btn = $.el 'span', - className: 'hide-board-list-button brackets-wrap' - innerHTML: ' - ' - $.on btn, 'click', Header.toggleBoardList - $.add fullBoardList, btn - else - $.rm $ '#custom-board-list', Header.bar - fullBoardList.hidden = false + Header.setCatalogLinks Conf['Header catalog links'] + Header.setCustomNav Conf['Custom Board Navigation'] + Header.generateBoardList Conf['boardnav'] - Header.setCatalogLinks Conf['Header catalog links'] - $.sync 'Header catalog links', Header.setCatalogLinks + $.sync 'Header catalog links', Header.setCatalogLinks + $.sync 'Custom Board Navigation', Header.setCustomNav + $.sync 'boardnav', Header.generateBoardList generateBoardList: (text) -> - unless list = $ '#custom-board-list', Header.bar - # init'd with the custom board list disabled. - return + list = $ '#custom-board-list', Header.bar $.rmAll list return unless text as = $$('#full-board-list a', Header.bar)[0...-2] # ignore the Settings and Home links @@ -136,18 +138,6 @@ Header = custom.hidden = !showBoardList full.hidden = showBoardList - setCatalogLinks: (useCatalog) -> - Header.catalogToggler.checked = useCatalog - as = $$ '#board-list a[href*="boards.4chan.org"]', Header.bar - path = if useCatalog then 'catalog' else '' - for a in as - continue if a.dataset.only - a.pathname = "/#{a.pathname.split('/')[1]}/#{path}" - return - toggleCatalogLinks: -> - $.cb.checked.call @ - Header.setCatalogLinks @checked - setBarVisibility: (hide) -> Header.headerToggler.checked = hide $.event 'CloseMenu' @@ -182,6 +172,31 @@ Header = $.cb.checked.call @ Header.setBarPosition @checked + setCatalogLinks: (useCatalog) -> + Header.catalogToggler.checked = useCatalog + as = $$ '#board-list a[href*="boards.4chan.org"]', Header.bar + path = if useCatalog then 'catalog' else '' + for a in as + continue if a.dataset.only + a.pathname = "/#{a.pathname.split('/')[1]}/#{path}" + return + toggleCatalogLinks: -> + $.cb.checked.call @ + Header.setCatalogLinks @checked + + setCustomNav: (show) -> + Header.customNavToggler.checked = show + cust = $ '#custom-board-list', Header.bar + full = $ '#full-board-list', Header.bar + btn = $ '.hide-board-list-button', full + [cust.hidden, full.hidden, btn.hidden] = if show + [false, true, false] + else + [true, false, true] + toggleCustomNav: -> + $.cb.checked.call @ + Header.setCustomNav @checked + hashScroll: -> return unless post = $.id @location.hash[1..] return if (Get.postFromRoot post).isHidden From 9757334700cdd445623b5e0940314ceb80e16aa3 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 23 Apr 2013 22:26:17 +0200 Subject: [PATCH 04/25] Update deps. --- package.json | 4 ++-- src/features.coffee | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 246d3913a..2b32a00dc 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "grunt-bump": "~0.0.2", "grunt-concurrent": "~0.2.0", "grunt-contrib-clean": "~0.4.1", - "grunt-contrib-coffee": "~0.6.7", - "grunt-contrib-compress": "~0.4.10", + "grunt-contrib-coffee": "~0.7.0", + "grunt-contrib-compress": "~0.5.0", "grunt-contrib-concat": "~0.2.0", "grunt-contrib-copy": "~0.4.1", "grunt-contrib-watch": "~0.3.1", diff --git a/src/features.coffee b/src/features.coffee index 9ac2880b2..ce1af1803 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -808,7 +808,7 @@ PSAHiding = entry = type: 'header' el: $.el 'a', - textContent: 'Show announcement.' + textContent: 'Show announcement' className: 'show-announcement' href: 'javascript:;' order: 50 From dc2ab7807adc1ba0170213e398fb9f9e211461f1 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 23 Apr 2013 22:35:36 +0200 Subject: [PATCH 05/25] Add a link to the header's menu to find where to edit the custom board nav. --- src/features.coffee | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/features.coffee b/src/features.coffee index ce1af1803..562389504 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -32,6 +32,9 @@ Header = innerHTML: ' Use catalog board links' customNavToggler = $.el 'label', innerHTML: ' Custom board navigation' + editCustomNav = $.el 'a', + textContent: 'Edit custom board navigation' + href: 'javascript:;' @headerToggler = headerToggler.firstElementChild @barPositionToggler = barPositionToggler.firstElementChild @@ -42,6 +45,7 @@ Header = $.on @barPositionToggler, 'change', @toggleBarPosition $.on @catalogToggler, 'change', @toggleCatalogLinks $.on @customNavToggler, 'change', @toggleCustomNav + $.on editCustomNav, 'click', @editCustomNav @setBarVisibility Conf['Header auto-hide'] @setBarPosition Conf['Bottom header'] @@ -58,6 +62,7 @@ Header = {el: barPositionToggler} {el: catalogToggler} {el: customNavToggler} + {el: editCustomNav} ] $.asap (-> d.body), -> @@ -197,6 +202,11 @@ Header = $.cb.checked.call @ Header.setCustomNav @checked + editCustomNav: -> + Settings.open 'Rice' + settings = $.id 'fourchanx-settings' + $('input[name=boardnav]', settings).focus() + hashScroll: -> return unless post = $.id @location.hash[1..] return if (Get.postFromRoot post).isHidden From fe0f79458d1a1712e331d57e27dbff6ca4e31a5f Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 23 Apr 2013 23:25:50 +0200 Subject: [PATCH 06/25] Optional top and bottom original board lists. #996 --- CHANGELOG.md | 1 + css/style.css | 7 ++++--- src/config.coffee | 8 +++++--- src/features.coffee | 49 ++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56c10344b..8de27bd6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- The top and bottom original board lists are now optional, disabled by default. - The button to show a hidden announcement is now inside the header's menu. - Added the `board-replace` setting to Custom Board Navigation ricing. - Added the option `Cooldown Prediction`, enabled by default. diff --git a/css/style.css b/css/style.css index fde0aef1f..1b1bb06f2 100644 --- a/css/style.css +++ b/css/style.css @@ -99,9 +99,10 @@ a[href="javascript:;"] { :root.bottom-header body { margin-bottom: 2em; } -.fourchan-x #boardNavDesktop, -.fourchan-x #navtopright, -.fourchan-x #boardNavDesktopFoot { +:root.fourchan-x #navtopright, +:root.fourchan-x #navbotright, +:root.fourchan-x:not(.show-original-top-board-list) #boardNavDesktop, +:root.fourchan-x:not(.show-original-bot-board-list) #boardNavDesktopFoot { display: none !important; } #header { diff --git a/src/config.coffee b/src/config.coffee index 1e69eb096..c1d1a1bad 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -141,9 +141,11 @@ Config = ].join '\n' 'Custom CSS': false Header: - 'Header catalog links': false - 'Header auto-hide': false - 'Bottom header': false + 'Header auto-hide': false + 'Bottom header': false + 'Header catalog links': false + 'Top Board List': false + 'Bottom Board List': false 'Custom Board Navigation': true boardnav: '[current-title / toggle-all]' time: '%m/%d/%y(%a)%H:%M:%S' diff --git a/src/features.coffee b/src/features.coffee index 562389504..1c0610cde 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -30,6 +30,10 @@ Header = innerHTML: ' Bottom header' catalogToggler = $.el 'label', innerHTML: ' Use catalog board links' + topBoardToggler = $.el 'label', + innerHTML: ' Top original board list' + botBoardToggler = $.el 'label', + innerHTML: ' Bottom original board list' customNavToggler = $.el 'label', innerHTML: ' Custom board navigation' editCustomNav = $.el 'a', @@ -39,19 +43,27 @@ Header = @headerToggler = headerToggler.firstElementChild @barPositionToggler = barPositionToggler.firstElementChild @catalogToggler = catalogToggler.firstElementChild + @topBoardToggler = topBoardToggler.firstElementChild + @botBoardToggler = botBoardToggler.firstElementChild @customNavToggler = customNavToggler.firstElementChild $.on @headerToggler, 'change', @toggleBarVisibility $.on @barPositionToggler, 'change', @toggleBarPosition $.on @catalogToggler, 'change', @toggleCatalogLinks + $.on @topBoardToggler, 'change', @toggleOriginalBoardList + $.on @botBoardToggler, 'change', @toggleOriginalBoardList $.on @customNavToggler, 'change', @toggleCustomNav $.on editCustomNav, 'click', @editCustomNav @setBarVisibility Conf['Header auto-hide'] @setBarPosition Conf['Bottom header'] + @setTopBoardList Conf['Top Board List'] + @setBotBoardList Conf['Bottom Board List'] - $.sync 'Header auto-hide', @setBarVisibility - $.sync 'Bottom header', @setBarPosition + $.sync 'Header auto-hide', @setBarVisibility + $.sync 'Bottom header', @setBarPosition + $.sync 'Top Board List', @setTopBoardList + $.sync 'Bottom Board List', @setBotBoardList $.event 'AddMenuEntry', type: 'header' @@ -61,6 +73,8 @@ Header = {el: headerToggler} {el: barPositionToggler} {el: catalogToggler} + {el: topBoardToggler} + {el: botBoardToggler} {el: customNavToggler} {el: editCustomNav} ] @@ -72,6 +86,13 @@ Header = $.asap (-> $.id('boardNavMobile') or d.readyState is 'complete'), Header.setBoardList $.prepend d.body, headerEl + $.ready -> + if a = $ "a[href*='/#{g.BOARD}/']", $.id 'boardNavDesktopFoot' + a.className = 'current' + + Header.setCatalogLinks Conf['Header catalog links'] + $.sync 'Header catalog links', Header.setCatalogLinks + setBoardList: -> nav = $.id 'boardNavDesktop' if a = $ "a[href*='/#{g.BOARD}/']", nav @@ -85,11 +106,9 @@ Header = $.on btn, 'click', Header.toggleBoardList $.add fullBoardList, btn - Header.setCatalogLinks Conf['Header catalog links'] Header.setCustomNav Conf['Custom Board Navigation'] Header.generateBoardList Conf['boardnav'] - $.sync 'Header catalog links', Header.setCatalogLinks $.sync 'Custom Board Navigation', Header.setCustomNav $.sync 'boardnav', Header.generateBoardList @@ -179,7 +198,11 @@ Header = setCatalogLinks: (useCatalog) -> Header.catalogToggler.checked = useCatalog - as = $$ '#board-list a[href*="boards.4chan.org"]', Header.bar + as = $$ [ + '#board-list a[href*="boards.4chan.org"]' + '#boardNavDesktop a[href*="boards.4chan.org"]' + '#boardNavDesktopFoot a[href*="boards.4chan.org"]' + ].join ', ' path = if useCatalog then 'catalog' else '' for a in as continue if a.dataset.only @@ -189,6 +212,22 @@ Header = $.cb.checked.call @ Header.setCatalogLinks @checked + setTopBoardList: (show) -> + Header.topBoardToggler.checked = show + if show + $.addClass doc, 'show-original-top-board-list' + else + $.rmClass doc, 'show-original-top-board-list' + setBotBoardList: (show) -> + Header.botBoardToggler.checked = show + if show + $.addClass doc, 'show-original-bot-board-list' + else + $.rmClass doc, 'show-original-bot-board-list' + toggleOriginalBoardList: -> + $.cb.checked.call @ + (if @name is 'Top Board List' then Header.setTopBoardList else Header.setBotBoardList) @checked + setCustomNav: (show) -> Header.customNavToggler.checked = show cust = $ '#custom-board-list', Header.bar From 306584aa3605323c777473789c31c9ff8f7d9979 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 23 Apr 2013 23:33:54 +0200 Subject: [PATCH 07/25] Hide the
following a hidden announcement. --- src/features.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/features.coffee b/src/features.coffee index 1c0610cde..6a00dd7c7 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -908,6 +908,8 @@ PSAHiding = true else false + if hr = $.x 'following-sibling::hr', psa + hr.hidden = psa.hidden trim: (psa) -> psa.textContent.replace(/\W+/g, '').toLowerCase() From aed7a456f55c72da56778a98d1496bfd3695b7a5 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 23 Apr 2013 23:40:13 +0200 Subject: [PATCH 08/25] Changelog. --- CHANGELOG.md | 1 + img/changelog/3.2.0/0.png | Bin 0 -> 13687 bytes 2 files changed, 1 insertion(+) create mode 100644 img/changelog/3.2.0/0.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 8de27bd6c..857d98ac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ - The top and bottom original board lists are now optional, disabled by default. - The button to show a hidden announcement is now inside the header's menu. +- Reorganized Header menu:
![menu](img/changelog/3.2.0/0.png) - Added the `board-replace` setting to Custom Board Navigation ricing. - Added the option `Cooldown Prediction`, enabled by default. - Added the option `Hide Unread Count at (0)`, disabled by default. diff --git a/img/changelog/3.2.0/0.png b/img/changelog/3.2.0/0.png new file mode 100644 index 0000000000000000000000000000000000000000..d73ba75fe5aab5f4212f5b9d402d2fda0c03da21 GIT binary patch literal 13687 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 literal 0 HcmV?d00001 From ffe98e9dd9aec8093389e08b2ffe395eccf137a7 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 23 Apr 2013 23:52:18 +0200 Subject: [PATCH 09/25] Release 4chan X v3.2.0. --- CHANGELOG.md | 2 ++ Gruntfile.js | 2 +- package.json | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 857d98ac5..1abc7174f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## 3.2.0 - *2013-04-23* + - The top and bottom original board lists are now optional, disabled by default. - The button to show a hidden announcement is now inside the header's menu. - Reorganized Header menu:
![menu](img/changelog/3.2.0/0.png) diff --git a/Gruntfile.js b/Gruntfile.js index e40c4d9d9..8ca46754b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -180,7 +180,7 @@ module.exports = function(grunt) { grunt.registerTask('reloadPkg', 'Reload the package', function() { // Update the `pkg` object with the new version. pkg = grunt.file.readJSON('package.json'); - concatOptions.process.data = pkg; + grunt.config.data.pkg = concatOptions.process.data = pkg; grunt.log.ok('pkg reloaded.'); }); diff --git a/package.json b/package.json index 2b32a00dc..9290db6e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "4chan-X", - "version": "3.1.4", + "version": "3.2.0", "description": "Cross-browser extension for productive lurking on 4chan.", "meta": { "name": "4chan X", From d1abf7541f6acc59ef1302fd6a326621bd1007e2 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Wed, 24 Apr 2013 16:27:02 +0200 Subject: [PATCH 10/25] Fix unread scroll. --- src/features.coffee | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/features.coffee b/src/features.coffee index 6a00dd7c7..728d3fa58 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -3859,15 +3859,16 @@ Unread = scroll: -> # Let the header's onload callback handle it. - return if (hash = location.hash.match /\d+/) and hash[0] of @posts + return if (hash = location.hash.match /\d+/) and hash[0] of Unread.thread.posts if Unread.posts.length # Scroll to before the first unread post. while root = $.x 'preceding-sibling::div[contains(@class,"postContainer")][1]', Unread.posts[0].nodes.root break unless (Get.postFromRoot root).isHidden root.scrollIntoView false - else if posts.length - # Scroll to the last read post. - Header.scrollToPost posts[posts.length - 1].nodes.root + return + # Scroll to the last read post. + posts = Object.keys Unread.thread.posts + Header.scrollToPost Unread.thread.posts[posts[posts.length - 1]].nodes.root sync: -> lastReadPost = Unread.db.get From c00fad9bd32e582045d144cd83cb80f755574221 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Wed, 24 Apr 2013 23:44:06 +0200 Subject: [PATCH 11/25] Don't extend $. --- lib/$.coffee | 511 +++++++++++++++++++++++++-------------------------- 1 file changed, 254 insertions(+), 257 deletions(-) diff --git a/lib/$.coffee b/lib/$.coffee index 67b57c0ff..2ea705662 100644 --- a/lib/$.coffee +++ b/lib/$.coffee @@ -6,241 +6,238 @@ $ = (selector, root=d.body) -> $$ = (selector, root=d.body) -> [root.querySelectorAll(selector)...] +$.SECOND = 1000 +$.MINUTE = 1000 * 60 +$.HOUR = 1000 * 60 * 60 +$.DAY = 1000 * 60 * 60 * 24 +$.id = (id) -> + d.getElementById id +$.ready = (fc) -> + if d.readyState in ['interactive', 'complete'] + $.queueTask fc + return + cb = -> + $.off d, 'DOMContentLoaded', cb + fc() + $.on d, 'DOMContentLoaded', cb +$.formData = (form) -> + if form instanceof HTMLFormElement + return new FormData form + fd = new FormData() + for key, val of form + continue unless val + # XXX GM bug + # if val instanceof Blob + if val.size and val.name + fd.append key, val, val.name + else + fd.append key, val + fd $.extend = (object, properties) -> for key, val of properties object[key] = val return - -$.extend $, - SECOND: 1000 - MINUTE: 1000 * 60 - HOUR : 1000 * 60 * 60 - DAY : 1000 * 60 * 60 * 24 - id: (id) -> - d.getElementById id - ready: (fc) -> - if d.readyState in ['interactive', 'complete'] - $.queueTask fc - return - cb = -> - $.off d, 'DOMContentLoaded', cb - fc() - $.on d, 'DOMContentLoaded', cb - formData: (form) -> - if form instanceof HTMLFormElement - return new FormData form - fd = new FormData() - for key, val of form - continue unless val - # XXX GM bug - # if val instanceof Blob - if val.size and val.name - fd.append key, val, val.name +$.ajax = (url, callbacks, opts={}) -> + {type, cred, headers, upCallbacks, form, sync} = opts + r = new XMLHttpRequest() + type or= form and 'post' or 'get' + r.open type, url, !sync + for key, val of headers + r.setRequestHeader key, val + $.extend r, callbacks + $.extend r.upload, upCallbacks + r.withCredentials = cred + r.send form + r +$.cache = do -> + reqs = {} + (url, cb) -> + if req = reqs[url] + if req.readyState is 4 + cb.call req else - fd.append key, val - fd - ajax: (url, callbacks, opts={}) -> - {type, cred, headers, upCallbacks, form, sync} = opts - r = new XMLHttpRequest() - type or= form and 'post' or 'get' - r.open type, url, !sync - for key, val of headers - r.setRequestHeader key, val - $.extend r, callbacks - $.extend r.upload, upCallbacks - r.withCredentials = cred - r.send form - r - cache: do -> - reqs = {} - (url, cb) -> - if req = reqs[url] - if req.readyState is 4 - cb.call req - else - req.callbacks.push cb - return - rm = -> delete reqs[url] - req = $.ajax url, - onload: (e) -> - cb.call @, e for cb in @callbacks - delete @callbacks - onabort: rm - onerror: rm - req.callbacks = [cb] - reqs[url] = req - cb: - checked: -> - $.set @name, @checked - Conf[@name] = @checked - value: -> - $.set @name, @value.trim() - Conf[@name] = @value - asap: (test, cb) -> - if test() - cb() - else - setTimeout $.asap, 25, test, cb - addStyle: (css) -> - style = $.el 'style', - textContent: css - $.asap (-> d.head), -> - $.add d.head, style - style - x: (path, root=d.body) -> - # XPathResult.ANY_UNORDERED_NODE_TYPE === 8 - d.evaluate(path, root, null, 8, null).singleNodeValue - addClass: (el, className) -> - el.classList.add className - rmClass: (el, className) -> - el.classList.remove className - hasClass: (el, className) -> - el.classList.contains className - rm: do -> - if 'remove' of Element.prototype - (el) -> el.remove() - else - (el) -> el.parentNode?.removeChild el - rmAll: (root) -> - # jsperf.com/emptify-element - while node = root.firstChild - # HTMLSelectElement.remove !== Element.remove - root.removeChild node - return - tn: (s) -> - d.createTextNode s - nodes: (nodes) -> - unless nodes instanceof Array - return nodes - frag = d.createDocumentFragment() - for node in nodes - frag.appendChild node - frag - add: (parent, el) -> - parent.appendChild $.nodes el - prepend: (parent, el) -> - parent.insertBefore $.nodes(el), parent.firstChild - after: (root, el) -> - root.parentNode.insertBefore $.nodes(el), root.nextSibling - before: (root, el) -> - root.parentNode.insertBefore $.nodes(el), root - replace: (root, el) -> - root.parentNode.replaceChild $.nodes(el), root - el: (tag, properties) -> - el = d.createElement tag - $.extend el, properties if properties - el - on: (el, events, handler) -> - for event in events.split ' ' - el.addEventListener event, handler, false - return - off: (el, events, handler) -> - for event in events.split ' ' - el.removeEventListener event, handler, false - return - event: (event, detail, root=d) -> - root.dispatchEvent new CustomEvent event, {bubbles: true, detail} - open: do -> - if GM_openInTab? - (URL) -> - # XXX fix GM opening file://// for protocol-less URLs. - a = $.el 'a', href: URL - GM_openInTab a.href - else - (URL) -> window.open URL, '_blank' - debounce: (wait, fn) -> + req.callbacks.push cb + return + rm = -> delete reqs[url] + req = $.ajax url, + onload: (e) -> + cb.call @, e for cb in @callbacks + delete @callbacks + onabort: rm + onerror: rm + req.callbacks = [cb] + reqs[url] = req +$.cb = + checked: -> + $.set @name, @checked + Conf[@name] = @checked + value: -> + $.set @name, @value.trim() + Conf[@name] = @value +$.asap = (test, cb) -> + if test() + cb() + else + setTimeout $.asap, 25, test, cb +$.addStyle = (css) -> + style = $.el 'style', + textContent: css + $.asap (-> d.head), -> + $.add d.head, style + style +$.x = (path, root=d.body) -> + # XPathResult.ANY_UNORDERED_NODE_TYPE === 8 + d.evaluate(path, root, null, 8, null).singleNodeValue +$.addClass = (el, className) -> + el.classList.add className +$.rmClass = (el, className) -> + el.classList.remove className +$.hasClass = (el, className) -> + el.classList.contains className +$.rm = do -> + if 'remove' of Element.prototype + (el) -> el.remove() + else + (el) -> el.parentNode?.removeChild el +$.rmAll = (root) -> + # jsperf.com/emptify-element + while node = root.firstChild + # HTMLSelectElement.remove !== Element.remove + root.removeChild node + return +$.tn = (s) -> + d.createTextNode s +$.nodes = (nodes) -> + unless nodes instanceof Array + return nodes + frag = d.createDocumentFragment() + for node in nodes + frag.appendChild node + frag +$.add = (parent, el) -> + parent.appendChild $.nodes el +$.prepend = (parent, el) -> + parent.insertBefore $.nodes(el), parent.firstChild +$.after = (root, el) -> + root.parentNode.insertBefore $.nodes(el), root.nextSibling +$.before = (root, el) -> + root.parentNode.insertBefore $.nodes(el), root +$.replace = (root, el) -> + root.parentNode.replaceChild $.nodes(el), root +$.el = (tag, properties) -> + el = d.createElement tag + $.extend el, properties if properties + el +$.on = (el, events, handler) -> + for event in events.split ' ' + el.addEventListener event, handler, false + return +$.off = (el, events, handler) -> + for event in events.split ' ' + el.removeEventListener event, handler, false + return +$.event = (event, detail, root=d) -> + root.dispatchEvent new CustomEvent event, {bubbles: true, detail} +$.open = do -> + if GM_openInTab? + (URL) -> + # XXX fix GM opening file://// for protocol-less URLs. + a = $.el 'a', href: URL + GM_openInTab a.href + else + (URL) -> window.open URL, '_blank' +$.debounce = (wait, fn) -> + timeout = null + that = null + args = null + exec = -> + fn.apply that, args timeout = null - that = null - args = null - exec = -> - fn.apply that, args - timeout = null - -> - args = arguments - that = this - if timeout - # stop current reset - clearTimeout timeout - else - exec() + -> + args = arguments + that = this + if timeout + # stop current reset + clearTimeout timeout + else + exec() - # after wait, let next invocation execute immediately - timeout = setTimeout exec, wait - queueTask: do -> - # inspired by https://www.w3.org/Bugs/Public/show_bug.cgi?id=15007 - taskQueue = [] - execTask = -> - task = taskQueue.shift() - func = task[0] - args = Array::slice.call task, 1 - func.apply func, args - if window.MessageChannel - taskChannel = new MessageChannel() - taskChannel.port1.onmessage = execTask - -> - taskQueue.push arguments - taskChannel.port2.postMessage null - else # XXX Firefox - -> - taskQueue.push arguments - setTimeout execTask, 0 - globalEval: (code) -> - script = $.el 'script', - textContent: code - $.add (d.head or doc), script - $.rm script - bytesToString: (size) -> - unit = 0 # Bytes - while size >= 1024 - size /= 1024 - unit++ - # Remove trailing 0s. - size = - if unit > 1 - # Keep the size as a float if the size is greater than 2^20 B. - # Round to hundredth. - Math.round(size * 100) / 100 - else - # Round to an integer otherwise. - Math.round size - "#{size} #{['B', 'KB', 'MB', 'GB'][unit]}" - syncing: {} - sync: do -> + # after wait, let next invocation execute immediately + timeout = setTimeout exec, wait +$.queueTask = do -> + # inspired by https://www.w3.org/Bugs/Public/show_bug.cgi?id=15007 + taskQueue = [] + execTask = -> + task = taskQueue.shift() + func = task[0] + args = Array::slice.call task, 1 + func.apply func, args + if window.MessageChannel + taskChannel = new MessageChannel() + taskChannel.port1.onmessage = execTask + -> + taskQueue.push arguments + taskChannel.port2.postMessage null + else # XXX Firefox + -> + taskQueue.push arguments + setTimeout execTask, 0 +$.globalEval = (code) -> + script = $.el 'script', + textContent: code + $.add (d.head or doc), script + $.rm script +$.bytesToString = (size) -> + unit = 0 # Bytes + while size >= 1024 + size /= 1024 + unit++ + # Remove trailing 0s. + size = + if unit > 1 + # Keep the size as a float if the size is greater than 2^20 B. + # Round to hundredth. + Math.round(size * 100) / 100 + else + # Round to an integer otherwise. + Math.round size + "#{size} #{['B', 'KB', 'MB', 'GB'][unit]}" +$.syncing = {} +$.sync = do -> <% if (type === 'crx') { %> - chrome.storage.onChanged.addListener (changes) -> - for key of changes - if cb = $.syncing[key] - cb changes[key].newValue - return - (key, cb) -> $.syncing[key] = cb + chrome.storage.onChanged.addListener (changes) -> + for key of changes + if cb = $.syncing[key] + cb changes[key].newValue + return + (key, cb) -> $.syncing[key] = cb <% } else { %> - window.addEventListener 'storage', (e) -> - if cb = $.syncing[e.key] - cb JSON.parse e.newValue - , false - (key, cb) -> $.syncing[g.NAMESPACE + key] = cb + $.on window, 'storage', (e) -> + if cb = $.syncing[e.key] + cb JSON.parse e.newValue + (key, cb) -> $.syncing[g.NAMESPACE + key] = cb <% } %> - item: (key, val) -> - item = {} - item[key] = val - item +$.item = (key, val) -> + item = {} + item[key] = val + item <% if (type === 'crx') { %> - # https://developer.chrome.com/extensions/storage.html - delete: (keys) -> - chrome.storage.sync.remove keys - get: (key, val, cb) -> - if typeof cb is 'function' - items = $.item key, val - else - items = key - cb = val - chrome.storage.sync.get items, cb - set: (key, val) -> - items = if typeof key is 'string' - $.item key, val - else - key - chrome.storage.sync.set items +# https://developer.chrome.com/extensions/storage.html +$.delete = (keys) -> + chrome.storage.sync.remove keys +$.get = (key, val, cb) -> + if typeof cb is 'function' + items = $.item key, val + else + items = key + cb = val + chrome.storage.sync.get items, cb +$.set = (key, val) -> + items = if typeof key is 'string' + $.item key, val + else + key + chrome.storage.sync.set items <% } else if (type === 'userjs') { %> do -> # http://www.opera.com/docs/userjs/specs/#scriptstorage @@ -287,38 +284,38 @@ do -> return <% } else { %> # http://wiki.greasespot.net/Main_Page - delete: (keys) -> - unless keys instanceof Array - keys = [keys] - for key in keys - key = g.NAMESPACE + key - localStorage.removeItem key - GM_deleteValue key - return - get: (key, val, cb) -> - if typeof cb is 'function' - items = $.item key, val - else - items = key - cb = val - $.queueTask -> - for key of items - if val = GM_getValue g.NAMESPACE + key - items[key] = JSON.parse val - cb items - set: do -> - set = (key, val) -> - key = g.NAMESPACE + key - val = JSON.stringify val - if key of $.syncing - # for `storage` events - localStorage.setItem key, val - GM_setValue key, val - (keys, val) -> - if typeof keys is 'string' - set keys, val - return - for key, val of keys - set key, val +$.delete = (keys) -> + unless keys instanceof Array + keys = [keys] + for key in keys + key = g.NAMESPACE + key + localStorage.removeItem key + GM_deleteValue key + return +$.get = (key, val, cb) -> + if typeof cb is 'function' + items = $.item key, val + else + items = key + cb = val + $.queueTask -> + for key of items + if val = GM_getValue g.NAMESPACE + key + items[key] = JSON.parse val + cb items +$.set = do -> + set = (key, val) -> + key = g.NAMESPACE + key + val = JSON.stringify val + if key of $.syncing + # for `storage` events + localStorage.setItem key, val + GM_setValue key, val + (keys, val) -> + if typeof keys is 'string' + set keys, val return + for key, val of keys + set key, val + return <% } %> From 4b474a5ec55b2d8b503e77191379c7e3d4935451 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Thu, 25 Apr 2013 20:49:20 +0200 Subject: [PATCH 12/25] license "damn i wouldn't like working with that kind of assholes around" --- LICENSE | 10 ++++++++++ src/banner.js | 5 +---- src/features.coffee | 4 ---- src/metadata.js | 4 +--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/LICENSE b/LICENSE index 37991acde..78a762845 100644 --- a/LICENSE +++ b/LICENSE @@ -20,3 +20,13 @@ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- + +Contains code from the following repositories: + +4chan/4chan-JS (https://github.com/4chan/4chan-JS) + license: https://github.com/4chan/4chan-JS/blob/master/LICENSE + +seaweedchan/4chan-x (https://github.com/seaweedchan/4chan-x) + license: https://github.com/seaweedchan/4chan-x/blob/master/LICENSE diff --git a/src/banner.js b/src/banner.js index f7d4ace23..10140c5d6 100644 --- a/src/banner.js +++ b/src/banner.js @@ -1,10 +1,7 @@ /* <%= meta.name %> - Version <%= version %> - <%= grunt.template.today('yyyy-mm-dd') %> * <%= meta.page %> * - * Copyright (c) 2009-2011 James Campos - * Copyright (c) 2012-<%= grunt.template.today('yyyy') %> Nicolas Stepien - * Licensed under the MIT license. - * <%= meta.repo %>blob/master/LICENSE + * Copyrights and License: <%= meta.repo %>blob/<%= meta.mainBranch %>/LICENSE * * Contributors: * <%= meta.repo %>graphs/contributors diff --git a/src/features.coffee b/src/features.coffee index 728d3fa58..7e9d612f7 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -2306,10 +2306,6 @@ Build = isDeleted: !!data.filedeleted Build.post o post: (o, isArchived) -> - ### - This function contains code from 4chan-JS (https://github.com/4chan/4chan-JS). - @license: https://github.com/4chan/4chan-JS/blob/master/LICENSE - ### { postID, threadID, boardID name, capcode, tripcode, uniqueID, email, subject, flagCode, flagName, date, dateUTC diff --git a/src/metadata.js b/src/metadata.js index 8371b5a41..957342f9a 100644 --- a/src/metadata.js +++ b/src/metadata.js @@ -3,9 +3,7 @@ // @version <%= version %> // @namespace <%= name %> // @description <%= description %> -// @copyright 2009-2011 James Campos -// @copyright 2012-<%= grunt.template.today('yyyy') %> Nicolas Stepien -// @license MIT; http://en.wikipedia.org/wiki/Mit_license +// @license MIT; <%= meta.repo %>blob/<%= meta.mainBranch %>/LICENSE <%= meta.matches.map(function(match) { return '// @match ' + match; From 8b5441cf65068c0e773e475e3390ca1aac049b88 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Fri, 26 Apr 2013 01:42:21 +0200 Subject: [PATCH 13/25] Don't save the updater interval on each thread load. --- src/features.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features.coffee b/src/features.coffee index 7e9d612f7..93ca86478 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -4175,10 +4175,10 @@ ThreadUpdater = ThreadUpdater.timeoutID = setTimeout ThreadUpdater.timeout, 1000 else clearTimeout ThreadUpdater.timeoutID - interval: -> + interval: (e) -> val = Math.max 5, parseInt @value, 10 ThreadUpdater.interval = @value = val - $.cb.value.call @ + $.cb.value.call @ if e load: -> {req} = ThreadUpdater switch req.status From 581fc802abdb359ed7f5ee4cee6fc00bf6724be2 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Fri, 26 Apr 2013 01:52:35 +0200 Subject: [PATCH 14/25] Auto Update This shouldn't be in Conf and shouldn't be saved. --- src/features.coffee | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/features.coffee b/src/features.coffee index 93ca86478..1c2c6c46b 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -4091,11 +4091,10 @@ ThreadUpdater = checked = if Conf[name] then 'checked' else '' html += "
" - checked = if Conf['Auto Update'] then 'checked' else '' html = """
#{html} -
+
""" @@ -4103,6 +4102,7 @@ ThreadUpdater = @dialog = UI.dialog 'updater', 'bottom: 0; right: 0;', html @timer = $ '#update-timer', @dialog @status = $ '#update-status', @dialog + @isUpdating = Conf['Auto Update'] Thread::callbacks.push name: 'Thread Updater' @@ -4123,7 +4123,8 @@ ThreadUpdater = $.on input, 'change', ThreadUpdater.cb.scrollBG ThreadUpdater.cb.scrollBG() when 'Auto Update This' - $.on input, 'change', ThreadUpdater.cb.autoUpdate + $.off input, 'change', $.cb.checked + $.on input, 'change', ThreadUpdater.cb.autoUpdate $.event 'change', null, input when 'Interval' $.on input, 'change', ThreadUpdater.cb.interval @@ -4149,14 +4150,14 @@ ThreadUpdater = if ThreadUpdater.online = navigator.onLine ThreadUpdater.outdateCount = 0 ThreadUpdater.set 'timer', ThreadUpdater.getInterval() - ThreadUpdater.update() if Conf['Auto Update This'] + ThreadUpdater.update() if ThreadUpdater.isUpdating ThreadUpdater.set 'status', null, null else ThreadUpdater.set 'timer', null ThreadUpdater.set 'status', 'Offline', 'warning' ThreadUpdater.cb.autoUpdate() post: (e) -> - return unless Conf['Auto Update This'] and e.detail.threadID is ThreadUpdater.thread.ID + return unless ThreadUpdater.isUpdating and e.detail.threadID is ThreadUpdater.thread.ID ThreadUpdater.outdateCount = 0 setTimeout ThreadUpdater.update, 1000 if ThreadUpdater.seconds > 2 visibility: -> @@ -4170,8 +4171,9 @@ ThreadUpdater = -> true else -> not d.hidden - autoUpdate: -> - if Conf['Auto Update This'] and ThreadUpdater.online + autoUpdate: (e) -> + ThreadUpdater.isUpdating = @checked if e + if ThreadUpdater.isUpdating and ThreadUpdater.online ThreadUpdater.timeoutID = setTimeout ThreadUpdater.timeout, 1000 else clearTimeout ThreadUpdater.timeoutID From 91d6c50604cf320aaf0034d760c441da39c6bc58 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Fri, 26 Apr 2013 02:02:47 +0200 Subject: [PATCH 15/25] Don't save an empty board value when syncing hidden threads with the catalog. --- src/features.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/features.coffee b/src/features.coffee index 1c2c6c46b..d771b1df3 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -1291,6 +1291,9 @@ ThreadHiding = # Was cleaned just now. ThreadHiding.cleanCatalog hiddenThreadsOnCatalog + unless Object.keys(hiddenThreads).length + ThreadHiding.db.delete boardID: g.BOARD.ID + return ThreadHiding.db.set boardID: g.BOARD.ID val: hiddenThreads From d32e9c8ef641f582a36c13d23e89c41050bb5d2d Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Fri, 26 Apr 2013 02:07:57 +0200 Subject: [PATCH 16/25] Debounce $.set on Chrome. Should help #1018. --- lib/$.coffee | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/$.coffee b/lib/$.coffee index 2ea705662..35b077f03 100644 --- a/lib/$.coffee +++ b/lib/$.coffee @@ -232,12 +232,22 @@ $.get = (key, val, cb) -> items = key cb = val chrome.storage.sync.get items, cb -$.set = (key, val) -> - items = if typeof key is 'string' - $.item key, val - else - key - chrome.storage.sync.set items +$.set = do -> + items = {} + + set = $.debounce 500, -> + try + chrome.storage.sync.set items + items = {} + catch err + c.error err + + (key, val) -> + if typeof key is 'string' + items[key] = val + else + $.extend items, key + set() <% } else if (type === 'userjs') { %> do -> # http://www.opera.com/docs/userjs/specs/#scriptstorage From cede04f144f499309ad717d628cca2ce923fed42 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Fri, 26 Apr 2013 02:24:13 +0200 Subject: [PATCH 17/25] Fix $.debounce calling the function twice. --- lib/$.coffee | 23 +++++++++++------------ src/features.coffee | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/$.coffee b/lib/$.coffee index 35b077f03..c0c500f0e 100644 --- a/lib/$.coffee +++ b/lib/$.coffee @@ -147,21 +147,20 @@ $.open = do -> else (URL) -> window.open URL, '_blank' $.debounce = (wait, fn) -> - timeout = null - that = null - args = null - exec = -> + lastCall = 0 + timeout = null + that = null + args = null + exec = -> + lastCall = Date.now() fn.apply that, args - timeout = null -> args = arguments that = this - if timeout - # stop current reset - clearTimeout timeout - else - exec() - + if lastCall < Date.now() - wait + return exec() + # stop current reset + clearTimeout timeout # after wait, let next invocation execute immediately timeout = setTimeout exec, wait $.queueTask = do -> @@ -235,7 +234,7 @@ $.get = (key, val, cb) -> $.set = do -> items = {} - set = $.debounce 500, -> + set = $.debounce $.SECOND, -> try chrome.storage.sync.set items items = {} diff --git a/src/features.coffee b/src/features.coffee index d771b1df3..01dbb24b2 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -3942,9 +3942,9 @@ Unread = Unread.readArray Unread.postsQuotingYou Unread.update() if e - saveLastReadPost: $.debounce 2 * $.SECOND, -> + saveLastReadPost: -> Unread.db.set - boardID: Unread.thread.board.ID + boardID: Unread.thread.board.ID threadID: Unread.thread.ID val: Unread.lastReadPost From 5c79d5bb4de042bb6f3cb94fbf12fbd0688a1d1f Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Fri, 26 Apr 2013 02:29:29 +0200 Subject: [PATCH 18/25] Clean databoards more often. --- src/databoard.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/databoard.coffee b/src/databoard.coffee index cceb63c10..893de9edb 100644 --- a/src/databoard.coffee +++ b/src/databoard.coffee @@ -60,7 +60,7 @@ class DataBoard @deleteIfEmpty {boardID} now = Date.now() - if (@data.lastChecked or 0) < now - 12 * $.HOUR + if (@data.lastChecked or 0) < now - 2 * $.HOUR @data.lastChecked = now for boardID of @data.boards @ajaxClean boardID From 11b51df9752b303d40b3532d948328bce78efce8 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Fri, 26 Apr 2013 02:34:57 +0200 Subject: [PATCH 19/25] Make $.open depend on the build target. --- lib/$.coffee | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/$.coffee b/lib/$.coffee index c0c500f0e..53574559f 100644 --- a/lib/$.coffee +++ b/lib/$.coffee @@ -138,14 +138,13 @@ $.off = (el, events, handler) -> return $.event = (event, detail, root=d) -> root.dispatchEvent new CustomEvent event, {bubbles: true, detail} -$.open = do -> - if GM_openInTab? - (URL) -> - # XXX fix GM opening file://// for protocol-less URLs. - a = $.el 'a', href: URL - GM_openInTab a.href - else - (URL) -> window.open URL, '_blank' +<% if (type === 'userscript') { %> +# XXX fix GM opening file://// for protocol-less URLs. +# https://github.com/greasemonkey/greasemonkey/issues/1719 +$.open = (URL) -> GM_openInTab ($.el 'a', href: URL).href +<% } else { %> +$.open = (URL) -> window.open URL, '_blank' +<% } %> $.debounce = (wait, fn) -> lastCall = 0 timeout = null From 43d97c326a9e184f4ec7ec7d7625e33801aeb208 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Fri, 26 Apr 2013 02:37:24 +0200 Subject: [PATCH 20/25] Shove that down the license. --- LICENSE | 5 ++++- src/features.coffee | 4 ---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/LICENSE b/LICENSE index 78a762845..3056ec7d2 100644 --- a/LICENSE +++ b/LICENSE @@ -23,7 +23,10 @@ OTHER DEALINGS IN THE SOFTWARE. --- -Contains code from the following repositories: +Contains data from external sources: + +audio/beep.wav from http://freesound.org/people/pierrecartoons1979/sounds/90112/ + cc-by-nc-3.0 4chan/4chan-JS (https://github.com/4chan/4chan-JS) license: https://github.com/4chan/4chan-JS/blob/master/LICENSE diff --git a/src/features.coffee b/src/features.coffee index 01dbb24b2..2c95eb4d1 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -4142,10 +4142,6 @@ ThreadUpdater = ThreadUpdater.cb.online() $.add d.body, ThreadUpdater.dialog - ### - http://freesound.org/people/pierrecartoons1979/sounds/90112/ - cc-by-nc-3.0 - ### beep: 'data:audio/wav;base64,<%= grunt.file.read("audio/beep.wav", {encoding: "base64"}) %>' cb: From 2a6399b8bf8aabc0f97f82c79adc75ed4e99904b Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Fri, 26 Apr 2013 02:43:55 +0200 Subject: [PATCH 21/25] Fix the font-family of .fields, it should depend on the style. --- css/photon.css | 3 +++ css/style.css | 2 +- css/tomorrow.css | 3 +++ css/yotsuba-b.css | 3 +++ css/yotsuba.css | 3 +++ 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/css/photon.css b/css/photon.css index cd4d9a702..448d49a86 100644 --- a/css/photon.css +++ b/css/photon.css @@ -3,6 +3,9 @@ background-color: #DDD; border-color: #CCC; } +:root.photon .field { + font-family: arial, helvetica, sans-serif; +} :root.photon .field:focus { border-color: #EA8; } diff --git a/css/style.css b/css/style.css index 1b1bb06f2..1f98a4eb7 100644 --- a/css/style.css +++ b/css/style.css @@ -11,7 +11,7 @@ -moz-box-sizing: border-box; box-sizing: border-box; color: #333; - font: 13px sans-serif; + font-size: 13px; margin: 0; padding: 2px 4px 3px; outline: none; diff --git a/css/tomorrow.css b/css/tomorrow.css index 2d03d4498..cd36d78cb 100644 --- a/css/tomorrow.css +++ b/css/tomorrow.css @@ -3,6 +3,9 @@ background-color: #282A2E; border-color: #111; } +:root.tomorrow .field { + font-family: arial, helvetica, sans-serif; +} :root.tomorrow .field:focus { border-color: #000; } diff --git a/css/yotsuba-b.css b/css/yotsuba-b.css index 20b1214fe..73884a2ac 100644 --- a/css/yotsuba-b.css +++ b/css/yotsuba-b.css @@ -3,6 +3,9 @@ background-color: #D6DAF0; border-color: #B7C5D9; } +:root.yotsuba-b .field { + font-family: arial, helvetica, sans-serif; +} :root.yotsuba-b .field:focus { border-color: #98E; } diff --git a/css/yotsuba.css b/css/yotsuba.css index 2cf8b83ba..bbc9f2478 100644 --- a/css/yotsuba.css +++ b/css/yotsuba.css @@ -3,6 +3,9 @@ background-color: #F0E0D6; border-color: #D9BFB7; } +:root.yotsuba .field { + font-family: arial, helvetica, sans-serif; +} :root.yotsuba .field:focus { border-color: #EA8; } From df33758a813402e115287890838233904fcadc08 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Fri, 26 Apr 2013 03:00:07 +0200 Subject: [PATCH 22/25] Link to the FAQ in case of connection error. --- src/qr.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/qr.coffee b/src/qr.coffee index 915f53e7b..db7f355a2 100644 --- a/src/qr.coffee +++ b/src/qr.coffee @@ -947,7 +947,10 @@ QR = QR.cooldown.auto = false QR.status() QR.error $.el 'span', - innerHTML: 'Connection error. You may have been banned.' + innerHTML: """ + Connection error. You may have been banned. + [FAQ] + """ opts = cred: true form: $.formData postData From 03ec22aa92af474e35bf6153bbb815cd3f2783f4 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Fri, 26 Apr 2013 04:30:52 +0200 Subject: [PATCH 23/25] Fix #1066. --- CHANGELOG.md | 3 +++ lib/$.coffee | 46 ++++++++++++++++++++++++++++++++++++++++++++- src/features.coffee | 21 ++++++++++++++++----- 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1abc7174f..6d2a40b54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +- Minor fixes. +- Chrome only: Due to technical limitations, Filter lists and Custom CSS will not by synchronized across devices anymore. + ## 3.2.0 - *2013-04-23* - The top and bottom original board lists are now optional, disabled by default. diff --git a/lib/$.coffee b/lib/$.coffee index 53574559f..89451e20b 100644 --- a/lib/$.coffee +++ b/lib/$.coffee @@ -220,6 +220,23 @@ $.item = (key, val) -> item[key] = val item <% if (type === 'crx') { %> +$.localKeys = [ + # filters + 'name', + 'uniqueID', + 'tripcode', + 'capcode', + 'email', + 'subject', + 'comment', + 'flag', + 'filename', + 'dimensions', + 'filesize', + 'MD5', + # custom css + 'usercss' +] # https://developer.chrome.com/extensions/storage.html $.delete = (keys) -> chrome.storage.sync.remove keys @@ -229,14 +246,41 @@ $.get = (key, val, cb) -> else items = key cb = val - chrome.storage.sync.get items, cb + + localItems = null + syncItems = null + for key, val of items + if key in $.localKeys + (localItems or= {})[key] = val + else + (syncItems or= {})[key] = val + + items = {} + count = 0 + done = (item) -> + $.extend items, item + cb items unless --count + + if localItems + count++ + chrome.storage.local.get localItems, done + if syncItems + count++ + chrome.storage.sync.get syncItems, done $.set = do -> items = {} + localItems = {} set = $.debounce $.SECOND, -> + for key in $.localKeys + if key of items + (localItems or= {})[key] = items[key] + delete items[key] try + chrome.storage.local.set localItems chrome.storage.sync.set items items = {} + localItems = {} catch err c.error err diff --git a/src/features.coffee b/src/features.coffee index 2c95eb4d1..e468c02ba 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -331,11 +331,10 @@ Settings = $.get 'previousversion', null, (item) -> if previous = item['previousversion'] return if previous is g.VERSION - # Avoid conflicts between sync'd newer versions - # and out of date extension on this device. - prev = previous.match(/\d+/g).map Number - curr = g.VERSION.match(/\d+/g).map Number - + <% if (type === 'crx') { %> + # XXX tmp conversion: move some settings from sync to local + Settings['3.2.1-update'] previous + <% } %> changelog = '<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md' el = $.el 'span', innerHTML: "<%= meta.name %> has been updated to version #{g.VERSION}." @@ -606,6 +605,18 @@ Settings = data.Conf[newKey] = data.Conf[prevKey] if newKey delete data.Conf[prevKey] data + <% if (type === 'crx') { %> + '3.2.1-update': (previous) -> + return unless /^3\.[10]\.|^3\.2\.0$/.test previous + items = {} + for key in $.localKeys + items[key] = null + chrome.storage.sync.get items, (items) -> + chrome.storage.sync.remove $.localKeys + for key, val of items + delete items[key] if val is null + chrome.storage.local.set items + <% } %> filter: (section) -> section.innerHTML = """ From c54e585f44304b4ccc9f1aab1c35316017a821e9 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Fri, 26 Apr 2013 04:35:41 +0200 Subject: [PATCH 24/25] Use font-family instead as suggested by @saxamaphone69. --- css/photon.css | 3 --- css/style.css | 1 + css/tomorrow.css | 3 --- css/yotsuba-b.css | 3 --- css/yotsuba.css | 3 --- 5 files changed, 1 insertion(+), 12 deletions(-) diff --git a/css/photon.css b/css/photon.css index 448d49a86..cd4d9a702 100644 --- a/css/photon.css +++ b/css/photon.css @@ -3,9 +3,6 @@ background-color: #DDD; border-color: #CCC; } -:root.photon .field { - font-family: arial, helvetica, sans-serif; -} :root.photon .field:focus { border-color: #EA8; } diff --git a/css/style.css b/css/style.css index 1f98a4eb7..22063a186 100644 --- a/css/style.css +++ b/css/style.css @@ -11,6 +11,7 @@ -moz-box-sizing: border-box; box-sizing: border-box; color: #333; + font-family: inherit; font-size: 13px; margin: 0; padding: 2px 4px 3px; diff --git a/css/tomorrow.css b/css/tomorrow.css index cd36d78cb..2d03d4498 100644 --- a/css/tomorrow.css +++ b/css/tomorrow.css @@ -3,9 +3,6 @@ background-color: #282A2E; border-color: #111; } -:root.tomorrow .field { - font-family: arial, helvetica, sans-serif; -} :root.tomorrow .field:focus { border-color: #000; } diff --git a/css/yotsuba-b.css b/css/yotsuba-b.css index 73884a2ac..20b1214fe 100644 --- a/css/yotsuba-b.css +++ b/css/yotsuba-b.css @@ -3,9 +3,6 @@ background-color: #D6DAF0; border-color: #B7C5D9; } -:root.yotsuba-b .field { - font-family: arial, helvetica, sans-serif; -} :root.yotsuba-b .field:focus { border-color: #98E; } diff --git a/css/yotsuba.css b/css/yotsuba.css index bbc9f2478..2cf8b83ba 100644 --- a/css/yotsuba.css +++ b/css/yotsuba.css @@ -3,9 +3,6 @@ background-color: #F0E0D6; border-color: #D9BFB7; } -:root.yotsuba .field { - font-family: arial, helvetica, sans-serif; -} :root.yotsuba .field:focus { border-color: #EA8; } From 6530f265443285ad566670ce7d7c3eb84dbc2cec Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Fri, 26 Apr 2013 04:40:07 +0200 Subject: [PATCH 25/25] Release 4chan X v3.2.1. --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d2a40b54..d9825f7ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +### 3.2.1 - *2013-04-26* + - Minor fixes. - Chrome only: Due to technical limitations, Filter lists and Custom CSS will not by synchronized across devices anymore. diff --git a/package.json b/package.json index 9290db6e5..2e17a0c52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "4chan-X", - "version": "3.2.0", + "version": "3.2.1", "description": "Cross-browser extension for productive lurking on 4chan.", "meta": { "name": "4chan X",