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