diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab5aab80..835dea61f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ +<<<<<<< HEAD ### v2.8.7 *2014-01-19* +======= +**MayhemYDG**: +- Added a `Reset Settings` button in the settings. +- More stability update. +- Stability update. +>>>>>>> v3 **Zixaphir**: - Fix posting. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..3478e5082 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,48 @@ +## Reporting bugs and suggestions + +Reporting bugs: + +1. Make sure both your **browser** and **4chan X** are up to date.
+ Only **Chrome**, **Firefox** and **Opera** are supported.
+ **SRWare Iron**, **Firefox ESR**, **Pale Moon**, **Waterfox**, and other derivatives are not supported, use them at your own risk. +2. Look at the list of [known problems and solutions](https://github.com/MayhemYDG/4chan-x/wiki/FAQ#known-problems). +3. Disable your other extensions & scripts to identify conflicts. +4. If your issue persists, open a [new issue](https://github.com/MayhemYDG/4chan-x/issues) with the following information: + 1. Precise steps to reproduce the problem, with the expected and actual results. + 2. [Console errors](https://github.com/MayhemYDG/4chan-x/wiki/FAQ#console-errors), if any. + 3. 4chan X version, browser variant, browser version, and Greasemonkey version if you are using it. + 4. Your exported settings. If your settings contains sensible information (e.g. personas), edit the text file manually. + +Respect these guidelines: +- Describe the issue clearly, put some effort into it. A one-liner isn't a good enough description. +- If you want to get your suggestion implemented sooner, make it convincing. +- If you want to criticize, make it convincing and constructive. +- Be mature. Act like an idiot and you will be blocked without warning. + +## Development & Contribution + +### Get started + +- Install [node.js](http://nodejs.org/). +- Install [Grunt's CLI](http://gruntjs.com/) with `npm install -g grunt-cli`. +- Clone 4chan X. +- `cd` into it. +- Install/Update 4chan X dependencies with `npm install`. + +### Build + +- Build with `grunt`. +- Continuously build with `grunt watch`. + +### Release + +- Update the version with `grunt patch`, `grunt minor` or `grunt major`. +- Release with `grunt release`. + +Note: this is only used to release new 4chan X versions, and is **not** needed or wanted in pull requests. + +### Contribute + +- Edit the sources. +- If the edits affect regular users, edit the changelog. +- Open a pull request. diff --git a/LICENSE b/LICENSE index d3e5735a6..ad0d54469 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ /* -* appchan x - Version 2.8.7 - 2014-01-21 +* appchan x - Version 2.8.7 - 2014-01-26 * * Licensed under the MIT license. * https://github.com/zixaphir/appchan-x/blob/master/LICENSE diff --git a/builds/4chan-X.meta.js b/builds/4chan-X.meta.js index 3364a720d..d74b232cc 100755 --- a/builds/4chan-X.meta.js +++ b/builds/4chan-X.meta.js @@ -1,7 +1,7 @@ // ==UserScript== // @name 4chan X // @version 1.3.2 -// @minGMVer 1.13 +// @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X // @description Cross-browser userscript for maximum lurking on 4chan. @@ -13,6 +13,7 @@ // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue +// @grant GM_listValues // @grant GM_openInTab // @run-at document-start // @updateURL https://github.com/seaweedchan/4chan-x/raw/stable/builds/4chan-X.meta.js diff --git a/builds/appchan-x.meta.js b/builds/appchan-x.meta.js index 6a207bc0a..41a0de940 100644 --- a/builds/appchan-x.meta.js +++ b/builds/appchan-x.meta.js @@ -1,7 +1,7 @@ // ==UserScript== // @name appchan x // @version 2.8.7 -// @minGMVer 1.13 +// @minGMVer 1.14 // @minFFVer 26 // @namespace zixaphir // @description The most comprehensive 4chan userscript. @@ -13,6 +13,7 @@ // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue +// @grant GM_listValues // @grant GM_openInTab // @run-at document-start // @updateURL https://github.com/zixaphir/appchan-x/raw/stable/builds/appchan-x.meta.js diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js index 6dc71b936..0af2825c6 100644 --- a/builds/appchan-x.user.js +++ b/builds/appchan-x.user.js @@ -2,7 +2,7 @@ // ==UserScript== // @name appchan x // @version 2.8.7 -// @minGMVer 1.13 +// @minGMVer 1.14 // @minFFVer 26 // @namespace zixaphir // @description The most comprehensive 4chan userscript. @@ -14,6 +14,7 @@ // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue +// @grant GM_listValues // @grant GM_openInTab // @run-at document-start // @updateURL https://github.com/zixaphir/appchan-x/raw/stable/builds/appchan-x.meta.js @@ -22,7 +23,7 @@ // ==/UserScript== /* -* appchan x - Version 2.8.7 - 2014-01-21 +* appchan x - Version 2.8.7 - 2014-01-26 * * Licensed under the MIT license. * https://github.com/zixaphir/appchan-x/blob/master/LICENSE @@ -111,8 +112,8 @@ (function() { var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, InfiniScroll, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __slice = [].slice, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -2624,6 +2625,9 @@ return lastModified[url] = r.getResponseHeader('Last-Modified'); }); } + if (/\.json$/.test(url)) { + r.responseType = 'json'; + } $.extend(r, options); $.extend(r.upload, upCallbacks); r.send(form); @@ -2712,12 +2716,16 @@ return d.evaluate(path, root, null, 7, null); }; - $.addClass = function(el, className) { - return el.classList.add(className); + $.addClass = function() { + var className, el, _ref; + el = arguments[0], className = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + return (_ref = el.classList).add.apply(_ref, className); }; - $.rmClass = function(el, className) { - return el.classList.remove(className); + $.rmClass = function() { + var className, el, _ref; + el = arguments[0], className = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + return (_ref = el.classList).remove.apply(_ref, className); }; $.toggleClass = function(el, className) { @@ -2742,12 +2750,7 @@ })(); $.rmAll = function(root) { - var node, _i, _len, _ref; - _ref = __slice.call(root.childNodes); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - node = _ref[_i]; - root.removeChild(node); - } + return root.textContent = ''; }; $.tn = function(s) { @@ -2980,6 +2983,13 @@ }; })(); + $.clear = function(cb) { + $["delete"](GM_listValues().map(function(key) { + return key.replace(g.NAMESPACE, ''); + })); + return typeof cb === "function" ? cb() : void 0; + }; + $.remove = function(arr, value) { var i; i = arr.indexOf(value); @@ -3622,7 +3632,7 @@ } board = _this.data.boards[boardID]; threads = {}; - _ref = JSON.parse(e.target.response); + _ref = e.target.response; for (_i = 0, _len = _ref.length; _i < _len; _i++) { page = _ref[_i]; _ref1 = page.threads; @@ -4190,18 +4200,16 @@ toggleHideBarOnScroll: function(e) { var hide; hide = this.checked; - $.set('Header auto-hide on scroll', hide); + $.cb.checked.call(this); return Header.setHideBarOnScroll(hide); }, hideBarOnScroll: function() { var offsetY; offsetY = window.pageYOffset; if (offsetY > (Header.previousOffset || 0)) { - $.addClass(Header.bar, 'autohide'); - $.addClass(Header.bar, 'scroll'); + $.addClass(Header.bar, 'autohide', 'scroll'); } else { - $.rmClass(Header.bar, 'autohide'); - $.rmClass(Header.bar, 'scroll'); + $.rmClass(Header.bar, 'autohide', 'scroll'); } return Header.previousOffset = offsetY; }, @@ -4249,14 +4257,38 @@ return Header.scrollTo(post); }, scrollTo: function(root, down, needed) { - var x; + var height, x; if (down) { x = Header.getBottomOf(root); + if (Conf['Header auto-hide on scroll'] && Conf['Bottom header']) { + height = Header.bar.getBoundingClientRect().height; + if (x <= 0) { + if (!Header.isHidden()) { + x += height; + } + } else { + if (Header.isHidden()) { + x -= height; + } + } + } if (!(needed && x >= 0)) { return window.scrollBy(0, -x); } } else { x = Header.getTopOf(root); + if (Conf['Header auto-hide on scroll'] && !Conf['Bottom header']) { + height = Header.bar.getBoundingClientRect().height; + if (x >= 0) { + if (!Header.isHidden()) { + x += height; + } + } else { + if (Header.isHidden()) { + x -= height; + } + } + } if (!(needed && x >= 0)) { return window.scrollBy(0, x); } @@ -4284,6 +4316,15 @@ } return bottom; }, + isHidden: function() { + var top; + top = Header.bar.getBoundingClientRect().top; + if (Conf['Bottom header']) { + return top === doc.clientHeight; + } else { + return top < 0; + } + }, addShortcut: function(el, icon) { $.addClass(el, 'shortcut'); return $.add(Header[icon ? 'icons' : 'stats'], el); @@ -4731,7 +4772,7 @@ Index.board = "" + g.BOARD; try { if (req.status === 200) { - Index.parse(JSON.parse(req.response), pageNum); + Index.parse(req.response, pageNum); } else if (req.status === 304 && (pageNum != null)) { Index.pageNav(pageNum); } @@ -5009,6 +5050,9 @@ pageNum = Index.getCurrentPage(); } } else { + if (!Index.searchInput.dataset.searching) { + return; + } pageNum = Index.pageBeforeSearch; delete Index.pageBeforeSearch; Index.searchInput.removeAttribute('data-searching'); @@ -5378,6 +5422,7 @@ return $.cache(url, function() { return Get.archivedPost(this, boardID, postID, root, context); }, { + responseType: 'json', withCredentials: url.archive.withCredentials }); } @@ -5418,7 +5463,7 @@ } return; } - posts = JSON.parse(req.response).posts; + posts = req.response.posts; Build.spoilerRange[boardID] = posts[0].custom_spoiler; for (_i = 0, _len = posts.length; _i < _len; _i++) { post = posts[_i]; @@ -5454,7 +5499,7 @@ Get.insert(post, root, context); return; } - data = JSON.parse(req.response); + data = req.response; if (data.error) { $.addClass(root, 'warning'); root.textContent = data.error; @@ -5559,6 +5604,7 @@ this.type = type; this.rmEntry = __bind(this.rmEntry, this); this.addEntry = __bind(this.addEntry, this); + this.onFocus = __bind(this.onFocus, this); this.keybinds = __bind(this.keybinds, this); this.close = __bind(this.close, this); $.on(d, 'AddMenuEntry', this.addEntry); @@ -5723,6 +5769,11 @@ return e.stopPropagation(); }; + Menu.prototype.onFocus = function(e) { + e.stopPropagation(); + return this.focus(e.target); + }; + Menu.prototype.focus = function(entry) { var bottom, cHeight, cWidth, eRect, focused, left, right, sRect, style, submenu, top, _i, _len, _ref, _ref1, _ref2; while (focused = $.x('parent::*/child::*[contains(@class,"focused")]', entry)) { @@ -5774,10 +5825,7 @@ var el, subEntries, subEntry, _i, _len; el = entry.el, subEntries = entry.subEntries; $.addClass(el, 'entry'); - $.on(el, 'focus mouseover', (function(e) { - e.stopPropagation(); - return this.focus(el); - }).bind(this)); + $.on(el, 'focus mouseover', this.onFocus); el.style.order = entry.order || 100; if (!subEntries) { return; @@ -6684,7 +6732,7 @@ return; } threads = {}; - _ref = JSON.parse(this.response); + _ref = this.response; for (_i = 0, _len = _ref.length; _i < _len; _i++) { page = _ref[_i]; _ref1 = page.threads; @@ -7888,7 +7936,7 @@ switch (response.status) { case 200: case 304: - text = "" + (service.text(JSON.parse(response.responseText))); + text = "" + (service.text(response.response)); if (Conf['Embedding']) { embed.dataset.title = text; } @@ -7984,7 +8032,7 @@ if (status !== 200 && status !== 304) { return div.innerHTML = "ERROR " + status; } - files = JSON.parse(this.response).files; + files = this.response.files; _ref = ['video/mp4', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'image/svg', 'audio/mpeg']; for (_i = 0, _len = _ref.length; _i < _len; _i++) { type = _ref[_i]; @@ -8889,7 +8937,7 @@ QR.cooldown.set({ delay: 2 }); - } else if (err.textContent && (m = err.textContent.match(/wait\s(\d+)\ssecond/i))) { + } else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i))) { QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true; QR.cooldown.set({ delay: m[1] @@ -10028,12 +10076,13 @@ } return $.ajax("//api.4chan.org/" + post.board + "/res/" + post.thread + ".json", { onload: function() { - var i, postObj; + var i, postObj, posts; if (this.status !== 200) { return; } i = 0; - while (postObj = JSON.parse(this.response).posts[i++]) { + posts = this.response.posts; + while (postObj = posts[i++]) { if (postObj.no === post.ID) { break; } @@ -10310,7 +10359,7 @@ if (this.status !== 200) { return; } - _ref = JSON.parse(this.response).posts; + _ref = this.response.posts; for (_i = 0, _len = _ref.length; _i < _len; _i++) { postObj = _ref[_i]; if (postObj.no === post.ID) { @@ -10438,7 +10487,7 @@ if (this.status !== 200) { return; } - _ref = JSON.parse(this.response).posts; + _ref = this.response.posts; for (_i = 0, _len = _ref.length; _i < _len; _i++) { postObj = _ref[_i]; if (postObj.no === post.ID) { @@ -11061,21 +11110,21 @@ }); }, onThreadsLoad: function() { - var page, pages, thread, _i, _j, _len, _len1, _ref; + var page, thread, _i, _j, _len, _len1, _ref, _ref1; if (!(Conf["Page Count in Stats"] && this.status === 200)) { return; } - pages = JSON.parse(this.response); - for (_i = 0, _len = pages.length; _i < _len; _i++) { - page = pages[_i]; - _ref = page.threads; - for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { - thread = _ref[_j]; + _ref = this.response; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + page = _ref[_i]; + _ref1 = page.threads; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + thread = _ref1[_j]; if (!(thread.no === ThreadStats.thread.ID)) { continue; } ThreadStats.pageCountEl.textContent = page.page; - (page.page === pages.length - 1 ? $.addClass : $.rmClass)(ThreadStats.pageCountEl, 'warning'); + (page.page === this.response.length - 1 ? $.addClass : $.rmClass)(ThreadStats.pageCountEl, 'warning'); return; } } @@ -11279,7 +11328,7 @@ switch (req.status) { case 200: g.DEAD = false; - ThreadUpdater.parse(JSON.parse(req.response).posts); + ThreadUpdater.parse(req.response.posts); ThreadUpdater.setInterval(); break; case 404: @@ -12334,8 +12383,8 @@ } }, { name: "4plebs", - boards: ["hr", "o", "pol", "s4s", "tg", "tv", "x"], - files: ["hr", "o", "pol", "s4s", "tg", "tv", "x"], + boards: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"], + files: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"], data: { domain: "archive.4plebs.org", http: true, @@ -12352,6 +12401,16 @@ https: true, software: "foolfuuka" } + }, { + name: "Love is Over", + boards: ["d", "i"], + files: ["d", "i"], + data: { + domain: "loveisover.me", + http: true, + https: true, + software: "foolfuuka" + } }, { name: "Install Gentoo", boards: ["diy", "g", "sci"], @@ -12367,7 +12426,7 @@ boards: ["cgl", "g", "mu", "w"], files: ["cgl", "g", "mu", "w"], data: { - domain: "rbt.asia", + domain: "archive.rebeccablacktech.com", http: true, https: true, software: "fuuka" @@ -12387,10 +12446,37 @@ files: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"], data: { domain: "fuuka.warosu.org", - http: true, https: true, software: "fuuka" } + }, { + name: "fgts", + boards: ["soc"], + files: ["soc"], + data: { + domain: "fgts.eu", + http: true, + https: true, + software: "foolfuuka" + } + }, { + name: "maware", + boards: ["t"], + files: ["t"], + data: { + domain: "archive.mawa.re", + http: true, + software: "foolfuuka" + } + }, { + name: "installgentoo.com", + boards: ["g", "t"], + files: ["g", "t"], + data: { + domain: "chan.installgentoo.com", + http: true, + software: "foolfuuka" + } }, { name: "Foolz Beta", boards: ["a", "co", "d", "gd", "h", "jp", "m", "mlp", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], @@ -12402,16 +12488,6 @@ withCredentials: true, software: "foolfuuka" } - }, { - name: "Love is Over", - boards: ["d", "i"], - files: ["d", "i"], - data: { - domain: "loveisover.me", - http: true, - https: true, - software: "foolfuuka" - } } ], to: function(dest, data) { @@ -14503,18 +14579,21 @@ return a.textContent = ExpandThread.text('+', postsCount, filesCount); }, parse: function(req, thread, a) { - var data, filesCount, post, postData, posts, postsCount, postsRoot, root, _i, _len, _ref; + var filesCount, post, postData, posts, postsCount, postsRoot, root, _i, _len, _ref, _ref1; if ((_ref = req.status) !== 200 && _ref !== 304) { a.textContent = "Error " + req.statusText + " (" + req.status + ")"; return; } - data = JSON.parse(req.response).posts; - Build.spoilerRange[thread.board] = data.shift().custom_spoiler; + Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler; posts = []; postsRoot = []; filesCount = 0; - for (_i = 0, _len = data.length; _i < _len; _i++) { - postData = data[_i]; + _ref1 = req.response.posts; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + postData = _ref1[_i]; + if (postData.no === thread.ID) { + continue; + } if (post = thread.posts[postData.no]) { if ('file' in post) { filesCount++; @@ -15643,8 +15722,6 @@ } }, clean: function() { - var posts, threads; - posts = g.posts, threads = g.threads; g.threads.forEach(function(thread) { return thread.collect(); }); @@ -15746,7 +15823,6 @@ }, updateBoard: function(boardID) { var fullBoardList, onload, req; - req = null; fullBoardList = $('#full-board-list', Header.boardList); $.rmClass($('.current', fullBoardList), 'current'); $.addClass($("a[href*='/" + boardID + "/']", fullBoardList), 'current'); @@ -15762,7 +15838,7 @@ return; } try { - _ref = JSON.parse(req.response).boards; + _ref = req.response.boards; for (_i = 0, _len = _ref.length; _i < _len; _i++) { aboard = _ref[_i]; if (!(aboard.board === boardID)) { @@ -15905,7 +15981,7 @@ } Navigate.title(); try { - return Navigate.parse(JSON.parse(req.response).posts); + return Navigate.parse(req.response.posts); } catch (_error) { err = _error; console.error('Navigate failure:'); @@ -15983,59 +16059,27 @@ Settings = { init: function() { - var addSection, check, el, key, settings, value, _ref; + var check, el, settings, + _this = this; el = $.el('a', { className: 'settings-link', href: 'javascript:;', textContent: 'Settings' }); - $.on(el, 'click', Settings.open); + $.on(el, 'click', this.open); $.event('AddMenuEntry', { type: 'header', el: el, order: 1 }); - $.get('previousversion', null, function(item) { - var changelog, curr, prev, previous; - if (previous = item['previousversion']) { - if (previous === g.VERSION) { - return; - } - prev = previous.match(/\d+/g).map(Number); - curr = g.VERSION.match(/\d+/g).map(Number); - if (!(prev[0] <= curr[0] && prev[1] <= curr[1] && prev[2] <= curr[2])) { - return; - } - changelog = 'https://github.com/zixaphir/appchan-x/blob/master/CHANGELOG.md'; - el = $.el('span', { - innerHTML: "appchan x has been updated to version " + g.VERSION + "." - }); - if (Conf['Show Updated Notifications']) { - new Notice('info', el, 30); - } - } else { - $.on(d, '4chanXInitFinished', Settings.open); - } - return $.set('previousversion', g.VERSION); - }); - addSection = Settings.addSection; - _ref = { - 'style': 'Style', - 'themes': 'Themes', - 'mascots': 'Mascots', - 'main': 'Script', - 'filter': 'Filter', - 'sauce': 'Sauce', - 'advanced': 'Advanced', - 'keybinds': 'Keybinds' - }; - for (key in _ref) { - value = _ref[key]; - addSection(value, Settings[key]); - } - $.on(d, 'AddSettingsSection', Settings.addSection); + this.addSection('Main', this.main); + this.addSection('Filter', this.filter); + this.addSection('Sauce', this.sauce); + this.addSection('Advanced', this.advanced); + this.addSection('Keybinds', this.keybinds); + $.on(d, 'AddSettingsSection', this.addSection); $.on(d, 'OpenSettings', function(e) { - return Settings.open(e.detail); + return _this.open(e.detail); }); settings = JSON.parse(localStorage.getItem('4chan-settings')) || {}; if (!settings.disableAll) { @@ -16071,13 +16115,14 @@ Settings.dialog = dialog = $.el('div', { id: 'appchanx-settings', "class": 'dialog', - innerHTML: "
" + innerHTML: "
" }); Settings.overlay = overlay = $.el('div', { id: 'overlay' }); $.on($('.export', dialog), 'click', Settings["export"]); $.on($('.import', dialog), 'click', Settings["import"]); + $.on($('.reset', dialog), 'click', Settings.reset); $.on($('input', dialog), 'change', Settings.onImport); links = []; _ref = Settings.sections; @@ -16137,7 +16182,7 @@ return $.event('OpenSettings', null, section); }, main: function(section) { - var arr, button, description, div, fs, hiddenNum, input, inputs, items, key, obj, _ref; + var arr, button, description, div, fs, input, inputs, items, key, obj, _ref; items = {}; inputs = {}; _ref = Config.main; @@ -16173,45 +16218,34 @@ innerHTML: ": Clear manually-hidden threads and posts on all boards. Reload the page to apply." }); button = $('button', div); - hiddenNum = 0; - $.get('hiddenThreads', { - boards: {} - }, function(item) { - var ID, board, thread, _ref1; - _ref1 = item.hiddenThreads.boards; + $.get({ + hiddenThreads: {}, + hiddenPosts: {} + }, function(_arg) { + var ID, board, hiddenNum, hiddenPosts, hiddenThreads, thread, _ref1, _ref2; + hiddenThreads = _arg.hiddenThreads, hiddenPosts = _arg.hiddenPosts; + hiddenNum = 0; + _ref1 = hiddenThreads.boards; for (ID in _ref1) { board = _ref1[ID]; - for (ID in board) { - thread = board[ID]; - hiddenNum++; - } + hiddenNum += Object.keys(board).length; } - return button.textContent = "Hidden: " + hiddenNum; - }); - $.get('hiddenPosts', { - boards: {} - }, function(item) { - var ID, board, post, thread, _ref1; - _ref1 = item.hiddenPosts.boards; - for (ID in _ref1) { - board = _ref1[ID]; + _ref2 = hiddenPosts.boards; + for (ID in _ref2) { + board = _ref2[ID]; for (ID in board) { thread = board[ID]; - for (ID in thread) { - post = thread[ID]; - hiddenNum++; - } + hiddenNum += Object.keys(thread).length; } } return button.textContent = "Hidden: " + hiddenNum; }); $.on(button, 'click', function() { this.textContent = 'Hidden: 0'; - return $.get('hiddenThreads', { - boards: {} - }, function(item) { - var boardID; - for (boardID in item.hiddenThreads.boards) { + return $.get('hiddenThreads', {}, function(_arg) { + var boardID, hiddenThreads; + hiddenThreads = _arg.hiddenThreads; + for (boardID in hiddenThreads.boards) { localStorage.removeItem("4chan-hide-t-" + boardID); } return $["delete"](['hiddenThreads', 'hiddenPosts']); @@ -16219,41 +16253,29 @@ }); return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div); }, - "export": function(now, data) { - var a, db, span, _i, _len, _ref; - if (typeof now !== 'number') { - now = Date.now(); - data = { + "export": function() { + return $.get(Conf, function(Conf) { + delete Conf['archives']; + return Settings.downloadExport({ version: g.VERSION, - date: now - }; - _ref = DataBoard.keys; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - db = _ref[_i]; - Conf[db] = { - boards: {} - }; - } - $.get(Conf, function(Conf) { - delete Conf['archives']; - data.Conf = Conf; - return Settings["export"](now, data); + date: Date.now(), + Conf: Conf }); - return; - } - a = $.el('a', { - className: 'warning', - textContent: 'Save me!', - download: "appchan x v" + g.VERSION + "-" + now + ".json", - href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))))), - target: '_blank' }); - span = $('.imp-exp-result', Settings.dialog); - $.rmAll(span); - return $.add(span, a); + }, + downloadExport: function(data) { + var a, p; + a = $.el('a', { + download: "appchan x v" + g.VERSION + "-" + data.date + ".json", + href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))))) + }); + p = $('.imp-exp-result', Settings.dialog); + $.rmAll(p); + $.add(p, a); + return a.click(); }, "import": function() { - return this.nextElementSibling.click(); + return $('input', this.parentNode).click(); }, onImport: function() { var file, output, reader; @@ -16267,10 +16289,9 @@ } reader = new FileReader(); reader.onload = function(e) { - var data, err; + var err; try { - data = JSON.parse(e.target.result); - Settings.loadSettings(data); + Settings.loadSettings(JSON.parse(e.target.result)); if (confirm('Import successful. Reload now?')) { return window.location.reload(); } @@ -16291,16 +16312,14 @@ } return $.set(data.Conf); }, - convertSettings: function(data, map) { - var newKey, prevKey; - for (prevKey in map) { - newKey = map[prevKey]; - if (newKey) { - data.Conf[newKey] = data.Conf[prevKey]; - } - delete data.Conf[prevKey]; + reset: function() { + if (confirm('Your current settings will be entirely wiped, are you sure?')) { + return $.clear(function() { + if (confirm('Reset successful. Reload now?')) { + return window.location.reload(); + } + }); } - return data; }, filter: function(section) { var select; @@ -17233,7 +17252,7 @@ return $.ready(Main.initReady); }, initReady: function() { - var GMver, err, href, i, passLink, styleSelector, v, _i, _len, _ref, _ref1; + var GMver, err, href, i, passLink, styleSelector, test, v, _i, _len, _ref, _ref1; if ((_ref = d.title) === '4chan - Temporarily Offline' || _ref === '4chan - 404 Not Found') { if (Conf['404 Redirect'] && g.VIEW === 'thread') { href = Redirect.to('thread', { @@ -17260,14 +17279,19 @@ } else { $.event('4chanXInitFinished'); } + test = $.el('span'); + test.classList.add('a', 'b'); + if (test.className !== 'a b') { + new Notice('warning', "Your version of Firefox is outdated (v26 minimum) and appchan x may not operate correctly.", 30); + } GMver = GM_info.version.split('.'); - _ref1 = "1.13".split('.'); + _ref1 = "1.14".split('.'); for (i = _i = 0, _len = _ref1.length; _i < _len; i = ++_i) { v = _ref1[i]; if (v === GMver[i]) { continue; } - (v < GMver[i]) || new Notice('warning', "Your version of Greasemonkey is outdated (v" + GM_info.version + " instead of v1.13 minimum) and appchan x may not operate correctly.", 30); + (v < GMver[i]) || new Notice('warning', "Your version of Greasemonkey is outdated (v" + GM_info.version + " instead of v1.14 minimum) and appchan x may not operate correctly.", 30); break; } try { @@ -17308,10 +17332,27 @@ Main.handleErrors(errors); } Main.callbackNodes(Thread, threads); - return Main.callbackNodesDB(Post, posts, function() { + Main.callbackNodesDB(Post, posts, function() { return $.event('4chanXInitFinished'); }); } + return $.get('previousversion', null, function(_arg) { + var changelog, el, previousversion; + previousversion = _arg.previousversion; + if (previousversion === g.VERSION) { + return; + } + if (previousversion) { + changelog = 'https://github.com/zixaphir/appchan-x/blob/master/CHANGELOG.md'; + el = $.el('span', { + innerHTML: "appchan x has been updated to version " + g.VERSION + "." + }); + new Notice('info', el, 15); + } else { + Settings.open(); + } + return $.set('previousversion', g.VERSION); + }); }, callbackNodes: function(klass, nodes) { var cb, i, node; diff --git a/builds/crx/script.js b/builds/crx/script.js index 21fd01f39..dacf9bd16 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript /* -* appchan x - Version 2.8.7 - 2014-01-21 +* appchan x - Version 2.8.7 - 2014-01-26 * * Licensed under the MIT license. * https://github.com/zixaphir/appchan-x/blob/master/LICENSE @@ -89,8 +89,8 @@ (function() { var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, InfiniScroll, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __slice = [].slice, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -2602,6 +2602,9 @@ return lastModified[url] = r.getResponseHeader('Last-Modified'); }); } + if (/\.json$/.test(url)) { + r.responseType = 'json'; + } $.extend(r, options); $.extend(r.upload, upCallbacks); r.send(form); @@ -2690,12 +2693,16 @@ return d.evaluate(path, root, null, 7, null); }; - $.addClass = function(el, className) { - return el.classList.add(className); + $.addClass = function() { + var className, el, _ref; + el = arguments[0], className = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + return (_ref = el.classList).add.apply(_ref, className); }; - $.rmClass = function(el, className) { - return el.classList.remove(className); + $.rmClass = function() { + var className, el, _ref; + el = arguments[0], className = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + return (_ref = el.classList).remove.apply(_ref, className); }; $.toggleClass = function(el, className) { @@ -2720,12 +2727,7 @@ })(); $.rmAll = function(root) { - var node, _i, _len, _ref; - _ref = __slice.call(root.childNodes); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - node = _ref[_i]; - root.removeChild(node); - } + return root.textContent = ''; }; $.tn = function(s) { @@ -2952,24 +2954,48 @@ }; $.set = (function() { - var items, localItems, set; - items = {}; - localItems = {}; - set = $.debounce($.SECOND, function() { + var items, setAll, setArea, timeout; + items = { + sync: {}, + local: {} + }; + timeout = {}; + setArea = function(area) { + var data; + data = items[area]; + if (!Object.keys(data).length || timeout[area]) { + return; + } + items[area] = {}; + return chrome.storage[area].set(data, function() { + var key, val; + if (chrome.runtime.lastError) { + c.error(chrome.runtime.lastError.message); + for (key in data) { + val = data[key]; + if (!(key in items[area])) { + items[area][key] = val; + } + } + timeout[area] = setTimeout(setArea, $.MINUTE, area); + return; + } + return delete timeout[area]; + }); + }; + setAll = $.debounce($.SECOND, function() { var err, key, _i, _len, _ref; _ref = $.localKeys; for (_i = 0, _len = _ref.length; _i < _len; _i++) { key = _ref[_i]; - if (key in items) { - (localItems || (localItems = {}))[key] = items[key]; - delete items[key]; + if (key in items.sync) { + items.local[key] = items.sync[key]; + delete items.sync[key]; } } try { - chrome.storage.local.set(localItems); - chrome.storage.sync.set(items); - items = {}; - return localItems = {}; + setArea('local'); + return setArea('sync'); } catch (_error) { err = _error; return c.error(err.stack); @@ -2977,14 +3003,30 @@ }); return function(key, val) { if (typeof key === 'string') { - items[key] = val; + items.sync[key] = val; } else { - $.extend(items, key); + $.extend(items.sync, key); } - return set(); + return setAll(); }; })(); + $.clear = function(cb) { + var count, done; + count = 2; + done = function() { + if (chrome.runtime.lastError) { + c.error(chrome.runtime.lastError.message); + return; + } + if (!--count) { + return typeof cb === "function" ? cb() : void 0; + } + }; + chrome.storage.local.clear(done); + return chrome.storage.sync.clear(done); + }; + $.remove = function(arr, value) { var i; i = arr.indexOf(value); @@ -3628,7 +3670,7 @@ } board = _this.data.boards[boardID]; threads = {}; - _ref = JSON.parse(e.target.response); + _ref = e.target.response; for (_i = 0, _len = _ref.length; _i < _len; _i++) { page = _ref[_i]; _ref1 = page.threads; @@ -4200,18 +4242,16 @@ toggleHideBarOnScroll: function(e) { var hide; hide = this.checked; - $.set('Header auto-hide on scroll', hide); + $.cb.checked.call(this); return Header.setHideBarOnScroll(hide); }, hideBarOnScroll: function() { var offsetY; offsetY = window.pageYOffset; if (offsetY > (Header.previousOffset || 0)) { - $.addClass(Header.bar, 'autohide'); - $.addClass(Header.bar, 'scroll'); + $.addClass(Header.bar, 'autohide', 'scroll'); } else { - $.rmClass(Header.bar, 'autohide'); - $.rmClass(Header.bar, 'scroll'); + $.rmClass(Header.bar, 'autohide', 'scroll'); } return Header.previousOffset = offsetY; }, @@ -4259,14 +4299,38 @@ return Header.scrollTo(post); }, scrollTo: function(root, down, needed) { - var x; + var height, x; if (down) { x = Header.getBottomOf(root); + if (Conf['Header auto-hide on scroll'] && Conf['Bottom header']) { + height = Header.bar.getBoundingClientRect().height; + if (x <= 0) { + if (!Header.isHidden()) { + x += height; + } + } else { + if (Header.isHidden()) { + x -= height; + } + } + } if (!(needed && x >= 0)) { return window.scrollBy(0, -x); } } else { x = Header.getTopOf(root); + if (Conf['Header auto-hide on scroll'] && !Conf['Bottom header']) { + height = Header.bar.getBoundingClientRect().height; + if (x >= 0) { + if (!Header.isHidden()) { + x += height; + } + } else { + if (Header.isHidden()) { + x -= height; + } + } + } if (!(needed && x >= 0)) { return window.scrollBy(0, x); } @@ -4294,6 +4358,15 @@ } return bottom; }, + isHidden: function() { + var top; + top = Header.bar.getBoundingClientRect().top; + if (Conf['Bottom header']) { + return top === doc.clientHeight; + } else { + return top < 0; + } + }, addShortcut: function(el, icon) { $.addClass(el, 'shortcut'); return $.add(Header[icon ? 'icons' : 'stats'], el); @@ -4741,7 +4814,7 @@ Index.board = "" + g.BOARD; try { if (req.status === 200) { - Index.parse(JSON.parse(req.response), pageNum); + Index.parse(req.response, pageNum); } else if (req.status === 304 && (pageNum != null)) { Index.pageNav(pageNum); } @@ -5019,6 +5092,9 @@ pageNum = Index.getCurrentPage(); } } else { + if (!Index.searchInput.dataset.searching) { + return; + } pageNum = Index.pageBeforeSearch; delete Index.pageBeforeSearch; delete Index.searchInput.dataset.searching; @@ -5388,6 +5464,7 @@ return $.cache(url, function() { return Get.archivedPost(this, boardID, postID, root, context); }, { + responseType: 'json', withCredentials: url.archive.withCredentials }); } @@ -5428,7 +5505,7 @@ } return; } - posts = JSON.parse(req.response).posts; + posts = req.response.posts; Build.spoilerRange[boardID] = posts[0].custom_spoiler; for (_i = 0, _len = posts.length; _i < _len; _i++) { post = posts[_i]; @@ -5464,7 +5541,7 @@ Get.insert(post, root, context); return; } - data = JSON.parse(req.response); + data = req.response; if (data.error) { $.addClass(root, 'warning'); root.textContent = data.error; @@ -5569,6 +5646,7 @@ this.type = type; this.rmEntry = __bind(this.rmEntry, this); this.addEntry = __bind(this.addEntry, this); + this.onFocus = __bind(this.onFocus, this); this.keybinds = __bind(this.keybinds, this); this.close = __bind(this.close, this); $.on(d, 'AddMenuEntry', this.addEntry); @@ -5733,6 +5811,11 @@ return e.stopPropagation(); }; + Menu.prototype.onFocus = function(e) { + e.stopPropagation(); + return this.focus(e.target); + }; + Menu.prototype.focus = function(entry) { var bottom, cHeight, cWidth, eRect, focused, left, right, sRect, style, submenu, top, _i, _len, _ref, _ref1, _ref2; while (focused = $.x('parent::*/child::*[contains(@class,"focused")]', entry)) { @@ -5784,10 +5867,7 @@ var el, subEntries, subEntry, _i, _len; el = entry.el, subEntries = entry.subEntries; $.addClass(el, 'entry'); - $.on(el, 'focus mouseover', (function(e) { - e.stopPropagation(); - return this.focus(el); - }).bind(this)); + $.on(el, 'focus mouseover', this.onFocus); el.style.order = entry.order || 100; if (!subEntries) { return; @@ -6687,7 +6767,7 @@ return; } threads = {}; - _ref = JSON.parse(this.response); + _ref = this.response; for (_i = 0, _len = _ref.length; _i < _len; _i++) { page = _ref[_i]; _ref1 = page.threads; @@ -7891,7 +7971,7 @@ switch (response.status) { case 200: case 304: - text = "" + (service.text(JSON.parse(response.responseText))); + text = "" + (service.text(response.response)); if (Conf['Embedding']) { embed.dataset.title = text; } @@ -7987,7 +8067,7 @@ if (status !== 200 && status !== 304) { return div.innerHTML = "ERROR " + status; } - files = JSON.parse(this.response).files; + files = this.response.files; _ref = ['video/mp4', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'image/svg', 'audio/mpeg']; for (_i = 0, _len = _ref.length; _i < _len; _i++) { type = _ref[_i]; @@ -8880,7 +8960,7 @@ QR.cooldown.set({ delay: 2 }); - } else if (err.textContent && (m = err.textContent.match(/wait\s(\d+)\ssecond/i))) { + } else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i))) { QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true; QR.cooldown.set({ delay: m[1] @@ -10011,12 +10091,13 @@ } return $.ajax("//api.4chan.org/" + post.board + "/res/" + post.thread + ".json", { onload: function() { - var i, postObj; + var i, postObj, posts; if (this.status !== 200) { return; } i = 0; - while (postObj = JSON.parse(this.response).posts[i++]) { + posts = this.response.posts; + while (postObj = posts[i++]) { if (postObj.no === post.ID) { break; } @@ -10287,27 +10368,16 @@ } } timeoutID = setTimeout(ImageExpand.expand, 10000, post); - return $.ajax("//a.4cdn.org/" + post.board + "/res/" + post.thread + ".json", { - onload: function() { - var postObj, _i, _len, _ref; - if (this.status !== 200) { + return $.ajax(this.src, { + onloadend: function() { + if (this.status !== 404) { return; } - _ref = JSON.parse(this.response).posts; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - postObj = _ref[_i]; - if (postObj.no === post.ID) { - break; - } - } - if (postObj.no !== post.ID) { - clearTimeout(timeoutID); - return post.kill(); - } else if (postObj.filedeleted) { - clearTimeout(timeoutID); - return post.kill(true); - } + clearTimeout(timeoutID); + return post.kill(true); } + }, { + type: 'head' }); }, menu: { @@ -10415,27 +10485,16 @@ timeoutID = setTimeout((function() { return _this.src = post.file.URL + '?' + Date.now(); }), 3000); - return $.ajax("//a.4cdn.org/" + post.board + "/res/" + post.thread + ".json", { - onload: function() { - var postObj, _i, _len, _ref; - if (this.status !== 200) { + return $.ajax(this.src, { + onloadend: function() { + if (this.status !== 404) { return; } - _ref = JSON.parse(this.response).posts; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - postObj = _ref[_i]; - if (postObj.no === post.ID) { - break; - } - } - if (postObj.no !== post.ID) { - clearTimeout(timeoutID); - return post.kill(); - } else if (postObj.filedeleted) { - clearTimeout(timeoutID); - return post.kill(true); - } + clearTimeout(timeoutID); + return post.kill(true); } + }, { + type: 'head' }); } }; @@ -11044,21 +11103,21 @@ }); }, onThreadsLoad: function() { - var page, pages, thread, _i, _j, _len, _len1, _ref; + var page, thread, _i, _j, _len, _len1, _ref, _ref1; if (!(Conf["Page Count in Stats"] && this.status === 200)) { return; } - pages = JSON.parse(this.response); - for (_i = 0, _len = pages.length; _i < _len; _i++) { - page = pages[_i]; - _ref = page.threads; - for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { - thread = _ref[_j]; + _ref = this.response; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + page = _ref[_i]; + _ref1 = page.threads; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + thread = _ref1[_j]; if (!(thread.no === ThreadStats.thread.ID)) { continue; } ThreadStats.pageCountEl.textContent = page.page; - (page.page === pages.length - 1 ? $.addClass : $.rmClass)(ThreadStats.pageCountEl, 'warning'); + (page.page === this.response.length - 1 ? $.addClass : $.rmClass)(ThreadStats.pageCountEl, 'warning'); return; } } @@ -11262,7 +11321,7 @@ switch (req.status) { case 200: g.DEAD = false; - ThreadUpdater.parse(JSON.parse(req.response).posts); + ThreadUpdater.parse(req.response.posts); ThreadUpdater.setInterval(); break; case 404: @@ -12323,8 +12382,8 @@ } }, { name: "4plebs", - boards: ["hr", "o", "pol", "s4s", "tg", "tv", "x"], - files: ["hr", "o", "pol", "s4s", "tg", "tv", "x"], + boards: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"], + files: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"], data: { domain: "archive.4plebs.org", http: true, @@ -12341,6 +12400,16 @@ https: true, software: "foolfuuka" } + }, { + name: "Love is Over", + boards: ["d", "i"], + files: ["d", "i"], + data: { + domain: "loveisover.me", + http: true, + https: true, + software: "foolfuuka" + } }, { name: "Install Gentoo", boards: ["diy", "g", "sci"], @@ -12356,7 +12425,7 @@ boards: ["cgl", "g", "mu", "w"], files: ["cgl", "g", "mu", "w"], data: { - domain: "rbt.asia", + domain: "archive.rebeccablacktech.com", http: true, https: true, software: "fuuka" @@ -12376,10 +12445,37 @@ files: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"], data: { domain: "fuuka.warosu.org", - http: true, https: true, software: "fuuka" } + }, { + name: "fgts", + boards: ["soc"], + files: ["soc"], + data: { + domain: "fgts.eu", + http: true, + https: true, + software: "foolfuuka" + } + }, { + name: "maware", + boards: ["t"], + files: ["t"], + data: { + domain: "archive.mawa.re", + http: true, + software: "foolfuuka" + } + }, { + name: "installgentoo.com", + boards: ["g", "t"], + files: ["g", "t"], + data: { + domain: "chan.installgentoo.com", + http: true, + software: "foolfuuka" + } }, { name: "Foolz Beta", boards: ["a", "co", "d", "gd", "h", "jp", "m", "mlp", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], @@ -12391,16 +12487,6 @@ withCredentials: true, software: "foolfuuka" } - }, { - name: "Love is Over", - boards: ["d", "i"], - files: ["d", "i"], - data: { - domain: "loveisover.me", - http: true, - https: true, - software: "foolfuuka" - } } ], to: function(dest, data) { @@ -14498,18 +14584,21 @@ return a.textContent = ExpandThread.text('+', postsCount, filesCount); }, parse: function(req, thread, a) { - var data, filesCount, post, postData, posts, postsCount, postsRoot, root, _i, _len, _ref; + var filesCount, post, postData, posts, postsCount, postsRoot, root, _i, _len, _ref, _ref1; if ((_ref = req.status) !== 200 && _ref !== 304) { a.textContent = "Error " + req.statusText + " (" + req.status + ")"; return; } - data = JSON.parse(req.response).posts; - Build.spoilerRange[thread.board] = data.shift().custom_spoiler; + Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler; posts = []; postsRoot = []; filesCount = 0; - for (_i = 0, _len = data.length; _i < _len; _i++) { - postData = data[_i]; + _ref1 = req.response.posts; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + postData = _ref1[_i]; + if (postData.no === thread.ID) { + continue; + } if (post = thread.posts[postData.no]) { if ('file' in post) { filesCount++; @@ -15638,8 +15727,6 @@ } }, clean: function() { - var posts, threads; - posts = g.posts, threads = g.threads; g.threads.forEach(function(thread) { return thread.collect(); }); @@ -15741,7 +15828,6 @@ }, updateBoard: function(boardID) { var fullBoardList, onload, req; - req = null; fullBoardList = $('#full-board-list', Header.boardList); $.rmClass($('.current', fullBoardList), 'current'); $.addClass($("a[href*='/" + boardID + "/']", fullBoardList), 'current'); @@ -15757,7 +15843,7 @@ return; } try { - _ref = JSON.parse(req.response).boards; + _ref = req.response.boards; for (_i = 0, _len = _ref.length; _i < _len; _i++) { aboard = _ref[_i]; if (!(aboard.board === boardID)) { @@ -15900,7 +15986,7 @@ } Navigate.title(); try { - return Navigate.parse(JSON.parse(req.response).posts); + return Navigate.parse(req.response.posts); } catch (_error) { err = _error; console.error('Navigate failure:'); @@ -15978,59 +16064,27 @@ Settings = { init: function() { - var addSection, check, el, key, settings, value, _ref; + var check, el, settings, + _this = this; el = $.el('a', { className: 'settings-link', href: 'javascript:;', textContent: 'Settings' }); - $.on(el, 'click', Settings.open); + $.on(el, 'click', this.open); $.event('AddMenuEntry', { type: 'header', el: el, order: 1 }); - $.get('previousversion', null, function(item) { - var changelog, curr, prev, previous; - if (previous = item['previousversion']) { - if (previous === g.VERSION) { - return; - } - prev = previous.match(/\d+/g).map(Number); - curr = g.VERSION.match(/\d+/g).map(Number); - if (!(prev[0] <= curr[0] && prev[1] <= curr[1] && prev[2] <= curr[2])) { - return; - } - changelog = 'https://github.com/zixaphir/appchan-x/blob/master/CHANGELOG.md'; - el = $.el('span', { - innerHTML: "appchan x has been updated to version " + g.VERSION + "." - }); - if (Conf['Show Updated Notifications']) { - new Notice('info', el, 30); - } - } else { - $.on(d, '4chanXInitFinished', Settings.open); - } - return $.set('previousversion', g.VERSION); - }); - addSection = Settings.addSection; - _ref = { - 'style': 'Style', - 'themes': 'Themes', - 'mascots': 'Mascots', - 'main': 'Script', - 'filter': 'Filter', - 'sauce': 'Sauce', - 'advanced': 'Advanced', - 'keybinds': 'Keybinds' - }; - for (key in _ref) { - value = _ref[key]; - addSection(value, Settings[key]); - } - $.on(d, 'AddSettingsSection', Settings.addSection); + this.addSection('Main', this.main); + this.addSection('Filter', this.filter); + this.addSection('Sauce', this.sauce); + this.addSection('Advanced', this.advanced); + this.addSection('Keybinds', this.keybinds); + $.on(d, 'AddSettingsSection', this.addSection); $.on(d, 'OpenSettings', function(e) { - return Settings.open(e.detail); + return _this.open(e.detail); }); settings = JSON.parse(localStorage.getItem('4chan-settings')) || {}; if (!settings.disableAll) { @@ -16066,13 +16120,14 @@ Settings.dialog = dialog = $.el('div', { id: 'appchanx-settings', "class": 'dialog', - innerHTML: "
" + innerHTML: "
" }); Settings.overlay = overlay = $.el('div', { id: 'overlay' }); $.on($('.export', dialog), 'click', Settings["export"]); $.on($('.import', dialog), 'click', Settings["import"]); + $.on($('.reset', dialog), 'click', Settings.reset); $.on($('input', dialog), 'change', Settings.onImport); links = []; _ref = Settings.sections; @@ -16132,7 +16187,7 @@ return $.event('OpenSettings', null, section); }, main: function(section) { - var arr, button, description, div, fs, hiddenNum, input, inputs, items, key, obj, _ref; + var arr, button, description, div, fs, input, inputs, items, key, obj, _ref; items = {}; inputs = {}; _ref = Config.main; @@ -16168,45 +16223,34 @@ innerHTML: ": Clear manually-hidden threads and posts on all boards. Reload the page to apply." }); button = $('button', div); - hiddenNum = 0; - $.get('hiddenThreads', { - boards: {} - }, function(item) { - var ID, board, thread, _ref1; - _ref1 = item.hiddenThreads.boards; + $.get({ + hiddenThreads: {}, + hiddenPosts: {} + }, function(_arg) { + var ID, board, hiddenNum, hiddenPosts, hiddenThreads, thread, _ref1, _ref2; + hiddenThreads = _arg.hiddenThreads, hiddenPosts = _arg.hiddenPosts; + hiddenNum = 0; + _ref1 = hiddenThreads.boards; for (ID in _ref1) { board = _ref1[ID]; - for (ID in board) { - thread = board[ID]; - hiddenNum++; - } + hiddenNum += Object.keys(board).length; } - return button.textContent = "Hidden: " + hiddenNum; - }); - $.get('hiddenPosts', { - boards: {} - }, function(item) { - var ID, board, post, thread, _ref1; - _ref1 = item.hiddenPosts.boards; - for (ID in _ref1) { - board = _ref1[ID]; + _ref2 = hiddenPosts.boards; + for (ID in _ref2) { + board = _ref2[ID]; for (ID in board) { thread = board[ID]; - for (ID in thread) { - post = thread[ID]; - hiddenNum++; - } + hiddenNum += Object.keys(thread).length; } } return button.textContent = "Hidden: " + hiddenNum; }); $.on(button, 'click', function() { this.textContent = 'Hidden: 0'; - return $.get('hiddenThreads', { - boards: {} - }, function(item) { - var boardID; - for (boardID in item.hiddenThreads.boards) { + return $.get('hiddenThreads', {}, function(_arg) { + var boardID, hiddenThreads; + hiddenThreads = _arg.hiddenThreads; + for (boardID in hiddenThreads.boards) { localStorage.removeItem("4chan-hide-t-" + boardID); } return $["delete"](['hiddenThreads', 'hiddenPosts']); @@ -16214,39 +16258,26 @@ }); return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div); }, - "export": function(now, data) { - var a, db, _i, _len, _ref; - if (typeof now !== 'number') { - now = Date.now(); - data = { + "export": function() { + return $.get(Conf, function(Conf) { + delete Conf['archives']; + return Settings.downloadExport({ version: g.VERSION, - date: now - }; - _ref = DataBoard.keys; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - db = _ref[_i]; - Conf[db] = { - boards: {} - }; - } - $.get(Conf, function(Conf) { - delete Conf['archives']; - data.Conf = Conf; - return Settings["export"](now, data); + date: Date.now(), + Conf: Conf }); - return; - } + }); + }, + downloadExport: function(data) { + var a; a = $.el('a', { - className: 'warning', - textContent: 'Save me!', - download: "appchan x v" + g.VERSION + "-" + now + ".json", - href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))))), - target: '_blank' + download: "appchan x v" + g.VERSION + "-" + data.date + ".json", + href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))))) }); return a.click(); }, "import": function() { - return this.nextElementSibling.click(); + return $('input', this.parentNode).click(); }, onImport: function() { var file, output, reader; @@ -16260,10 +16291,9 @@ } reader = new FileReader(); reader.onload = function(e) { - var data, err; + var err; try { - data = JSON.parse(e.target.result); - Settings.loadSettings(data); + Settings.loadSettings(JSON.parse(e.target.result)); if (confirm('Import successful. Reload now?')) { return window.location.reload(); } @@ -16284,16 +16314,14 @@ } return $.set(data.Conf); }, - convertSettings: function(data, map) { - var newKey, prevKey; - for (prevKey in map) { - newKey = map[prevKey]; - if (newKey) { - data.Conf[newKey] = data.Conf[prevKey]; - } - delete data.Conf[prevKey]; + reset: function() { + if (confirm('Your current settings will be entirely wiped, are you sure?')) { + return $.clear(function() { + if (confirm('Reset successful. Reload now?')) { + return window.location.reload(); + } + }); } - return data; }, filter: function(section) { var select; @@ -17285,10 +17313,27 @@ Main.handleErrors(errors); } Main.callbackNodes(Thread, threads); - return Main.callbackNodesDB(Post, posts, function() { + Main.callbackNodesDB(Post, posts, function() { return $.event('4chanXInitFinished'); }); } + return $.get('previousversion', null, function(_arg) { + var changelog, el, previousversion; + previousversion = _arg.previousversion; + if (previousversion === g.VERSION) { + return; + } + if (previousversion) { + changelog = 'https://github.com/zixaphir/appchan-x/blob/master/CHANGELOG.md'; + el = $.el('span', { + innerHTML: "appchan x has been updated to version " + g.VERSION + "." + }); + new Notice('info', el, 15); + } else { + Settings.open(); + } + return $.set('previousversion', g.VERSION); + }); }, callbackNodes: function(klass, nodes) { var cb, i, node; diff --git a/package.json b/package.json index 88308ce39..aae922c2b 100644 --- a/package.json +++ b/package.json @@ -22,22 +22,22 @@ "min": { "chrome": "31", "firefox": "26", - "greasemonkey": "1.13" + "greasemonkey": "1.14" } }, "devDependencies": { - "font-awesome": "https://github.com/FortAwesome/Font-Awesome/archive/v4.0.3.tar.gz", - "grunt": "~0.4.1", - "grunt-bump": "~0.0.11", - "grunt-concurrent": "~0.4.0", + "font-awesome": "~4.0.3", + "grunt": "~0.4.2", + "grunt-bump": "~0.0.13", + "grunt-concurrent": "~0.4.3", "grunt-contrib-clean": "~0.5.0", - "grunt-contrib-coffee": "~0.8.0", - "grunt-contrib-compress": "~0.5.2", + "grunt-contrib-coffee": "~0.8.2", + "grunt-contrib-compress": "~0.6.0", "grunt-contrib-concat": "~0.3.0", "grunt-contrib-copy": "~0.5.0", "grunt-contrib-watch": "~0.5.3", - "grunt-shell": "~0.6.0", - "load-grunt-tasks": "~0.2.0" + "grunt-shell": "~0.6.3", + "load-grunt-tasks": "~0.2.1" }, "repository": { "type": "git", diff --git a/src/Archive/Redirect.coffee b/src/Archive/Redirect.coffee index bacd0ea02..bc42c6cff 100755 --- a/src/Archive/Redirect.coffee +++ b/src/Archive/Redirect.coffee @@ -1,5 +1,4 @@ Redirect = - init: -> o = thread: {} @@ -52,8 +51,8 @@ Redirect = software: "foolfuuka" , name: "4plebs" - boards: ["hr", "o", "pol", "s4s", "tg", "tv", "x"] - files: ["hr", "o", "pol", "s4s", "tg", "tv", "x"] + boards: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"] + files: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"] data: domain: "archive.4plebs.org" http: true @@ -68,6 +67,15 @@ Redirect = http: true https: true software: "foolfuuka" + , + name: "Love is Over" + boards: ["d", "i"], + files: ["d", "i"] + data: + domain: "loveisover.me" + http: true + https: true + software: "foolfuuka" , name: "Install Gentoo" boards: ["diy", "g", "sci"] @@ -82,7 +90,7 @@ Redirect = boards: ["cgl", "g", "mu", "w"] files: ["cgl", "g", "mu", "w"] data: - domain: "rbt.asia" + domain: "archive.rebeccablacktech.com" http: true https: true software: "fuuka" @@ -100,9 +108,33 @@ Redirect = files: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"] data: domain: "fuuka.warosu.org" - http: true https: true software: "fuuka" + , + name: "fgts" + boards: ["soc"] + files: ["soc"] + data: + domain: "fgts.eu" + http: true + https: true + software: "foolfuuka" + , + name: "maware" + boards: ["t"] + files: ["t"] + data: + domain: "archive.mawa.re" + http: true + software: "foolfuuka" + , + name: "installgentoo.com" + boards: ["g", "t"] + files: ["g", "t"] + data: + domain: "chan.installgentoo.com" + http: true + software: "foolfuuka" , name: "Foolz Beta" boards: ["a", "co", "d", "gd", "h", "jp", "m", "mlp", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], @@ -113,15 +145,6 @@ Redirect = https: true withCredentials: true software: "foolfuuka" - , - name: "Love is Over" - boards: ["d", "i"], - files: ["d", "i"] - data: - domain: "loveisover.me" - http: true - https: true - software: "foolfuuka" ] to: (dest, data) -> diff --git a/src/Filtering/ThreadHiding.coffee b/src/Filtering/ThreadHiding.coffee index b975417a2..6c3b9363b 100644 --- a/src/Filtering/ThreadHiding.coffee +++ b/src/Filtering/ThreadHiding.coffee @@ -58,7 +58,7 @@ ThreadHiding = $.cache "//a.4cdn.org/#{g.BOARD}/threads.json", -> return unless @status is 200 threads = {} - for page in JSON.parse @response + for page in @response for thread in page.threads if thread.no of hiddenThreadsOnCatalog threads[thread.no] = hiddenThreadsOnCatalog[thread.no] diff --git a/src/General/Get.coffee b/src/General/Get.coffee index d3676ae15..7d23faf3b 100755 --- a/src/General/Get.coffee +++ b/src/General/Get.coffee @@ -80,6 +80,7 @@ Get = $.cache url, -> Get.archivedPost @, boardID, postID, root, context , + responseType: 'json' withCredentials: url.archive.withCredentials insert: (post, root, context) -> # Stop here if the container has been removed while loading. @@ -118,7 +119,7 @@ Get = "Error #{req.statusText} (#{req.status})." return - posts = JSON.parse(req.response).posts + {posts} = req.response Build.spoilerRange[boardID] = posts[0].custom_spoiler for post in posts break if post.no is postID # we found it! @@ -149,7 +150,7 @@ Get = Get.insert post, root, context return - data = JSON.parse req.response + data = req.response if data.error $.addClass root, 'warning' root.textContent = data.error @@ -227,4 +228,4 @@ Get = '[/moot]': '' '[banned]': '' '[/banned]': '' - }[text] or text.replace ':lit', '' \ No newline at end of file + }[text] or text.replace ':lit', '' diff --git a/src/General/Header.coffee b/src/General/Header.coffee index d3043f472..06a937134 100644 --- a/src/General/Header.coffee +++ b/src/General/Header.coffee @@ -254,17 +254,15 @@ Header = toggleHideBarOnScroll: (e) -> hide = @checked - $.set 'Header auto-hide on scroll', hide + $.cb.checked.call @ Header.setHideBarOnScroll hide hideBarOnScroll: -> offsetY = window.pageYOffset if offsetY > (Header.previousOffset or 0) - $.addClass Header.bar, 'autohide' - $.addClass Header.bar, 'scroll' + $.addClass Header.bar, 'autohide', 'scroll' else - $.rmClass Header.bar, 'autohide' - $.rmClass Header.bar, 'scroll' + $.rmClass Header.bar, 'autohide', 'scroll' Header.previousOffset = offsetY setBarPosition: (bottom) -> @@ -320,9 +318,21 @@ Header = scrollTo: (root, down, needed) -> if down x = Header.getBottomOf root + if Conf['Header auto-hide on scroll'] and Conf['Bottom header'] + {height} = Header.bar.getBoundingClientRect() + if x <= 0 + x += height if !Header.isHidden() + else + x -= height if Header.isHidden() window.scrollBy 0, -x unless needed and x >= 0 else x = Header.getTopOf root + if Conf['Header auto-hide on scroll'] and !Conf['Bottom header'] + {height} = Header.bar.getBoundingClientRect() + if x >= 0 + x += height if !Header.isHidden() + else + x -= height if Header.isHidden() window.scrollBy 0, x unless needed and x >= 0 scrollToIfNeeded: (root, down) -> @@ -342,6 +352,12 @@ Header = headRect = Header.toggle.getBoundingClientRect() bottom -= clientHeight - headRect.bottom + headRect.height bottom + isHidden: -> + {top} = Header.bar.getBoundingClientRect() + if Conf['Bottom header'] + top is doc.clientHeight + else + top < 0 addShortcut: (el, icon) -> $.addClass el, 'shortcut' diff --git a/src/General/Index.coffee b/src/General/Index.coffee index c99b05d5c..370c56838 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -274,7 +274,7 @@ Index = try if req.status is 200 - Index.parse JSON.parse(req.response), pageNum + Index.parse req.response, pageNum else if req.status is 304 and pageNum? Index.pageNav pageNum catch err @@ -470,6 +470,7 @@ Index = else pageNum = Index.getCurrentPage() else + return unless Index.searchInput.dataset.searching pageNum = Index.pageBeforeSearch delete Index.pageBeforeSearch <% if (type === 'userscript') { %> diff --git a/src/General/Main.coffee b/src/General/Main.coffee index 5f6e44e2e..e59dfe621 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -125,12 +125,18 @@ Main = 'left=0,top=0,width=500,height=255,toolbar=0,resizable=0' $.before styleSelector.previousSibling, [$.tn '['; passLink, $.tn ']\u00A0\u00A0'] + # Parse HTML or skip it and start building from JSON. unless Conf['JSON Navigation'] and g.VIEW is 'index' Main.initThread() else $.event '4chanXInitFinished' <% if (type === 'userscript') { %> + test = $.el 'span' + test.classList.add 'a', 'b' + if test.className isnt 'a b' + new Notice 'warning', "Your version of Firefox is outdated (v<%= meta.min.firefox %> minimum) and <%= meta.name %> may not operate correctly.", 30 + GMver = GM_info.version.split '.' for v, i in "<%= meta.min.greasemonkey %>".split '.' continue if v is GMver[i] @@ -167,6 +173,17 @@ Main = Main.callbackNodesDB Post, posts, -> $.event '4chanXInitFinished' + $.get 'previousversion', null, ({previousversion}) -> + return if previousversion is g.VERSION + if previousversion + changelog = '<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md' + el = $.el 'span', + innerHTML: "<%= meta.name %> has been updated to version #{g.VERSION}." + new Notice 'info', el, 15 + else + Settings.open() + $.set 'previousversion', g.VERSION + callbackNodes: (klass, nodes) -> i = 0 cb = klass.callbacks diff --git a/src/General/Navigate.coffee b/src/General/Navigate.coffee index 68a12a593..81253f770 100644 --- a/src/General/Navigate.coffee +++ b/src/General/Navigate.coffee @@ -33,15 +33,11 @@ Navigate = return clean: -> - {posts, threads} = g - # Garbage collection g.threads.forEach (thread) -> thread.collect() - QuoteBacklink.containers = {} - # Delete nodes - $.rmAll $ '.board' + $.rmAll $('.board') features: [ ['Thread Excerpt', ThreadExcerpt] @@ -110,8 +106,6 @@ Navigate = }[g.VIEW]() updateBoard: (boardID) -> - req = null - fullBoardList = $ '#full-board-list', Header.boardList $.rmClass $('.current', fullBoardList), 'current' $.addClass $("a[href*='/#{boardID}/']", fullBoardList), 'current' @@ -127,7 +121,7 @@ Navigate = return unless req.status is 200 try - for aboard in JSON.parse(req.response).boards when aboard.board is boardID + for aboard in req.response.boards when aboard.board is boardID board = aboard break @@ -243,7 +237,7 @@ Navigate = Navigate.title() try - Navigate.parse JSON.parse(req.response).posts + Navigate.parse req.response.posts catch err console.error 'Navigate failure:' console.log err diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee index 566f37ebc..b58913aab 100755 --- a/src/General/Settings.coffee +++ b/src/General/Settings.coffee @@ -5,45 +5,21 @@ Settings = className: 'settings-link' href: 'javascript:;' textContent: 'Settings' - $.on el, 'click', Settings.open + $.on el, 'click', @open $.event 'AddMenuEntry', type: 'header' el: el order: 1 - $.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 - return unless prev[0] <= curr[0] and prev[1] <= curr[1] and prev[2] <= curr[2] + @addSection 'Main', @main + @addSection 'Filter', @filter + @addSection 'Sauce', @sauce + @addSection 'Advanced', @advanced + @addSection 'Keybinds', @keybinds - changelog = '<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md' - el = $.el 'span', - innerHTML: "<%= meta.name %> has been updated to version #{g.VERSION}." - if Conf['Show Updated Notifications'] - new Notice 'info', el, 30 - else - $.on d, '4chanXInitFinished', Settings.open - $.set 'previousversion', g.VERSION - - {addSection} = Settings - addSection value, Settings[key] for key, value of { - 'style': 'Style' - 'themes': 'Themes' - 'mascots': 'Mascots' - 'main': 'Script' - 'filter': 'Filter' - 'sauce': 'Sauce' - 'advanced': 'Advanced' - 'keybinds': 'Keybinds' - } - - $.on d, 'AddSettingsSection', Settings.addSection - $.on d, 'OpenSettings', (e) -> Settings.open e.detail + $.on d, 'AddSettingsSection', @addSection + $.on d, 'OpenSettings', (e) => @open e.detail settings = JSON.parse(localStorage.getItem '4chan-settings') or {} unless settings.disableAll @@ -79,8 +55,10 @@ Settings = $.on $('.export', dialog), 'click', Settings.export $.on $('.import', dialog), 'click', Settings.import + $.on $('.reset', dialog), 'click', Settings.reset $.on $('input', dialog), 'change', Settings.onImport + links = [] for section in Settings.sections link = $.el 'a', @@ -153,58 +131,41 @@ Settings = div = $.el 'div', innerHTML: ": Clear manually-hidden threads and posts on all boards. Reload the page to apply." button = $ 'button', div - hiddenNum = 0 - $.get 'hiddenThreads', boards: {}, (item) -> - for ID, board of item.hiddenThreads.boards + $.get {hiddenThreads: {}, hiddenPosts: {}}, ({hiddenThreads, hiddenPosts}) -> + hiddenNum = 0 + for ID, board of hiddenThreads.boards + hiddenNum += Object.keys(board).length + for ID, board of hiddenPosts.boards for ID, thread of board - hiddenNum++ - button.textContent = "Hidden: #{hiddenNum}" - $.get 'hiddenPosts', boards: {}, (item) -> - for ID, board of item.hiddenPosts.boards - for ID, thread of board - for ID, post of thread - hiddenNum++ + hiddenNum += Object.keys(thread).length button.textContent = "Hidden: #{hiddenNum}" $.on button, 'click', -> @textContent = 'Hidden: 0' - $.get 'hiddenThreads', boards: {}, (item) -> - for boardID of item.hiddenThreads.boards + $.get 'hiddenThreads', {}, ({hiddenThreads}) -> + for boardID of hiddenThreads.boards localStorage.removeItem "4chan-hide-t-#{boardID}" $.delete ['hiddenThreads', 'hiddenPosts'] $.after $('input[name="Stubs"]', section).parentNode.parentNode, div - export: (now, data) -> - unless typeof now is 'number' - now = Date.now() - data = - version: g.VERSION - date: now - for db in DataBoard.keys - Conf[db] = boards: {} - # Make sure to export the most recent data. - $.get Conf, (Conf) -> - # XXX don't export archives. - delete Conf['archives'] - data.Conf = Conf - Settings.export now, data - return - a = $.el 'a', - className: 'warning' - textContent: 'Save me!' - download: "<%= meta.name %> v#{g.VERSION}-#{now}.json" - href: "data:application/json;base64,#{btoa unescape encodeURIComponent JSON.stringify data, null, 2}" - target: '_blank' - <% if (type !== 'userscript') { %> - a.click() - <% } else { %> - # XXX Firefox won't let us download automatically. - span = $ '.imp-exp-result', Settings.dialog - $.rmAll span - $.add span, a - <% } %> + export: -> + # Make sure to export the most recent data. + $.get Conf, (Conf) -> + # XXX don't export archives. + delete Conf['archives'] + Settings.downloadExport {version: g.VERSION, date: Date.now(), Conf} + downloadExport: (data) -> + a = $.el 'a', + download: "<%= meta.name %> v#{g.VERSION}-#{data.date}.json" + href: "data:application/json;base64,#{btoa unescape encodeURIComponent JSON.stringify data, null, 2}" + <% if (type === 'userscript') { %> + p = $ '.imp-exp-result', Settings.dialog + $.rmAll p + $.add p, a + <% } %> + a.click() import: -> - @nextElementSibling.click() + $('input', @parentNode).click() onImport: -> return unless file = @files[0] @@ -215,8 +176,7 @@ Settings = reader = new FileReader() reader.onload = (e) -> try - data = JSON.parse e.target.result - Settings.loadSettings data + Settings.loadSettings JSON.parse e.target.result if confirm 'Import successful. Reload now?' window.location.reload() catch err @@ -230,11 +190,9 @@ Settings = delete data.Conf['WatchedThreads'] $.set data.Conf - convertSettings: (data, map) -> - for prevKey, newKey of map - data.Conf[newKey] = data.Conf[prevKey] if newKey - delete data.Conf[prevKey] - data + reset: -> + if confirm 'Your current settings will be entirely wiped, are you sure?' + $.clear -> window.location.reload() if confirm 'Reset successful. Reload now?' filter: (section) -> section.innerHTML = <%= importHTML('Settings/Filter-select') %> diff --git a/src/General/UI.coffee b/src/General/UI.coffee index 83da927e5..af783f40b 100755 --- a/src/General/UI.coffee +++ b/src/General/UI.coffee @@ -154,6 +154,9 @@ UI = do -> e.preventDefault() e.stopPropagation() + onFocus: (e) => + e.stopPropagation() + @focus e.target focus: (entry) -> while focused = $.x 'parent::*/child::*[contains(@class,"focused")]', entry $.rmClass focused, 'focused' @@ -196,10 +199,7 @@ UI = do -> parseEntry: (entry) -> {el, subEntries} = entry $.addClass el, 'entry' - $.on el, 'focus mouseover', ((e) -> - e.stopPropagation() - @focus el - ).bind @ + $.on el, 'focus mouseover', @onFocus el.style.order = entry.order or 100 return unless subEntries $.addClass el, 'has-submenu' diff --git a/src/General/css/style.css b/src/General/css/style.css index 91342c993..223768bbe 100755 --- a/src/General/css/style.css +++ b/src/General/css/style.css @@ -653,6 +653,15 @@ span.hide-announcement { text-decoration: none; border-bottom: 1px dashed; } +@supports (text-decoration-style: dashed) or (-moz-text-decoration-style: dashed) { + .quotelink.forwardlink, + .backlink.forwardlink { + text-decoration: underline; + -moz-text-decoration-style: dashed; + text-decoration-style: dashed; + border-bottom: none; + } +} .filtered { text-decoration: underline line-through; } @@ -1189,7 +1198,7 @@ a:only-of-type > .remove { left: 0px; width: 200px; } -.export, .import { +.export, .import, .reset { cursor: pointer; text-decoration: none !important; } diff --git a/src/General/html/Settings/Settings.html b/src/General/html/Settings/Settings.html index 996fd2b27..972f8cf1a 100755 --- a/src/General/html/Settings/Settings.html +++ b/src/General/html/Settings/Settings.html @@ -2,9 +2,10 @@
- Export | - Import | - + Export |  + Import |  + Reset Settings |  + <%= meta.name %> | #{g.VERSION} | Issues | diff --git a/src/General/lib/$.coffee b/src/General/lib/$.coffee index 3e45ed12a..1e7291598 100755 --- a/src/General/lib/$.coffee +++ b/src/General/lib/$.coffee @@ -54,6 +54,8 @@ $.ajax = do -> if whenModified r.setRequestHeader 'If-Modified-Since', lastModified[url] if url of lastModified $.on r, 'load', -> lastModified[url] = r.getResponseHeader 'Last-Modified' + if /\.json$/.test url + r.responseType = 'json' $.extend r, options $.extend r.upload, upCallbacks r.send form @@ -113,11 +115,11 @@ $.X = (path, root) -> # XPathResult.ORDERED_NODE_SNAPSHOT_TYPE === 7 d.evaluate path, root, null, 7, null -$.addClass = (el, className) -> - el.classList.add className +$.addClass = (el, className...) -> + el.classList.add className... -$.rmClass = (el, className) -> - el.classList.remove className +$.rmClass = (el, className...) -> + el.classList.remove className... $.toggleClass = (el, className) -> el.classList.toggle className @@ -131,12 +133,8 @@ $.rm = do -> else (el) -> el.parentNode?.removeChild el -$.rmAll = (root) -> - # jsperf.com/emptify-element - for node in [root.childNodes...] - # HTMLSelectElement.remove !== Element.remove - root.removeChild node - return +# jsperf.com/emptify-element/9 +$.rmAll = (root) -> root.textContent = '' $.tn = (s) -> d.createTextNode s @@ -332,29 +330,50 @@ $.get = (key, val, cb) -> chrome.storage.sync.get syncItems, done $.set = do -> - items = {} - localItems = {} + items = + sync: {} + local: {} + timeout = {} - set = $.debounce $.SECOND, -> + setArea = (area) -> + data = items[area] + return if !Object.keys(data).length or timeout[area] + items[area] = {} + chrome.storage[area].set data, -> + if chrome.runtime.lastError + c.error chrome.runtime.lastError.message + for key, val of data when key not of items[area] + items[area][key] = val + timeout[area] = setTimeout setArea, $.MINUTE, area + return + delete timeout[area] + + setAll = $.debounce $.SECOND, -> for key in $.localKeys - if key of items - (localItems or= {})[key] = items[key] - delete items[key] + if key of items.sync + items.local[key] = items.sync[key] + delete items.sync[key] try - chrome.storage.local.set localItems - chrome.storage.sync.set items - items = {} - localItems = {} + setArea 'local' + setArea 'sync' catch err c.error err.stack (key, val) -> if typeof key is 'string' - items[key] = val + items.sync[key] = val else - $.extend items, key - set() - + $.extend items.sync, key + setAll() +$.clear = (cb) -> + count = 2 + done = -> + if chrome.runtime.lastError + c.error chrome.runtime.lastError.message + return + cb?() unless --count + chrome.storage.local.clear done + chrome.storage.sync.clear done <% } else { %> # http://wiki.greasespot.net/Main_Page @@ -402,6 +421,9 @@ $.set = do -> for key, val of keys set key, val return +$.clear = (cb) -> + $.delete GM_listValues().map (key) -> key.replace g.NAMESPACE, '' + cb?() <% } %> $.remove = (arr, value) -> diff --git a/src/General/lib/databoard.class b/src/General/lib/databoard.class index ff0162aad..fec423990 100755 --- a/src/General/lib/databoard.class +++ b/src/General/lib/databoard.class @@ -78,7 +78,7 @@ class DataBoard return board = @data.boards[boardID] threads = {} - for page in JSON.parse e.target.response + for page in e.target.response for thread in page.threads if thread.no of board threads[thread.no] = board[thread.no] @@ -93,4 +93,4 @@ class DataBoard disconnect: -> $.desync @key delete @sync - delete @data \ No newline at end of file + delete @data diff --git a/src/General/meta/metadata.js b/src/General/meta/metadata.js index f41ea591d..9dac93a7e 100755 --- a/src/General/meta/metadata.js +++ b/src/General/meta/metadata.js @@ -14,6 +14,7 @@ // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue +// @grant GM_listValues // @grant GM_openInTab // @run-at document-start // @updateURL <%= meta.repo %>raw/stable/builds/<%= meta.files.metajs %> diff --git a/src/Images/Gallery.coffee b/src/Images/Gallery.coffee index 3aa5585b1..8529266c1 100644 --- a/src/Images/Gallery.coffee +++ b/src/Images/Gallery.coffee @@ -203,7 +203,8 @@ Gallery = $.ajax "//api.4chan.org/#{post.board}/res/#{post.thread}.json", onload: -> return if @status isnt 200 i = 0 - while postObj = JSON.parse(@response).posts[i++] + {posts} = @response + while postObj = posts[i++] break if postObj.no is post.ID unless postObj.no return post.kill() diff --git a/src/Images/ImageExpand.coffee b/src/Images/ImageExpand.coffee index ed2cd2fa7..057964e53 100644 --- a/src/Images/ImageExpand.coffee +++ b/src/Images/ImageExpand.coffee @@ -156,10 +156,19 @@ ImageExpand = return timeoutID = setTimeout ImageExpand.expand, 10000, post + <% if (type === 'crx') { %> + $.ajax @src, + onloadend: -> + return if @status isnt 404 + clearTimeout timeoutID + post.kill true + , + type: 'head' + <% } else { %> # XXX CORS for i.4cdn.org WHEN? $.ajax "//a.4cdn.org/#{post.board}/res/#{post.thread}.json", onload: -> return if @status isnt 200 - for postObj in JSON.parse(@response).posts + for postObj in @response.posts break if postObj.no is post.ID if postObj.no isnt post.ID clearTimeout timeoutID @@ -167,6 +176,7 @@ ImageExpand = else if postObj.filedeleted clearTimeout timeoutID post.kill true + <% } %> menu: init: -> diff --git a/src/Images/ImageHover.coffee b/src/Images/ImageHover.coffee index 9116a6c95..9ef4f10e7 100755 --- a/src/Images/ImageHover.coffee +++ b/src/Images/ImageHover.coffee @@ -38,10 +38,19 @@ ImageHover = return timeoutID = setTimeout (=> @src = post.file.URL + '?' + Date.now()), 3000 + <% if (type === 'crx') { %> + $.ajax @src, + onloadend: -> + return if @status isnt 404 + clearTimeout timeoutID + post.kill true + , + type: 'head' + <% } else { %> # XXX CORS for i.4cdn.org WHEN? $.ajax "//a.4cdn.org/#{post.board}/res/#{post.thread}.json", onload: -> return if @status isnt 200 - for postObj in JSON.parse(@response).posts + for postObj in @response.posts break if postObj.no is post.ID if postObj.no isnt post.ID clearTimeout timeoutID @@ -49,3 +58,4 @@ ImageHover = else if postObj.filedeleted clearTimeout timeoutID post.kill true + <% } %> diff --git a/src/Linkification/Linkify.coffee b/src/Linkification/Linkify.coffee index 236e68097..52d241cdb 100755 --- a/src/Linkification/Linkify.coffee +++ b/src/Linkification/Linkify.coffee @@ -241,7 +241,7 @@ Linkify = service = Linkify.types[key].title switch response.status when 200, 304 - text = "#{service.text JSON.parse response.responseText}" + text = "#{service.text response.response}" if Conf['Embedding'] embed.dataset.title = text when 404 @@ -304,7 +304,7 @@ Linkify = $.cache "https://mediacru.sh/#{a.dataset.uid}.json", -> {status} = @ return div.innerHTML = "ERROR #{status}" unless status in [200, 304] - {files} = JSON.parse @response + {files} = @response for type in ['video/mp4', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'image/svg', 'audio/mpeg'] for file in files if file.type is type diff --git a/src/Miscellaneous/AnnouncementHiding.coffee b/src/Miscellaneous/AnnouncementHiding.coffee index a27290420..883159e6c 100755 --- a/src/Miscellaneous/AnnouncementHiding.coffee +++ b/src/Miscellaneous/AnnouncementHiding.coffee @@ -1,10 +1,9 @@ PSAHiding = init: -> return unless Conf['Announcement Hiding'] - $.addClass doc, 'hide-announcement' - $.on d, '4chanXInitFinished', @setup + setup: -> $.off d, '4chanXInitFinished', PSAHiding.setup diff --git a/src/Miscellaneous/ExpandThread.coffee b/src/Miscellaneous/ExpandThread.coffee index 69accf4af..beed897f5 100755 --- a/src/Miscellaneous/ExpandThread.coffee +++ b/src/Miscellaneous/ExpandThread.coffee @@ -77,13 +77,13 @@ ExpandThread = a.textContent = "Error #{req.statusText} (#{req.status})" return - data = JSON.parse(req.response).posts - Build.spoilerRange[thread.board] = data.shift().custom_spoiler + Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler posts = [] postsRoot = [] filesCount = 0 - for postData in data + for postData in req.response.posts + continue if postData.no is thread.ID if post = thread.posts[postData.no] filesCount++ if 'file' of post postsRoot.push post.nodes.root diff --git a/src/Monitoring/ThreadStats.coffee b/src/Monitoring/ThreadStats.coffee index bb1e3ae29..03af30d89 100755 --- a/src/Monitoring/ThreadStats.coffee +++ b/src/Monitoring/ThreadStats.coffee @@ -77,9 +77,8 @@ ThreadStats = onThreadsLoad: -> return unless Conf["Page Count in Stats"] and @status is 200 - pages = JSON.parse @response - for page in pages + for page in @response for thread in page.threads when thread.no is ThreadStats.thread.ID ThreadStats.pageCountEl.textContent = page.page - (if page.page is pages.length - 1 then $.addClass else $.rmClass) ThreadStats.pageCountEl, 'warning' + (if page.page is @response.length - 1 then $.addClass else $.rmClass) ThreadStats.pageCountEl, 'warning' return diff --git a/src/Monitoring/ThreadUpdater.coffee b/src/Monitoring/ThreadUpdater.coffee index d672b6cfd..de1975ea5 100755 --- a/src/Monitoring/ThreadUpdater.coffee +++ b/src/Monitoring/ThreadUpdater.coffee @@ -159,7 +159,7 @@ ThreadUpdater = switch req.status when 200 g.DEAD = false - ThreadUpdater.parse JSON.parse(req.response).posts + ThreadUpdater.parse req.response.posts ThreadUpdater.setInterval() when 404 g.DEAD = true diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index 7a23273cc..8d910d027 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -695,7 +695,7 @@ QR = # Too many frequent mistyped captchas will auto-ban you! # On connection error, the post most likely didn't go through. QR.cooldown.set delay: 2 - else if err.textContent and m = err.textContent.match /wait\s(\d+)\ssecond/i + else if err.textContent and m = err.textContent.match /wait\s+(\d+)\s+second/i QR.cooldown.auto = if QR.captcha.isEnabled !!QR.captcha.captchas.length else