Merge branch 'v3'

Conflicts:
	CHANGELOG.md
	LICENSE
	builds/appchan-x.user.js
	builds/crx/script.js
	src/General/Main.coffee
	src/General/Settings.coffee
	src/General/html/Settings/Settings.html
	src/Miscellaneous/AnnouncementHiding.coffee
This commit is contained in:
Zixaphir 2014-01-26 11:40:50 -07:00
commit 1f49ef2212
31 changed files with 792 additions and 587 deletions

View File

@ -1,5 +1,12 @@
<<<<<<< HEAD
### v2.8.7 ### v2.8.7
*2014-01-19* *2014-01-19*
=======
**MayhemYDG**:
- Added a `Reset Settings` button in the settings.
- More stability update.
- Stability update.
>>>>>>> v3
**Zixaphir**: **Zixaphir**:
- Fix posting. - Fix posting.

48
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,48 @@
## Reporting bugs and suggestions
Reporting bugs:
1. Make sure both your **browser** and **4chan X** are up to date.<br>
Only **Chrome**, **Firefox** and **Opera** are supported.<br>
**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.

View File

@ -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. * Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE * https://github.com/zixaphir/appchan-x/blob/master/LICENSE

View File

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name 4chan X // @name 4chan X
// @version 1.3.2 // @version 1.3.2
// @minGMVer 1.13 // @minGMVer 1.14
// @minFFVer 26 // @minFFVer 26
// @namespace 4chan-X // @namespace 4chan-X
// @description Cross-browser userscript for maximum lurking on 4chan. // @description Cross-browser userscript for maximum lurking on 4chan.
@ -13,6 +13,7 @@
// @grant GM_getValue // @grant GM_getValue
// @grant GM_setValue // @grant GM_setValue
// @grant GM_deleteValue // @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_openInTab // @grant GM_openInTab
// @run-at document-start // @run-at document-start
// @updateURL https://github.com/seaweedchan/4chan-x/raw/stable/builds/4chan-X.meta.js // @updateURL https://github.com/seaweedchan/4chan-x/raw/stable/builds/4chan-X.meta.js

View File

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name appchan x // @name appchan x
// @version 2.8.7 // @version 2.8.7
// @minGMVer 1.13 // @minGMVer 1.14
// @minFFVer 26 // @minFFVer 26
// @namespace zixaphir // @namespace zixaphir
// @description The most comprehensive 4chan userscript. // @description The most comprehensive 4chan userscript.
@ -13,6 +13,7 @@
// @grant GM_getValue // @grant GM_getValue
// @grant GM_setValue // @grant GM_setValue
// @grant GM_deleteValue // @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_openInTab // @grant GM_openInTab
// @run-at document-start // @run-at document-start
// @updateURL https://github.com/zixaphir/appchan-x/raw/stable/builds/appchan-x.meta.js // @updateURL https://github.com/zixaphir/appchan-x/raw/stable/builds/appchan-x.meta.js

View File

@ -2,7 +2,7 @@
// ==UserScript== // ==UserScript==
// @name appchan x // @name appchan x
// @version 2.8.7 // @version 2.8.7
// @minGMVer 1.13 // @minGMVer 1.14
// @minFFVer 26 // @minFFVer 26
// @namespace zixaphir // @namespace zixaphir
// @description The most comprehensive 4chan userscript. // @description The most comprehensive 4chan userscript.
@ -14,6 +14,7 @@
// @grant GM_getValue // @grant GM_getValue
// @grant GM_setValue // @grant GM_setValue
// @grant GM_deleteValue // @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_openInTab // @grant GM_openInTab
// @run-at document-start // @run-at document-start
// @updateURL https://github.com/zixaphir/appchan-x/raw/stable/builds/appchan-x.meta.js // @updateURL https://github.com/zixaphir/appchan-x/raw/stable/builds/appchan-x.meta.js
@ -22,7 +23,7 @@
// ==/UserScript== // ==/UserScript==
/* /*
* appchan x - Version 2.8.7 - 2014-01-21 * appchan x - Version 2.8.7 - 2014-01-26
* *
* Licensed under the MIT license. * Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE * https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@ -111,8 +112,8 @@
(function() { (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, 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, __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, __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; }, __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); }; }; __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
@ -2624,6 +2625,9 @@
return lastModified[url] = r.getResponseHeader('Last-Modified'); return lastModified[url] = r.getResponseHeader('Last-Modified');
}); });
} }
if (/\.json$/.test(url)) {
r.responseType = 'json';
}
$.extend(r, options); $.extend(r, options);
$.extend(r.upload, upCallbacks); $.extend(r.upload, upCallbacks);
r.send(form); r.send(form);
@ -2712,12 +2716,16 @@
return d.evaluate(path, root, null, 7, null); return d.evaluate(path, root, null, 7, null);
}; };
$.addClass = function(el, className) { $.addClass = function() {
return el.classList.add(className); 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) { $.rmClass = function() {
return el.classList.remove(className); 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) { $.toggleClass = function(el, className) {
@ -2742,12 +2750,7 @@
})(); })();
$.rmAll = function(root) { $.rmAll = function(root) {
var node, _i, _len, _ref; return root.textContent = '';
_ref = __slice.call(root.childNodes);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
root.removeChild(node);
}
}; };
$.tn = function(s) { $.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) { $.remove = function(arr, value) {
var i; var i;
i = arr.indexOf(value); i = arr.indexOf(value);
@ -3622,7 +3632,7 @@
} }
board = _this.data.boards[boardID]; board = _this.data.boards[boardID];
threads = {}; threads = {};
_ref = JSON.parse(e.target.response); _ref = e.target.response;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = _ref[_i]; page = _ref[_i];
_ref1 = page.threads; _ref1 = page.threads;
@ -4190,18 +4200,16 @@
toggleHideBarOnScroll: function(e) { toggleHideBarOnScroll: function(e) {
var hide; var hide;
hide = this.checked; hide = this.checked;
$.set('Header auto-hide on scroll', hide); $.cb.checked.call(this);
return Header.setHideBarOnScroll(hide); return Header.setHideBarOnScroll(hide);
}, },
hideBarOnScroll: function() { hideBarOnScroll: function() {
var offsetY; var offsetY;
offsetY = window.pageYOffset; offsetY = window.pageYOffset;
if (offsetY > (Header.previousOffset || 0)) { if (offsetY > (Header.previousOffset || 0)) {
$.addClass(Header.bar, 'autohide'); $.addClass(Header.bar, 'autohide', 'scroll');
$.addClass(Header.bar, 'scroll');
} else { } else {
$.rmClass(Header.bar, 'autohide'); $.rmClass(Header.bar, 'autohide', 'scroll');
$.rmClass(Header.bar, 'scroll');
} }
return Header.previousOffset = offsetY; return Header.previousOffset = offsetY;
}, },
@ -4249,14 +4257,38 @@
return Header.scrollTo(post); return Header.scrollTo(post);
}, },
scrollTo: function(root, down, needed) { scrollTo: function(root, down, needed) {
var x; var height, x;
if (down) { if (down) {
x = Header.getBottomOf(root); 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)) { if (!(needed && x >= 0)) {
return window.scrollBy(0, -x); return window.scrollBy(0, -x);
} }
} else { } else {
x = Header.getTopOf(root); 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)) { if (!(needed && x >= 0)) {
return window.scrollBy(0, x); return window.scrollBy(0, x);
} }
@ -4284,6 +4316,15 @@
} }
return bottom; 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) { addShortcut: function(el, icon) {
$.addClass(el, 'shortcut'); $.addClass(el, 'shortcut');
return $.add(Header[icon ? 'icons' : 'stats'], el); return $.add(Header[icon ? 'icons' : 'stats'], el);
@ -4731,7 +4772,7 @@
Index.board = "" + g.BOARD; Index.board = "" + g.BOARD;
try { try {
if (req.status === 200) { if (req.status === 200) {
Index.parse(JSON.parse(req.response), pageNum); Index.parse(req.response, pageNum);
} else if (req.status === 304 && (pageNum != null)) { } else if (req.status === 304 && (pageNum != null)) {
Index.pageNav(pageNum); Index.pageNav(pageNum);
} }
@ -5009,6 +5050,9 @@
pageNum = Index.getCurrentPage(); pageNum = Index.getCurrentPage();
} }
} else { } else {
if (!Index.searchInput.dataset.searching) {
return;
}
pageNum = Index.pageBeforeSearch; pageNum = Index.pageBeforeSearch;
delete Index.pageBeforeSearch; delete Index.pageBeforeSearch;
Index.searchInput.removeAttribute('data-searching'); Index.searchInput.removeAttribute('data-searching');
@ -5378,6 +5422,7 @@
return $.cache(url, function() { return $.cache(url, function() {
return Get.archivedPost(this, boardID, postID, root, context); return Get.archivedPost(this, boardID, postID, root, context);
}, { }, {
responseType: 'json',
withCredentials: url.archive.withCredentials withCredentials: url.archive.withCredentials
}); });
} }
@ -5418,7 +5463,7 @@
} }
return; return;
} }
posts = JSON.parse(req.response).posts; posts = req.response.posts;
Build.spoilerRange[boardID] = posts[0].custom_spoiler; Build.spoilerRange[boardID] = posts[0].custom_spoiler;
for (_i = 0, _len = posts.length; _i < _len; _i++) { for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i]; post = posts[_i];
@ -5454,7 +5499,7 @@
Get.insert(post, root, context); Get.insert(post, root, context);
return; return;
} }
data = JSON.parse(req.response); data = req.response;
if (data.error) { if (data.error) {
$.addClass(root, 'warning'); $.addClass(root, 'warning');
root.textContent = data.error; root.textContent = data.error;
@ -5559,6 +5604,7 @@
this.type = type; this.type = type;
this.rmEntry = __bind(this.rmEntry, this); this.rmEntry = __bind(this.rmEntry, this);
this.addEntry = __bind(this.addEntry, this); this.addEntry = __bind(this.addEntry, this);
this.onFocus = __bind(this.onFocus, this);
this.keybinds = __bind(this.keybinds, this); this.keybinds = __bind(this.keybinds, this);
this.close = __bind(this.close, this); this.close = __bind(this.close, this);
$.on(d, 'AddMenuEntry', this.addEntry); $.on(d, 'AddMenuEntry', this.addEntry);
@ -5723,6 +5769,11 @@
return e.stopPropagation(); return e.stopPropagation();
}; };
Menu.prototype.onFocus = function(e) {
e.stopPropagation();
return this.focus(e.target);
};
Menu.prototype.focus = function(entry) { Menu.prototype.focus = function(entry) {
var bottom, cHeight, cWidth, eRect, focused, left, right, sRect, style, submenu, top, _i, _len, _ref, _ref1, _ref2; 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)) { while (focused = $.x('parent::*/child::*[contains(@class,"focused")]', entry)) {
@ -5774,10 +5825,7 @@
var el, subEntries, subEntry, _i, _len; var el, subEntries, subEntry, _i, _len;
el = entry.el, subEntries = entry.subEntries; el = entry.el, subEntries = entry.subEntries;
$.addClass(el, 'entry'); $.addClass(el, 'entry');
$.on(el, 'focus mouseover', (function(e) { $.on(el, 'focus mouseover', this.onFocus);
e.stopPropagation();
return this.focus(el);
}).bind(this));
el.style.order = entry.order || 100; el.style.order = entry.order || 100;
if (!subEntries) { if (!subEntries) {
return; return;
@ -6684,7 +6732,7 @@
return; return;
} }
threads = {}; threads = {};
_ref = JSON.parse(this.response); _ref = this.response;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = _ref[_i]; page = _ref[_i];
_ref1 = page.threads; _ref1 = page.threads;
@ -7888,7 +7936,7 @@
switch (response.status) { switch (response.status) {
case 200: case 200:
case 304: case 304:
text = "" + (service.text(JSON.parse(response.responseText))); text = "" + (service.text(response.response));
if (Conf['Embedding']) { if (Conf['Embedding']) {
embed.dataset.title = text; embed.dataset.title = text;
} }
@ -7984,7 +8032,7 @@
if (status !== 200 && status !== 304) { if (status !== 200 && status !== 304) {
return div.innerHTML = "ERROR " + status; 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']; _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++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
type = _ref[_i]; type = _ref[_i];
@ -8889,7 +8937,7 @@
QR.cooldown.set({ QR.cooldown.set({
delay: 2 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.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true;
QR.cooldown.set({ QR.cooldown.set({
delay: m[1] delay: m[1]
@ -10028,12 +10076,13 @@
} }
return $.ajax("//api.4chan.org/" + post.board + "/res/" + post.thread + ".json", { return $.ajax("//api.4chan.org/" + post.board + "/res/" + post.thread + ".json", {
onload: function() { onload: function() {
var i, postObj; var i, postObj, posts;
if (this.status !== 200) { if (this.status !== 200) {
return; return;
} }
i = 0; i = 0;
while (postObj = JSON.parse(this.response).posts[i++]) { posts = this.response.posts;
while (postObj = posts[i++]) {
if (postObj.no === post.ID) { if (postObj.no === post.ID) {
break; break;
} }
@ -10310,7 +10359,7 @@
if (this.status !== 200) { if (this.status !== 200) {
return; return;
} }
_ref = JSON.parse(this.response).posts; _ref = this.response.posts;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
postObj = _ref[_i]; postObj = _ref[_i];
if (postObj.no === post.ID) { if (postObj.no === post.ID) {
@ -10438,7 +10487,7 @@
if (this.status !== 200) { if (this.status !== 200) {
return; return;
} }
_ref = JSON.parse(this.response).posts; _ref = this.response.posts;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
postObj = _ref[_i]; postObj = _ref[_i];
if (postObj.no === post.ID) { if (postObj.no === post.ID) {
@ -11061,21 +11110,21 @@
}); });
}, },
onThreadsLoad: function() { 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)) { if (!(Conf["Page Count in Stats"] && this.status === 200)) {
return; return;
} }
pages = JSON.parse(this.response); _ref = this.response;
for (_i = 0, _len = pages.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = pages[_i]; page = _ref[_i];
_ref = page.threads; _ref1 = page.threads;
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
thread = _ref[_j]; thread = _ref1[_j];
if (!(thread.no === ThreadStats.thread.ID)) { if (!(thread.no === ThreadStats.thread.ID)) {
continue; continue;
} }
ThreadStats.pageCountEl.textContent = page.page; 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; return;
} }
} }
@ -11279,7 +11328,7 @@
switch (req.status) { switch (req.status) {
case 200: case 200:
g.DEAD = false; g.DEAD = false;
ThreadUpdater.parse(JSON.parse(req.response).posts); ThreadUpdater.parse(req.response.posts);
ThreadUpdater.setInterval(); ThreadUpdater.setInterval();
break; break;
case 404: case 404:
@ -12334,8 +12383,8 @@
} }
}, { }, {
name: "4plebs", name: "4plebs",
boards: ["hr", "o", "pol", "s4s", "tg", "tv", "x"], boards: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"],
files: ["hr", "o", "pol", "s4s", "tg", "tv", "x"], files: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"],
data: { data: {
domain: "archive.4plebs.org", domain: "archive.4plebs.org",
http: true, http: true,
@ -12352,6 +12401,16 @@
https: true, https: true,
software: "foolfuuka" 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", name: "Install Gentoo",
boards: ["diy", "g", "sci"], boards: ["diy", "g", "sci"],
@ -12367,7 +12426,7 @@
boards: ["cgl", "g", "mu", "w"], boards: ["cgl", "g", "mu", "w"],
files: ["cgl", "g", "mu", "w"], files: ["cgl", "g", "mu", "w"],
data: { data: {
domain: "rbt.asia", domain: "archive.rebeccablacktech.com",
http: true, http: true,
https: true, https: true,
software: "fuuka" software: "fuuka"
@ -12387,10 +12446,37 @@
files: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"], files: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"],
data: { data: {
domain: "fuuka.warosu.org", domain: "fuuka.warosu.org",
http: true,
https: true, https: true,
software: "fuuka" 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", name: "Foolz Beta",
boards: ["a", "co", "d", "gd", "h", "jp", "m", "mlp", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], boards: ["a", "co", "d", "gd", "h", "jp", "m", "mlp", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"],
@ -12402,16 +12488,6 @@
withCredentials: true, withCredentials: true,
software: "foolfuuka" 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) { to: function(dest, data) {
@ -14503,18 +14579,21 @@
return a.textContent = ExpandThread.text('+', postsCount, filesCount); return a.textContent = ExpandThread.text('+', postsCount, filesCount);
}, },
parse: function(req, thread, a) { 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) { if ((_ref = req.status) !== 200 && _ref !== 304) {
a.textContent = "Error " + req.statusText + " (" + req.status + ")"; a.textContent = "Error " + req.statusText + " (" + req.status + ")";
return; return;
} }
data = JSON.parse(req.response).posts; Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler;
Build.spoilerRange[thread.board] = data.shift().custom_spoiler;
posts = []; posts = [];
postsRoot = []; postsRoot = [];
filesCount = 0; filesCount = 0;
for (_i = 0, _len = data.length; _i < _len; _i++) { _ref1 = req.response.posts;
postData = data[_i]; 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 (post = thread.posts[postData.no]) {
if ('file' in post) { if ('file' in post) {
filesCount++; filesCount++;
@ -15643,8 +15722,6 @@
} }
}, },
clean: function() { clean: function() {
var posts, threads;
posts = g.posts, threads = g.threads;
g.threads.forEach(function(thread) { g.threads.forEach(function(thread) {
return thread.collect(); return thread.collect();
}); });
@ -15746,7 +15823,6 @@
}, },
updateBoard: function(boardID) { updateBoard: function(boardID) {
var fullBoardList, onload, req; var fullBoardList, onload, req;
req = null;
fullBoardList = $('#full-board-list', Header.boardList); fullBoardList = $('#full-board-list', Header.boardList);
$.rmClass($('.current', fullBoardList), 'current'); $.rmClass($('.current', fullBoardList), 'current');
$.addClass($("a[href*='/" + boardID + "/']", fullBoardList), 'current'); $.addClass($("a[href*='/" + boardID + "/']", fullBoardList), 'current');
@ -15762,7 +15838,7 @@
return; return;
} }
try { try {
_ref = JSON.parse(req.response).boards; _ref = req.response.boards;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
aboard = _ref[_i]; aboard = _ref[_i];
if (!(aboard.board === boardID)) { if (!(aboard.board === boardID)) {
@ -15905,7 +15981,7 @@
} }
Navigate.title(); Navigate.title();
try { try {
return Navigate.parse(JSON.parse(req.response).posts); return Navigate.parse(req.response.posts);
} catch (_error) { } catch (_error) {
err = _error; err = _error;
console.error('Navigate failure:'); console.error('Navigate failure:');
@ -15983,59 +16059,27 @@
Settings = { Settings = {
init: function() { init: function() {
var addSection, check, el, key, settings, value, _ref; var check, el, settings,
_this = this;
el = $.el('a', { el = $.el('a', {
className: 'settings-link', className: 'settings-link',
href: 'javascript:;', href: 'javascript:;',
textContent: 'Settings' textContent: 'Settings'
}); });
$.on(el, 'click', Settings.open); $.on(el, 'click', this.open);
$.event('AddMenuEntry', { $.event('AddMenuEntry', {
type: 'header', type: 'header',
el: el, el: el,
order: 1 order: 1
}); });
$.get('previousversion', null, function(item) { this.addSection('Main', this.main);
var changelog, curr, prev, previous; this.addSection('Filter', this.filter);
if (previous = item['previousversion']) { this.addSection('Sauce', this.sauce);
if (previous === g.VERSION) { this.addSection('Advanced', this.advanced);
return; this.addSection('Keybinds', this.keybinds);
} $.on(d, 'AddSettingsSection', this.addSection);
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 <a href='" + changelog + "' target=_blank>version " + g.VERSION + "</a>."
});
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);
$.on(d, 'OpenSettings', function(e) { $.on(d, 'OpenSettings', function(e) {
return Settings.open(e.detail); return _this.open(e.detail);
}); });
settings = JSON.parse(localStorage.getItem('4chan-settings')) || {}; settings = JSON.parse(localStorage.getItem('4chan-settings')) || {};
if (!settings.disableAll) { if (!settings.disableAll) {
@ -16071,13 +16115,14 @@
Settings.dialog = dialog = $.el('div', { Settings.dialog = dialog = $.el('div', {
id: 'appchanx-settings', id: 'appchanx-settings',
"class": 'dialog', "class": 'dialog',
innerHTML: "<nav><div class=sections-list></div><span class='imp-exp-result warning'></span><div class=credits><a class=export>Export</a> |<a class=import>Import</a> |<input type=file style='display: none;'><a href='http://zixaphir.github.com/appchan-x/' target=_blank>appchan x</a> |<a href='https://github.com/zixaphir/appchan-x/blob/master/CHANGELOG.md' target=_blank>" + g.VERSION + "</a> |<a href='https://github.com/zixaphir/appchan-x/blob/master/README.md#reporting-bugs-and-suggestions' target=_blank>Issues</a> |<a href=javascript:; class='close fa' title=Close>\uf00d</a></div></nav><hr><div class=section-container><section></section></div>" innerHTML: "<nav><div class=sections-list></div><span class='imp-exp-result warning'></span><div class=credits><a class=export>Export</a>&nbsp|&nbsp<a class=import>Import</a>&nbsp|&nbsp<a class=reset>Reset Settings</a>&nbsp|&nbsp<input type=file hidden><a href='http://zixaphir.github.com/appchan-x/' target=_blank>appchan x</a> |<a href='https://github.com/zixaphir/appchan-x/blob/master/CHANGELOG.md' target=_blank>" + g.VERSION + "</a> |<a href='https://github.com/zixaphir/appchan-x/blob/master/README.md#reporting-bugs-and-suggestions' target=_blank>Issues</a> |<a href=javascript:; class='close fa' title=Close>\uf00d</a></div></nav><hr><div class=section-container><section></section></div>"
}); });
Settings.overlay = overlay = $.el('div', { Settings.overlay = overlay = $.el('div', {
id: 'overlay' id: 'overlay'
}); });
$.on($('.export', dialog), 'click', Settings["export"]); $.on($('.export', dialog), 'click', Settings["export"]);
$.on($('.import', dialog), 'click', Settings["import"]); $.on($('.import', dialog), 'click', Settings["import"]);
$.on($('.reset', dialog), 'click', Settings.reset);
$.on($('input', dialog), 'change', Settings.onImport); $.on($('input', dialog), 'change', Settings.onImport);
links = []; links = [];
_ref = Settings.sections; _ref = Settings.sections;
@ -16137,7 +16182,7 @@
return $.event('OpenSettings', null, section); return $.event('OpenSettings', null, section);
}, },
main: function(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 = {}; items = {};
inputs = {}; inputs = {};
_ref = Config.main; _ref = Config.main;
@ -16173,45 +16218,34 @@
innerHTML: "<button></button><span class=description>: Clear manually-hidden threads and posts on all boards. Reload the page to apply." innerHTML: "<button></button><span class=description>: Clear manually-hidden threads and posts on all boards. Reload the page to apply."
}); });
button = $('button', div); button = $('button', div);
hiddenNum = 0; $.get({
$.get('hiddenThreads', { hiddenThreads: {},
boards: {} hiddenPosts: {}
}, function(item) { }, function(_arg) {
var ID, board, thread, _ref1; var ID, board, hiddenNum, hiddenPosts, hiddenThreads, thread, _ref1, _ref2;
_ref1 = item.hiddenThreads.boards; hiddenThreads = _arg.hiddenThreads, hiddenPosts = _arg.hiddenPosts;
hiddenNum = 0;
_ref1 = hiddenThreads.boards;
for (ID in _ref1) { for (ID in _ref1) {
board = _ref1[ID]; board = _ref1[ID];
for (ID in board) { hiddenNum += Object.keys(board).length;
thread = board[ID];
hiddenNum++;
}
} }
return button.textContent = "Hidden: " + hiddenNum; _ref2 = hiddenPosts.boards;
}); for (ID in _ref2) {
$.get('hiddenPosts', { board = _ref2[ID];
boards: {}
}, function(item) {
var ID, board, post, thread, _ref1;
_ref1 = item.hiddenPosts.boards;
for (ID in _ref1) {
board = _ref1[ID];
for (ID in board) { for (ID in board) {
thread = board[ID]; thread = board[ID];
for (ID in thread) { hiddenNum += Object.keys(thread).length;
post = thread[ID];
hiddenNum++;
}
} }
} }
return button.textContent = "Hidden: " + hiddenNum; return button.textContent = "Hidden: " + hiddenNum;
}); });
$.on(button, 'click', function() { $.on(button, 'click', function() {
this.textContent = 'Hidden: 0'; this.textContent = 'Hidden: 0';
return $.get('hiddenThreads', { return $.get('hiddenThreads', {}, function(_arg) {
boards: {} var boardID, hiddenThreads;
}, function(item) { hiddenThreads = _arg.hiddenThreads;
var boardID; for (boardID in hiddenThreads.boards) {
for (boardID in item.hiddenThreads.boards) {
localStorage.removeItem("4chan-hide-t-" + boardID); localStorage.removeItem("4chan-hide-t-" + boardID);
} }
return $["delete"](['hiddenThreads', 'hiddenPosts']); return $["delete"](['hiddenThreads', 'hiddenPosts']);
@ -16219,41 +16253,29 @@
}); });
return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div); return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div);
}, },
"export": function(now, data) { "export": function() {
var a, db, span, _i, _len, _ref; return $.get(Conf, function(Conf) {
if (typeof now !== 'number') { delete Conf['archives'];
now = Date.now(); return Settings.downloadExport({
data = {
version: g.VERSION, version: g.VERSION,
date: now date: Date.now(),
}; Conf: Conf
_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);
}); });
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); downloadExport: function(data) {
return $.add(span, a); 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() { "import": function() {
return this.nextElementSibling.click(); return $('input', this.parentNode).click();
}, },
onImport: function() { onImport: function() {
var file, output, reader; var file, output, reader;
@ -16267,10 +16289,9 @@
} }
reader = new FileReader(); reader = new FileReader();
reader.onload = function(e) { reader.onload = function(e) {
var data, err; var err;
try { try {
data = JSON.parse(e.target.result); Settings.loadSettings(JSON.parse(e.target.result));
Settings.loadSettings(data);
if (confirm('Import successful. Reload now?')) { if (confirm('Import successful. Reload now?')) {
return window.location.reload(); return window.location.reload();
} }
@ -16291,16 +16312,14 @@
} }
return $.set(data.Conf); return $.set(data.Conf);
}, },
convertSettings: function(data, map) { reset: function() {
var newKey, prevKey; if (confirm('Your current settings will be entirely wiped, are you sure?')) {
for (prevKey in map) { return $.clear(function() {
newKey = map[prevKey]; if (confirm('Reset successful. Reload now?')) {
if (newKey) { return window.location.reload();
data.Conf[newKey] = data.Conf[prevKey]; }
} });
delete data.Conf[prevKey];
} }
return data;
}, },
filter: function(section) { filter: function(section) {
var select; var select;
@ -17233,7 +17252,7 @@
return $.ready(Main.initReady); return $.ready(Main.initReady);
}, },
initReady: function() { 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 ((_ref = d.title) === '4chan - Temporarily Offline' || _ref === '4chan - 404 Not Found') {
if (Conf['404 Redirect'] && g.VIEW === 'thread') { if (Conf['404 Redirect'] && g.VIEW === 'thread') {
href = Redirect.to('thread', { href = Redirect.to('thread', {
@ -17260,14 +17279,19 @@
} else { } else {
$.event('4chanXInitFinished'); $.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('.'); GMver = GM_info.version.split('.');
_ref1 = "1.13".split('.'); _ref1 = "1.14".split('.');
for (i = _i = 0, _len = _ref1.length; _i < _len; i = ++_i) { for (i = _i = 0, _len = _ref1.length; _i < _len; i = ++_i) {
v = _ref1[i]; v = _ref1[i];
if (v === GMver[i]) { if (v === GMver[i]) {
continue; 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; break;
} }
try { try {
@ -17308,10 +17332,27 @@
Main.handleErrors(errors); Main.handleErrors(errors);
} }
Main.callbackNodes(Thread, threads); Main.callbackNodes(Thread, threads);
return Main.callbackNodesDB(Post, posts, function() { Main.callbackNodesDB(Post, posts, function() {
return $.event('4chanXInitFinished'); 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 <a href='" + changelog + "' target=_blank>version " + g.VERSION + "</a>."
});
new Notice('info', el, 15);
} else {
Settings.open();
}
return $.set('previousversion', g.VERSION);
});
}, },
callbackNodes: function(klass, nodes) { callbackNodes: function(klass, nodes) {
var cb, i, node; var cb, i, node;

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript // 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. * Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE * https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@ -89,8 +89,8 @@
(function() { (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, 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, __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, __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; }, __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); }; }; __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
@ -2602,6 +2602,9 @@
return lastModified[url] = r.getResponseHeader('Last-Modified'); return lastModified[url] = r.getResponseHeader('Last-Modified');
}); });
} }
if (/\.json$/.test(url)) {
r.responseType = 'json';
}
$.extend(r, options); $.extend(r, options);
$.extend(r.upload, upCallbacks); $.extend(r.upload, upCallbacks);
r.send(form); r.send(form);
@ -2690,12 +2693,16 @@
return d.evaluate(path, root, null, 7, null); return d.evaluate(path, root, null, 7, null);
}; };
$.addClass = function(el, className) { $.addClass = function() {
return el.classList.add(className); 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) { $.rmClass = function() {
return el.classList.remove(className); 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) { $.toggleClass = function(el, className) {
@ -2720,12 +2727,7 @@
})(); })();
$.rmAll = function(root) { $.rmAll = function(root) {
var node, _i, _len, _ref; return root.textContent = '';
_ref = __slice.call(root.childNodes);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
root.removeChild(node);
}
}; };
$.tn = function(s) { $.tn = function(s) {
@ -2952,24 +2954,48 @@
}; };
$.set = (function() { $.set = (function() {
var items, localItems, set; var items, setAll, setArea, timeout;
items = {}; items = {
localItems = {}; sync: {},
set = $.debounce($.SECOND, function() { 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; var err, key, _i, _len, _ref;
_ref = $.localKeys; _ref = $.localKeys;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
key = _ref[_i]; key = _ref[_i];
if (key in items) { if (key in items.sync) {
(localItems || (localItems = {}))[key] = items[key]; items.local[key] = items.sync[key];
delete items[key]; delete items.sync[key];
} }
} }
try { try {
chrome.storage.local.set(localItems); setArea('local');
chrome.storage.sync.set(items); return setArea('sync');
items = {};
return localItems = {};
} catch (_error) { } catch (_error) {
err = _error; err = _error;
return c.error(err.stack); return c.error(err.stack);
@ -2977,14 +3003,30 @@
}); });
return function(key, val) { return function(key, val) {
if (typeof key === 'string') { if (typeof key === 'string') {
items[key] = val; items.sync[key] = val;
} else { } 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) { $.remove = function(arr, value) {
var i; var i;
i = arr.indexOf(value); i = arr.indexOf(value);
@ -3628,7 +3670,7 @@
} }
board = _this.data.boards[boardID]; board = _this.data.boards[boardID];
threads = {}; threads = {};
_ref = JSON.parse(e.target.response); _ref = e.target.response;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = _ref[_i]; page = _ref[_i];
_ref1 = page.threads; _ref1 = page.threads;
@ -4200,18 +4242,16 @@
toggleHideBarOnScroll: function(e) { toggleHideBarOnScroll: function(e) {
var hide; var hide;
hide = this.checked; hide = this.checked;
$.set('Header auto-hide on scroll', hide); $.cb.checked.call(this);
return Header.setHideBarOnScroll(hide); return Header.setHideBarOnScroll(hide);
}, },
hideBarOnScroll: function() { hideBarOnScroll: function() {
var offsetY; var offsetY;
offsetY = window.pageYOffset; offsetY = window.pageYOffset;
if (offsetY > (Header.previousOffset || 0)) { if (offsetY > (Header.previousOffset || 0)) {
$.addClass(Header.bar, 'autohide'); $.addClass(Header.bar, 'autohide', 'scroll');
$.addClass(Header.bar, 'scroll');
} else { } else {
$.rmClass(Header.bar, 'autohide'); $.rmClass(Header.bar, 'autohide', 'scroll');
$.rmClass(Header.bar, 'scroll');
} }
return Header.previousOffset = offsetY; return Header.previousOffset = offsetY;
}, },
@ -4259,14 +4299,38 @@
return Header.scrollTo(post); return Header.scrollTo(post);
}, },
scrollTo: function(root, down, needed) { scrollTo: function(root, down, needed) {
var x; var height, x;
if (down) { if (down) {
x = Header.getBottomOf(root); 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)) { if (!(needed && x >= 0)) {
return window.scrollBy(0, -x); return window.scrollBy(0, -x);
} }
} else { } else {
x = Header.getTopOf(root); 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)) { if (!(needed && x >= 0)) {
return window.scrollBy(0, x); return window.scrollBy(0, x);
} }
@ -4294,6 +4358,15 @@
} }
return bottom; 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) { addShortcut: function(el, icon) {
$.addClass(el, 'shortcut'); $.addClass(el, 'shortcut');
return $.add(Header[icon ? 'icons' : 'stats'], el); return $.add(Header[icon ? 'icons' : 'stats'], el);
@ -4741,7 +4814,7 @@
Index.board = "" + g.BOARD; Index.board = "" + g.BOARD;
try { try {
if (req.status === 200) { if (req.status === 200) {
Index.parse(JSON.parse(req.response), pageNum); Index.parse(req.response, pageNum);
} else if (req.status === 304 && (pageNum != null)) { } else if (req.status === 304 && (pageNum != null)) {
Index.pageNav(pageNum); Index.pageNav(pageNum);
} }
@ -5019,6 +5092,9 @@
pageNum = Index.getCurrentPage(); pageNum = Index.getCurrentPage();
} }
} else { } else {
if (!Index.searchInput.dataset.searching) {
return;
}
pageNum = Index.pageBeforeSearch; pageNum = Index.pageBeforeSearch;
delete Index.pageBeforeSearch; delete Index.pageBeforeSearch;
delete Index.searchInput.dataset.searching; delete Index.searchInput.dataset.searching;
@ -5388,6 +5464,7 @@
return $.cache(url, function() { return $.cache(url, function() {
return Get.archivedPost(this, boardID, postID, root, context); return Get.archivedPost(this, boardID, postID, root, context);
}, { }, {
responseType: 'json',
withCredentials: url.archive.withCredentials withCredentials: url.archive.withCredentials
}); });
} }
@ -5428,7 +5505,7 @@
} }
return; return;
} }
posts = JSON.parse(req.response).posts; posts = req.response.posts;
Build.spoilerRange[boardID] = posts[0].custom_spoiler; Build.spoilerRange[boardID] = posts[0].custom_spoiler;
for (_i = 0, _len = posts.length; _i < _len; _i++) { for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i]; post = posts[_i];
@ -5464,7 +5541,7 @@
Get.insert(post, root, context); Get.insert(post, root, context);
return; return;
} }
data = JSON.parse(req.response); data = req.response;
if (data.error) { if (data.error) {
$.addClass(root, 'warning'); $.addClass(root, 'warning');
root.textContent = data.error; root.textContent = data.error;
@ -5569,6 +5646,7 @@
this.type = type; this.type = type;
this.rmEntry = __bind(this.rmEntry, this); this.rmEntry = __bind(this.rmEntry, this);
this.addEntry = __bind(this.addEntry, this); this.addEntry = __bind(this.addEntry, this);
this.onFocus = __bind(this.onFocus, this);
this.keybinds = __bind(this.keybinds, this); this.keybinds = __bind(this.keybinds, this);
this.close = __bind(this.close, this); this.close = __bind(this.close, this);
$.on(d, 'AddMenuEntry', this.addEntry); $.on(d, 'AddMenuEntry', this.addEntry);
@ -5733,6 +5811,11 @@
return e.stopPropagation(); return e.stopPropagation();
}; };
Menu.prototype.onFocus = function(e) {
e.stopPropagation();
return this.focus(e.target);
};
Menu.prototype.focus = function(entry) { Menu.prototype.focus = function(entry) {
var bottom, cHeight, cWidth, eRect, focused, left, right, sRect, style, submenu, top, _i, _len, _ref, _ref1, _ref2; 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)) { while (focused = $.x('parent::*/child::*[contains(@class,"focused")]', entry)) {
@ -5784,10 +5867,7 @@
var el, subEntries, subEntry, _i, _len; var el, subEntries, subEntry, _i, _len;
el = entry.el, subEntries = entry.subEntries; el = entry.el, subEntries = entry.subEntries;
$.addClass(el, 'entry'); $.addClass(el, 'entry');
$.on(el, 'focus mouseover', (function(e) { $.on(el, 'focus mouseover', this.onFocus);
e.stopPropagation();
return this.focus(el);
}).bind(this));
el.style.order = entry.order || 100; el.style.order = entry.order || 100;
if (!subEntries) { if (!subEntries) {
return; return;
@ -6687,7 +6767,7 @@
return; return;
} }
threads = {}; threads = {};
_ref = JSON.parse(this.response); _ref = this.response;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = _ref[_i]; page = _ref[_i];
_ref1 = page.threads; _ref1 = page.threads;
@ -7891,7 +7971,7 @@
switch (response.status) { switch (response.status) {
case 200: case 200:
case 304: case 304:
text = "" + (service.text(JSON.parse(response.responseText))); text = "" + (service.text(response.response));
if (Conf['Embedding']) { if (Conf['Embedding']) {
embed.dataset.title = text; embed.dataset.title = text;
} }
@ -7987,7 +8067,7 @@
if (status !== 200 && status !== 304) { if (status !== 200 && status !== 304) {
return div.innerHTML = "ERROR " + status; 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']; _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++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
type = _ref[_i]; type = _ref[_i];
@ -8880,7 +8960,7 @@
QR.cooldown.set({ QR.cooldown.set({
delay: 2 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.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true;
QR.cooldown.set({ QR.cooldown.set({
delay: m[1] delay: m[1]
@ -10011,12 +10091,13 @@
} }
return $.ajax("//api.4chan.org/" + post.board + "/res/" + post.thread + ".json", { return $.ajax("//api.4chan.org/" + post.board + "/res/" + post.thread + ".json", {
onload: function() { onload: function() {
var i, postObj; var i, postObj, posts;
if (this.status !== 200) { if (this.status !== 200) {
return; return;
} }
i = 0; i = 0;
while (postObj = JSON.parse(this.response).posts[i++]) { posts = this.response.posts;
while (postObj = posts[i++]) {
if (postObj.no === post.ID) { if (postObj.no === post.ID) {
break; break;
} }
@ -10287,27 +10368,16 @@
} }
} }
timeoutID = setTimeout(ImageExpand.expand, 10000, post); timeoutID = setTimeout(ImageExpand.expand, 10000, post);
return $.ajax("//a.4cdn.org/" + post.board + "/res/" + post.thread + ".json", { return $.ajax(this.src, {
onload: function() { onloadend: function() {
var postObj, _i, _len, _ref; if (this.status !== 404) {
if (this.status !== 200) {
return; return;
} }
_ref = JSON.parse(this.response).posts; clearTimeout(timeoutID);
for (_i = 0, _len = _ref.length; _i < _len; _i++) { return post.kill(true);
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);
}
} }
}, {
type: 'head'
}); });
}, },
menu: { menu: {
@ -10415,27 +10485,16 @@
timeoutID = setTimeout((function() { timeoutID = setTimeout((function() {
return _this.src = post.file.URL + '?' + Date.now(); return _this.src = post.file.URL + '?' + Date.now();
}), 3000); }), 3000);
return $.ajax("//a.4cdn.org/" + post.board + "/res/" + post.thread + ".json", { return $.ajax(this.src, {
onload: function() { onloadend: function() {
var postObj, _i, _len, _ref; if (this.status !== 404) {
if (this.status !== 200) {
return; return;
} }
_ref = JSON.parse(this.response).posts; clearTimeout(timeoutID);
for (_i = 0, _len = _ref.length; _i < _len; _i++) { return post.kill(true);
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);
}
} }
}, {
type: 'head'
}); });
} }
}; };
@ -11044,21 +11103,21 @@
}); });
}, },
onThreadsLoad: function() { 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)) { if (!(Conf["Page Count in Stats"] && this.status === 200)) {
return; return;
} }
pages = JSON.parse(this.response); _ref = this.response;
for (_i = 0, _len = pages.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = pages[_i]; page = _ref[_i];
_ref = page.threads; _ref1 = page.threads;
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
thread = _ref[_j]; thread = _ref1[_j];
if (!(thread.no === ThreadStats.thread.ID)) { if (!(thread.no === ThreadStats.thread.ID)) {
continue; continue;
} }
ThreadStats.pageCountEl.textContent = page.page; 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; return;
} }
} }
@ -11262,7 +11321,7 @@
switch (req.status) { switch (req.status) {
case 200: case 200:
g.DEAD = false; g.DEAD = false;
ThreadUpdater.parse(JSON.parse(req.response).posts); ThreadUpdater.parse(req.response.posts);
ThreadUpdater.setInterval(); ThreadUpdater.setInterval();
break; break;
case 404: case 404:
@ -12323,8 +12382,8 @@
} }
}, { }, {
name: "4plebs", name: "4plebs",
boards: ["hr", "o", "pol", "s4s", "tg", "tv", "x"], boards: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"],
files: ["hr", "o", "pol", "s4s", "tg", "tv", "x"], files: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"],
data: { data: {
domain: "archive.4plebs.org", domain: "archive.4plebs.org",
http: true, http: true,
@ -12341,6 +12400,16 @@
https: true, https: true,
software: "foolfuuka" 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", name: "Install Gentoo",
boards: ["diy", "g", "sci"], boards: ["diy", "g", "sci"],
@ -12356,7 +12425,7 @@
boards: ["cgl", "g", "mu", "w"], boards: ["cgl", "g", "mu", "w"],
files: ["cgl", "g", "mu", "w"], files: ["cgl", "g", "mu", "w"],
data: { data: {
domain: "rbt.asia", domain: "archive.rebeccablacktech.com",
http: true, http: true,
https: true, https: true,
software: "fuuka" software: "fuuka"
@ -12376,10 +12445,37 @@
files: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"], files: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"],
data: { data: {
domain: "fuuka.warosu.org", domain: "fuuka.warosu.org",
http: true,
https: true, https: true,
software: "fuuka" 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", name: "Foolz Beta",
boards: ["a", "co", "d", "gd", "h", "jp", "m", "mlp", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], boards: ["a", "co", "d", "gd", "h", "jp", "m", "mlp", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"],
@ -12391,16 +12487,6 @@
withCredentials: true, withCredentials: true,
software: "foolfuuka" 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) { to: function(dest, data) {
@ -14498,18 +14584,21 @@
return a.textContent = ExpandThread.text('+', postsCount, filesCount); return a.textContent = ExpandThread.text('+', postsCount, filesCount);
}, },
parse: function(req, thread, a) { 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) { if ((_ref = req.status) !== 200 && _ref !== 304) {
a.textContent = "Error " + req.statusText + " (" + req.status + ")"; a.textContent = "Error " + req.statusText + " (" + req.status + ")";
return; return;
} }
data = JSON.parse(req.response).posts; Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler;
Build.spoilerRange[thread.board] = data.shift().custom_spoiler;
posts = []; posts = [];
postsRoot = []; postsRoot = [];
filesCount = 0; filesCount = 0;
for (_i = 0, _len = data.length; _i < _len; _i++) { _ref1 = req.response.posts;
postData = data[_i]; 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 (post = thread.posts[postData.no]) {
if ('file' in post) { if ('file' in post) {
filesCount++; filesCount++;
@ -15638,8 +15727,6 @@
} }
}, },
clean: function() { clean: function() {
var posts, threads;
posts = g.posts, threads = g.threads;
g.threads.forEach(function(thread) { g.threads.forEach(function(thread) {
return thread.collect(); return thread.collect();
}); });
@ -15741,7 +15828,6 @@
}, },
updateBoard: function(boardID) { updateBoard: function(boardID) {
var fullBoardList, onload, req; var fullBoardList, onload, req;
req = null;
fullBoardList = $('#full-board-list', Header.boardList); fullBoardList = $('#full-board-list', Header.boardList);
$.rmClass($('.current', fullBoardList), 'current'); $.rmClass($('.current', fullBoardList), 'current');
$.addClass($("a[href*='/" + boardID + "/']", fullBoardList), 'current'); $.addClass($("a[href*='/" + boardID + "/']", fullBoardList), 'current');
@ -15757,7 +15843,7 @@
return; return;
} }
try { try {
_ref = JSON.parse(req.response).boards; _ref = req.response.boards;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
aboard = _ref[_i]; aboard = _ref[_i];
if (!(aboard.board === boardID)) { if (!(aboard.board === boardID)) {
@ -15900,7 +15986,7 @@
} }
Navigate.title(); Navigate.title();
try { try {
return Navigate.parse(JSON.parse(req.response).posts); return Navigate.parse(req.response.posts);
} catch (_error) { } catch (_error) {
err = _error; err = _error;
console.error('Navigate failure:'); console.error('Navigate failure:');
@ -15978,59 +16064,27 @@
Settings = { Settings = {
init: function() { init: function() {
var addSection, check, el, key, settings, value, _ref; var check, el, settings,
_this = this;
el = $.el('a', { el = $.el('a', {
className: 'settings-link', className: 'settings-link',
href: 'javascript:;', href: 'javascript:;',
textContent: 'Settings' textContent: 'Settings'
}); });
$.on(el, 'click', Settings.open); $.on(el, 'click', this.open);
$.event('AddMenuEntry', { $.event('AddMenuEntry', {
type: 'header', type: 'header',
el: el, el: el,
order: 1 order: 1
}); });
$.get('previousversion', null, function(item) { this.addSection('Main', this.main);
var changelog, curr, prev, previous; this.addSection('Filter', this.filter);
if (previous = item['previousversion']) { this.addSection('Sauce', this.sauce);
if (previous === g.VERSION) { this.addSection('Advanced', this.advanced);
return; this.addSection('Keybinds', this.keybinds);
} $.on(d, 'AddSettingsSection', this.addSection);
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 <a href='" + changelog + "' target=_blank>version " + g.VERSION + "</a>."
});
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);
$.on(d, 'OpenSettings', function(e) { $.on(d, 'OpenSettings', function(e) {
return Settings.open(e.detail); return _this.open(e.detail);
}); });
settings = JSON.parse(localStorage.getItem('4chan-settings')) || {}; settings = JSON.parse(localStorage.getItem('4chan-settings')) || {};
if (!settings.disableAll) { if (!settings.disableAll) {
@ -16066,13 +16120,14 @@
Settings.dialog = dialog = $.el('div', { Settings.dialog = dialog = $.el('div', {
id: 'appchanx-settings', id: 'appchanx-settings',
"class": 'dialog', "class": 'dialog',
innerHTML: "<nav><div class=sections-list></div><span class='imp-exp-result warning'></span><div class=credits><a class=export>Export</a> |<a class=import>Import</a> |<input type=file style='display: none;'><a href='http://zixaphir.github.com/appchan-x/' target=_blank>appchan x</a> |<a href='https://github.com/zixaphir/appchan-x/blob/master/CHANGELOG.md' target=_blank>" + g.VERSION + "</a> |<a href='https://github.com/zixaphir/appchan-x/blob/master/README.md#reporting-bugs-and-suggestions' target=_blank>Issues</a> |<a href=javascript:; class='close fa' title=Close>\uf00d</a></div></nav><hr><div class=section-container><section></section></div>" innerHTML: "<nav><div class=sections-list></div><span class='imp-exp-result warning'></span><div class=credits><a class=export>Export</a>&nbsp|&nbsp<a class=import>Import</a>&nbsp|&nbsp<a class=reset>Reset Settings</a>&nbsp|&nbsp<input type=file hidden><a href='http://zixaphir.github.com/appchan-x/' target=_blank>appchan x</a> |<a href='https://github.com/zixaphir/appchan-x/blob/master/CHANGELOG.md' target=_blank>" + g.VERSION + "</a> |<a href='https://github.com/zixaphir/appchan-x/blob/master/README.md#reporting-bugs-and-suggestions' target=_blank>Issues</a> |<a href=javascript:; class='close fa' title=Close>\uf00d</a></div></nav><hr><div class=section-container><section></section></div>"
}); });
Settings.overlay = overlay = $.el('div', { Settings.overlay = overlay = $.el('div', {
id: 'overlay' id: 'overlay'
}); });
$.on($('.export', dialog), 'click', Settings["export"]); $.on($('.export', dialog), 'click', Settings["export"]);
$.on($('.import', dialog), 'click', Settings["import"]); $.on($('.import', dialog), 'click', Settings["import"]);
$.on($('.reset', dialog), 'click', Settings.reset);
$.on($('input', dialog), 'change', Settings.onImport); $.on($('input', dialog), 'change', Settings.onImport);
links = []; links = [];
_ref = Settings.sections; _ref = Settings.sections;
@ -16132,7 +16187,7 @@
return $.event('OpenSettings', null, section); return $.event('OpenSettings', null, section);
}, },
main: function(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 = {}; items = {};
inputs = {}; inputs = {};
_ref = Config.main; _ref = Config.main;
@ -16168,45 +16223,34 @@
innerHTML: "<button></button><span class=description>: Clear manually-hidden threads and posts on all boards. Reload the page to apply." innerHTML: "<button></button><span class=description>: Clear manually-hidden threads and posts on all boards. Reload the page to apply."
}); });
button = $('button', div); button = $('button', div);
hiddenNum = 0; $.get({
$.get('hiddenThreads', { hiddenThreads: {},
boards: {} hiddenPosts: {}
}, function(item) { }, function(_arg) {
var ID, board, thread, _ref1; var ID, board, hiddenNum, hiddenPosts, hiddenThreads, thread, _ref1, _ref2;
_ref1 = item.hiddenThreads.boards; hiddenThreads = _arg.hiddenThreads, hiddenPosts = _arg.hiddenPosts;
hiddenNum = 0;
_ref1 = hiddenThreads.boards;
for (ID in _ref1) { for (ID in _ref1) {
board = _ref1[ID]; board = _ref1[ID];
for (ID in board) { hiddenNum += Object.keys(board).length;
thread = board[ID];
hiddenNum++;
}
} }
return button.textContent = "Hidden: " + hiddenNum; _ref2 = hiddenPosts.boards;
}); for (ID in _ref2) {
$.get('hiddenPosts', { board = _ref2[ID];
boards: {}
}, function(item) {
var ID, board, post, thread, _ref1;
_ref1 = item.hiddenPosts.boards;
for (ID in _ref1) {
board = _ref1[ID];
for (ID in board) { for (ID in board) {
thread = board[ID]; thread = board[ID];
for (ID in thread) { hiddenNum += Object.keys(thread).length;
post = thread[ID];
hiddenNum++;
}
} }
} }
return button.textContent = "Hidden: " + hiddenNum; return button.textContent = "Hidden: " + hiddenNum;
}); });
$.on(button, 'click', function() { $.on(button, 'click', function() {
this.textContent = 'Hidden: 0'; this.textContent = 'Hidden: 0';
return $.get('hiddenThreads', { return $.get('hiddenThreads', {}, function(_arg) {
boards: {} var boardID, hiddenThreads;
}, function(item) { hiddenThreads = _arg.hiddenThreads;
var boardID; for (boardID in hiddenThreads.boards) {
for (boardID in item.hiddenThreads.boards) {
localStorage.removeItem("4chan-hide-t-" + boardID); localStorage.removeItem("4chan-hide-t-" + boardID);
} }
return $["delete"](['hiddenThreads', 'hiddenPosts']); return $["delete"](['hiddenThreads', 'hiddenPosts']);
@ -16214,39 +16258,26 @@
}); });
return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div); return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div);
}, },
"export": function(now, data) { "export": function() {
var a, db, _i, _len, _ref; return $.get(Conf, function(Conf) {
if (typeof now !== 'number') { delete Conf['archives'];
now = Date.now(); return Settings.downloadExport({
data = {
version: g.VERSION, version: g.VERSION,
date: now date: Date.now(),
}; Conf: Conf
_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);
}); });
return; });
} },
downloadExport: function(data) {
var a;
a = $.el('a', { a = $.el('a', {
className: 'warning', download: "appchan x v" + g.VERSION + "-" + data.date + ".json",
textContent: 'Save me!', href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2)))))
download: "appchan x v" + g.VERSION + "-" + now + ".json",
href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))))),
target: '_blank'
}); });
return a.click(); return a.click();
}, },
"import": function() { "import": function() {
return this.nextElementSibling.click(); return $('input', this.parentNode).click();
}, },
onImport: function() { onImport: function() {
var file, output, reader; var file, output, reader;
@ -16260,10 +16291,9 @@
} }
reader = new FileReader(); reader = new FileReader();
reader.onload = function(e) { reader.onload = function(e) {
var data, err; var err;
try { try {
data = JSON.parse(e.target.result); Settings.loadSettings(JSON.parse(e.target.result));
Settings.loadSettings(data);
if (confirm('Import successful. Reload now?')) { if (confirm('Import successful. Reload now?')) {
return window.location.reload(); return window.location.reload();
} }
@ -16284,16 +16314,14 @@
} }
return $.set(data.Conf); return $.set(data.Conf);
}, },
convertSettings: function(data, map) { reset: function() {
var newKey, prevKey; if (confirm('Your current settings will be entirely wiped, are you sure?')) {
for (prevKey in map) { return $.clear(function() {
newKey = map[prevKey]; if (confirm('Reset successful. Reload now?')) {
if (newKey) { return window.location.reload();
data.Conf[newKey] = data.Conf[prevKey]; }
} });
delete data.Conf[prevKey];
} }
return data;
}, },
filter: function(section) { filter: function(section) {
var select; var select;
@ -17285,10 +17313,27 @@
Main.handleErrors(errors); Main.handleErrors(errors);
} }
Main.callbackNodes(Thread, threads); Main.callbackNodes(Thread, threads);
return Main.callbackNodesDB(Post, posts, function() { Main.callbackNodesDB(Post, posts, function() {
return $.event('4chanXInitFinished'); 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 <a href='" + changelog + "' target=_blank>version " + g.VERSION + "</a>."
});
new Notice('info', el, 15);
} else {
Settings.open();
}
return $.set('previousversion', g.VERSION);
});
}, },
callbackNodes: function(klass, nodes) { callbackNodes: function(klass, nodes) {
var cb, i, node; var cb, i, node;

View File

@ -22,22 +22,22 @@
"min": { "min": {
"chrome": "31", "chrome": "31",
"firefox": "26", "firefox": "26",
"greasemonkey": "1.13" "greasemonkey": "1.14"
} }
}, },
"devDependencies": { "devDependencies": {
"font-awesome": "https://github.com/FortAwesome/Font-Awesome/archive/v4.0.3.tar.gz", "font-awesome": "~4.0.3",
"grunt": "~0.4.1", "grunt": "~0.4.2",
"grunt-bump": "~0.0.11", "grunt-bump": "~0.0.13",
"grunt-concurrent": "~0.4.0", "grunt-concurrent": "~0.4.3",
"grunt-contrib-clean": "~0.5.0", "grunt-contrib-clean": "~0.5.0",
"grunt-contrib-coffee": "~0.8.0", "grunt-contrib-coffee": "~0.8.2",
"grunt-contrib-compress": "~0.5.2", "grunt-contrib-compress": "~0.6.0",
"grunt-contrib-concat": "~0.3.0", "grunt-contrib-concat": "~0.3.0",
"grunt-contrib-copy": "~0.5.0", "grunt-contrib-copy": "~0.5.0",
"grunt-contrib-watch": "~0.5.3", "grunt-contrib-watch": "~0.5.3",
"grunt-shell": "~0.6.0", "grunt-shell": "~0.6.3",
"load-grunt-tasks": "~0.2.0" "load-grunt-tasks": "~0.2.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,5 +1,4 @@
Redirect = Redirect =
init: -> init: ->
o = o =
thread: {} thread: {}
@ -52,8 +51,8 @@ Redirect =
software: "foolfuuka" software: "foolfuuka"
, ,
name: "4plebs" name: "4plebs"
boards: ["hr", "o", "pol", "s4s", "tg", "tv", "x"] boards: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"]
files: ["hr", "o", "pol", "s4s", "tg", "tv", "x"] files: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"]
data: data:
domain: "archive.4plebs.org" domain: "archive.4plebs.org"
http: true http: true
@ -68,6 +67,15 @@ Redirect =
http: true http: true
https: true https: true
software: "foolfuuka" 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" name: "Install Gentoo"
boards: ["diy", "g", "sci"] boards: ["diy", "g", "sci"]
@ -82,7 +90,7 @@ Redirect =
boards: ["cgl", "g", "mu", "w"] boards: ["cgl", "g", "mu", "w"]
files: ["cgl", "g", "mu", "w"] files: ["cgl", "g", "mu", "w"]
data: data:
domain: "rbt.asia" domain: "archive.rebeccablacktech.com"
http: true http: true
https: true https: true
software: "fuuka" software: "fuuka"
@ -100,9 +108,33 @@ Redirect =
files: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"] files: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"]
data: data:
domain: "fuuka.warosu.org" domain: "fuuka.warosu.org"
http: true
https: true https: true
software: "fuuka" 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" name: "Foolz Beta"
boards: ["a", "co", "d", "gd", "h", "jp", "m", "mlp", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], 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 https: true
withCredentials: true withCredentials: true
software: "foolfuuka" 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) -> to: (dest, data) ->

View File

@ -58,7 +58,7 @@ ThreadHiding =
$.cache "//a.4cdn.org/#{g.BOARD}/threads.json", -> $.cache "//a.4cdn.org/#{g.BOARD}/threads.json", ->
return unless @status is 200 return unless @status is 200
threads = {} threads = {}
for page in JSON.parse @response for page in @response
for thread in page.threads for thread in page.threads
if thread.no of hiddenThreadsOnCatalog if thread.no of hiddenThreadsOnCatalog
threads[thread.no] = hiddenThreadsOnCatalog[thread.no] threads[thread.no] = hiddenThreadsOnCatalog[thread.no]

View File

@ -80,6 +80,7 @@ Get =
$.cache url, $.cache url,
-> Get.archivedPost @, boardID, postID, root, context -> Get.archivedPost @, boardID, postID, root, context
, ,
responseType: 'json'
withCredentials: url.archive.withCredentials withCredentials: url.archive.withCredentials
insert: (post, root, context) -> insert: (post, root, context) ->
# Stop here if the container has been removed while loading. # Stop here if the container has been removed while loading.
@ -118,7 +119,7 @@ Get =
"Error #{req.statusText} (#{req.status})." "Error #{req.statusText} (#{req.status})."
return return
posts = JSON.parse(req.response).posts {posts} = req.response
Build.spoilerRange[boardID] = posts[0].custom_spoiler Build.spoilerRange[boardID] = posts[0].custom_spoiler
for post in posts for post in posts
break if post.no is postID # we found it! break if post.no is postID # we found it!
@ -149,7 +150,7 @@ Get =
Get.insert post, root, context Get.insert post, root, context
return return
data = JSON.parse req.response data = req.response
if data.error if data.error
$.addClass root, 'warning' $.addClass root, 'warning'
root.textContent = data.error root.textContent = data.error
@ -227,4 +228,4 @@ Get =
'[/moot]': '</div>' '[/moot]': '</div>'
'[banned]': '<strong style="color: red;">' '[banned]': '<strong style="color: red;">'
'[/banned]': '</strong>' '[/banned]': '</strong>'
}[text] or text.replace ':lit', '' }[text] or text.replace ':lit', ''

View File

@ -254,17 +254,15 @@ Header =
toggleHideBarOnScroll: (e) -> toggleHideBarOnScroll: (e) ->
hide = @checked hide = @checked
$.set 'Header auto-hide on scroll', hide $.cb.checked.call @
Header.setHideBarOnScroll hide Header.setHideBarOnScroll hide
hideBarOnScroll: -> hideBarOnScroll: ->
offsetY = window.pageYOffset offsetY = window.pageYOffset
if offsetY > (Header.previousOffset or 0) if offsetY > (Header.previousOffset or 0)
$.addClass Header.bar, 'autohide' $.addClass Header.bar, 'autohide', 'scroll'
$.addClass Header.bar, 'scroll'
else else
$.rmClass Header.bar, 'autohide' $.rmClass Header.bar, 'autohide', 'scroll'
$.rmClass Header.bar, 'scroll'
Header.previousOffset = offsetY Header.previousOffset = offsetY
setBarPosition: (bottom) -> setBarPosition: (bottom) ->
@ -320,9 +318,21 @@ Header =
scrollTo: (root, down, needed) -> scrollTo: (root, down, needed) ->
if down if down
x = Header.getBottomOf root 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 window.scrollBy 0, -x unless needed and x >= 0
else else
x = Header.getTopOf root 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 window.scrollBy 0, x unless needed and x >= 0
scrollToIfNeeded: (root, down) -> scrollToIfNeeded: (root, down) ->
@ -342,6 +352,12 @@ Header =
headRect = Header.toggle.getBoundingClientRect() headRect = Header.toggle.getBoundingClientRect()
bottom -= clientHeight - headRect.bottom + headRect.height bottom -= clientHeight - headRect.bottom + headRect.height
bottom bottom
isHidden: ->
{top} = Header.bar.getBoundingClientRect()
if Conf['Bottom header']
top is doc.clientHeight
else
top < 0
addShortcut: (el, icon) -> addShortcut: (el, icon) ->
$.addClass el, 'shortcut' $.addClass el, 'shortcut'

View File

@ -274,7 +274,7 @@ Index =
try try
if req.status is 200 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? else if req.status is 304 and pageNum?
Index.pageNav pageNum Index.pageNav pageNum
catch err catch err
@ -470,6 +470,7 @@ Index =
else else
pageNum = Index.getCurrentPage() pageNum = Index.getCurrentPage()
else else
return unless Index.searchInput.dataset.searching
pageNum = Index.pageBeforeSearch pageNum = Index.pageBeforeSearch
delete Index.pageBeforeSearch delete Index.pageBeforeSearch
<% if (type === 'userscript') { %> <% if (type === 'userscript') { %>

View File

@ -125,12 +125,18 @@ Main =
'left=0,top=0,width=500,height=255,toolbar=0,resizable=0' 'left=0,top=0,width=500,height=255,toolbar=0,resizable=0'
$.before styleSelector.previousSibling, [$.tn '['; passLink, $.tn ']\u00A0\u00A0'] $.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' unless Conf['JSON Navigation'] and g.VIEW is 'index'
Main.initThread() Main.initThread()
else else
$.event '4chanXInitFinished' $.event '4chanXInitFinished'
<% if (type === 'userscript') { %> <% 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 '.' GMver = GM_info.version.split '.'
for v, i in "<%= meta.min.greasemonkey %>".split '.' for v, i in "<%= meta.min.greasemonkey %>".split '.'
continue if v is GMver[i] continue if v is GMver[i]
@ -167,6 +173,17 @@ Main =
Main.callbackNodesDB Post, posts, -> Main.callbackNodesDB Post, posts, ->
$.event '4chanXInitFinished' $.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 <a href='#{changelog}' target=_blank>version #{g.VERSION}</a>."
new Notice 'info', el, 15
else
Settings.open()
$.set 'previousversion', g.VERSION
callbackNodes: (klass, nodes) -> callbackNodes: (klass, nodes) ->
i = 0 i = 0
cb = klass.callbacks cb = klass.callbacks

View File

@ -33,15 +33,11 @@ Navigate =
return return
clean: -> clean: ->
{posts, threads} = g
# Garbage collection # Garbage collection
g.threads.forEach (thread) -> thread.collect() g.threads.forEach (thread) -> thread.collect()
QuoteBacklink.containers = {} QuoteBacklink.containers = {}
# Delete nodes $.rmAll $('.board')
$.rmAll $ '.board'
features: [ features: [
['Thread Excerpt', ThreadExcerpt] ['Thread Excerpt', ThreadExcerpt]
@ -110,8 +106,6 @@ Navigate =
}[g.VIEW]() }[g.VIEW]()
updateBoard: (boardID) -> updateBoard: (boardID) ->
req = null
fullBoardList = $ '#full-board-list', Header.boardList fullBoardList = $ '#full-board-list', Header.boardList
$.rmClass $('.current', fullBoardList), 'current' $.rmClass $('.current', fullBoardList), 'current'
$.addClass $("a[href*='/#{boardID}/']", fullBoardList), 'current' $.addClass $("a[href*='/#{boardID}/']", fullBoardList), 'current'
@ -127,7 +121,7 @@ Navigate =
return unless req.status is 200 return unless req.status is 200
try 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 board = aboard
break break
@ -243,7 +237,7 @@ Navigate =
Navigate.title() Navigate.title()
try try
Navigate.parse JSON.parse(req.response).posts Navigate.parse req.response.posts
catch err catch err
console.error 'Navigate failure:' console.error 'Navigate failure:'
console.log err console.log err

View File

@ -5,45 +5,21 @@ Settings =
className: 'settings-link' className: 'settings-link'
href: 'javascript:;' href: 'javascript:;'
textContent: 'Settings' textContent: 'Settings'
$.on el, 'click', Settings.open $.on el, 'click', @open
$.event 'AddMenuEntry', $.event 'AddMenuEntry',
type: 'header' type: 'header'
el: el el: el
order: 1 order: 1
$.get 'previousversion', null, (item) -> @addSection 'Main', @main
if previous = item['previousversion'] @addSection 'Filter', @filter
return if previous is g.VERSION @addSection 'Sauce', @sauce
# Avoid conflicts between sync'd newer versions @addSection 'Advanced', @advanced
# and out of date extension on this device. @addSection 'Keybinds', @keybinds
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]
changelog = '<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md' $.on d, 'AddSettingsSection', @addSection
el = $.el 'span', $.on d, 'OpenSettings', (e) => @open e.detail
innerHTML: "<%= meta.name %> has been updated to <a href='#{changelog}' target=_blank>version #{g.VERSION}</a>."
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
settings = JSON.parse(localStorage.getItem '4chan-settings') or {} settings = JSON.parse(localStorage.getItem '4chan-settings') or {}
unless settings.disableAll unless settings.disableAll
@ -79,8 +55,10 @@ Settings =
$.on $('.export', dialog), 'click', Settings.export $.on $('.export', dialog), 'click', Settings.export
$.on $('.import', dialog), 'click', Settings.import $.on $('.import', dialog), 'click', Settings.import
$.on $('.reset', dialog), 'click', Settings.reset
$.on $('input', dialog), 'change', Settings.onImport $.on $('input', dialog), 'change', Settings.onImport
links = [] links = []
for section in Settings.sections for section in Settings.sections
link = $.el 'a', link = $.el 'a',
@ -153,58 +131,41 @@ Settings =
div = $.el 'div', div = $.el 'div',
innerHTML: "<button></button><span class=description>: Clear manually-hidden threads and posts on all boards. Reload the page to apply." innerHTML: "<button></button><span class=description>: Clear manually-hidden threads and posts on all boards. Reload the page to apply."
button = $ 'button', div button = $ 'button', div
hiddenNum = 0 $.get {hiddenThreads: {}, hiddenPosts: {}}, ({hiddenThreads, hiddenPosts}) ->
$.get 'hiddenThreads', boards: {}, (item) -> hiddenNum = 0
for ID, board of item.hiddenThreads.boards for ID, board of hiddenThreads.boards
hiddenNum += Object.keys(board).length
for ID, board of hiddenPosts.boards
for ID, thread of board for ID, thread of board
hiddenNum++ hiddenNum += Object.keys(thread).length
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++
button.textContent = "Hidden: #{hiddenNum}" button.textContent = "Hidden: #{hiddenNum}"
$.on button, 'click', -> $.on button, 'click', ->
@textContent = 'Hidden: 0' @textContent = 'Hidden: 0'
$.get 'hiddenThreads', boards: {}, (item) -> $.get 'hiddenThreads', {}, ({hiddenThreads}) ->
for boardID of item.hiddenThreads.boards for boardID of hiddenThreads.boards
localStorage.removeItem "4chan-hide-t-#{boardID}" localStorage.removeItem "4chan-hide-t-#{boardID}"
$.delete ['hiddenThreads', 'hiddenPosts'] $.delete ['hiddenThreads', 'hiddenPosts']
$.after $('input[name="Stubs"]', section).parentNode.parentNode, div $.after $('input[name="Stubs"]', section).parentNode.parentNode, div
export: (now, data) -> export: ->
unless typeof now is 'number' # Make sure to export the most recent data.
now = Date.now() $.get Conf, (Conf) ->
data = # XXX don't export archives.
version: g.VERSION delete Conf['archives']
date: now Settings.downloadExport {version: g.VERSION, date: Date.now(), Conf}
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
<% } %>
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: -> import: ->
@nextElementSibling.click() $('input', @parentNode).click()
onImport: -> onImport: ->
return unless file = @files[0] return unless file = @files[0]
@ -215,8 +176,7 @@ Settings =
reader = new FileReader() reader = new FileReader()
reader.onload = (e) -> reader.onload = (e) ->
try try
data = JSON.parse e.target.result Settings.loadSettings JSON.parse e.target.result
Settings.loadSettings data
if confirm 'Import successful. Reload now?' if confirm 'Import successful. Reload now?'
window.location.reload() window.location.reload()
catch err catch err
@ -230,11 +190,9 @@ Settings =
delete data.Conf['WatchedThreads'] delete data.Conf['WatchedThreads']
$.set data.Conf $.set data.Conf
convertSettings: (data, map) -> reset: ->
for prevKey, newKey of map if confirm 'Your current settings will be entirely wiped, are you sure?'
data.Conf[newKey] = data.Conf[prevKey] if newKey $.clear -> window.location.reload() if confirm 'Reset successful. Reload now?'
delete data.Conf[prevKey]
data
filter: (section) -> filter: (section) ->
section.innerHTML = <%= importHTML('Settings/Filter-select') %> section.innerHTML = <%= importHTML('Settings/Filter-select') %>

View File

@ -154,6 +154,9 @@ UI = do ->
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
onFocus: (e) =>
e.stopPropagation()
@focus e.target
focus: (entry) -> focus: (entry) ->
while focused = $.x 'parent::*/child::*[contains(@class,"focused")]', entry while focused = $.x 'parent::*/child::*[contains(@class,"focused")]', entry
$.rmClass focused, 'focused' $.rmClass focused, 'focused'
@ -196,10 +199,7 @@ UI = do ->
parseEntry: (entry) -> parseEntry: (entry) ->
{el, subEntries} = entry {el, subEntries} = entry
$.addClass el, 'entry' $.addClass el, 'entry'
$.on el, 'focus mouseover', ((e) -> $.on el, 'focus mouseover', @onFocus
e.stopPropagation()
@focus el
).bind @
el.style.order = entry.order or 100 el.style.order = entry.order or 100
return unless subEntries return unless subEntries
$.addClass el, 'has-submenu' $.addClass el, 'has-submenu'

View File

@ -653,6 +653,15 @@ span.hide-announcement {
text-decoration: none; text-decoration: none;
border-bottom: 1px dashed; 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 { .filtered {
text-decoration: underline line-through; text-decoration: underline line-through;
} }
@ -1189,7 +1198,7 @@ a:only-of-type > .remove {
left: 0px; left: 0px;
width: 200px; width: 200px;
} }
.export, .import { .export, .import, .reset {
cursor: pointer; cursor: pointer;
text-decoration: none !important; text-decoration: none !important;
} }

View File

@ -2,9 +2,10 @@
<div class=sections-list></div> <div class=sections-list></div>
<span class='imp-exp-result warning'></span> <span class='imp-exp-result warning'></span>
<div class=credits> <div class=credits>
<a class=export>Export</a> | <a class=export>Export</a>&nbsp|&nbsp
<a class=import>Import</a> | <a class=import>Import</a>&nbsp|&nbsp
<input type=file style='display: none;'> <a class=reset>Reset Settings</a>&nbsp|&nbsp
<input type=file hidden>
<a href='<%= meta.page %>' target=_blank><%= meta.name %></a> | <a href='<%= meta.page %>' target=_blank><%= meta.name %></a> |
<a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md' target=_blank>#{g.VERSION}</a> | <a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md' target=_blank>#{g.VERSION}</a> |
<a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/README.md#reporting-bugs-and-suggestions' target=_blank>Issues</a> | <a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/README.md#reporting-bugs-and-suggestions' target=_blank>Issues</a> |

View File

@ -54,6 +54,8 @@ $.ajax = do ->
if whenModified if whenModified
r.setRequestHeader 'If-Modified-Since', lastModified[url] if url of lastModified r.setRequestHeader 'If-Modified-Since', lastModified[url] if url of lastModified
$.on r, 'load', -> lastModified[url] = r.getResponseHeader 'Last-Modified' $.on r, 'load', -> lastModified[url] = r.getResponseHeader 'Last-Modified'
if /\.json$/.test url
r.responseType = 'json'
$.extend r, options $.extend r, options
$.extend r.upload, upCallbacks $.extend r.upload, upCallbacks
r.send form r.send form
@ -113,11 +115,11 @@ $.X = (path, root) ->
# XPathResult.ORDERED_NODE_SNAPSHOT_TYPE === 7 # XPathResult.ORDERED_NODE_SNAPSHOT_TYPE === 7
d.evaluate path, root, null, 7, null d.evaluate path, root, null, 7, null
$.addClass = (el, className) -> $.addClass = (el, className...) ->
el.classList.add className el.classList.add className...
$.rmClass = (el, className) -> $.rmClass = (el, className...) ->
el.classList.remove className el.classList.remove className...
$.toggleClass = (el, className) -> $.toggleClass = (el, className) ->
el.classList.toggle className el.classList.toggle className
@ -131,12 +133,8 @@ $.rm = do ->
else else
(el) -> el.parentNode?.removeChild el (el) -> el.parentNode?.removeChild el
$.rmAll = (root) -> # jsperf.com/emptify-element/9
# jsperf.com/emptify-element $.rmAll = (root) -> root.textContent = ''
for node in [root.childNodes...]
# HTMLSelectElement.remove !== Element.remove
root.removeChild node
return
$.tn = (s) -> $.tn = (s) ->
d.createTextNode s d.createTextNode s
@ -332,29 +330,50 @@ $.get = (key, val, cb) ->
chrome.storage.sync.get syncItems, done chrome.storage.sync.get syncItems, done
$.set = do -> $.set = do ->
items = {} items =
localItems = {} 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 for key in $.localKeys
if key of items if key of items.sync
(localItems or= {})[key] = items[key] items.local[key] = items.sync[key]
delete items[key] delete items.sync[key]
try try
chrome.storage.local.set localItems setArea 'local'
chrome.storage.sync.set items setArea 'sync'
items = {}
localItems = {}
catch err catch err
c.error err.stack c.error err.stack
(key, val) -> (key, val) ->
if typeof key is 'string' if typeof key is 'string'
items[key] = val items.sync[key] = val
else else
$.extend items, key $.extend items.sync, key
set() 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 { %> <% } else { %>
# http://wiki.greasespot.net/Main_Page # http://wiki.greasespot.net/Main_Page
@ -402,6 +421,9 @@ $.set = do ->
for key, val of keys for key, val of keys
set key, val set key, val
return return
$.clear = (cb) ->
$.delete GM_listValues().map (key) -> key.replace g.NAMESPACE, ''
cb?()
<% } %> <% } %>
$.remove = (arr, value) -> $.remove = (arr, value) ->

View File

@ -78,7 +78,7 @@ class DataBoard
return return
board = @data.boards[boardID] board = @data.boards[boardID]
threads = {} threads = {}
for page in JSON.parse e.target.response for page in e.target.response
for thread in page.threads for thread in page.threads
if thread.no of board if thread.no of board
threads[thread.no] = board[thread.no] threads[thread.no] = board[thread.no]
@ -93,4 +93,4 @@ class DataBoard
disconnect: -> disconnect: ->
$.desync @key $.desync @key
delete @sync delete @sync
delete @data delete @data

View File

@ -14,6 +14,7 @@
// @grant GM_getValue // @grant GM_getValue
// @grant GM_setValue // @grant GM_setValue
// @grant GM_deleteValue // @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_openInTab // @grant GM_openInTab
// @run-at document-start // @run-at document-start
// @updateURL <%= meta.repo %>raw/stable/builds/<%= meta.files.metajs %> // @updateURL <%= meta.repo %>raw/stable/builds/<%= meta.files.metajs %>

View File

@ -203,7 +203,8 @@ Gallery =
$.ajax "//api.4chan.org/#{post.board}/res/#{post.thread}.json", onload: -> $.ajax "//api.4chan.org/#{post.board}/res/#{post.thread}.json", onload: ->
return if @status isnt 200 return if @status isnt 200
i = 0 i = 0
while postObj = JSON.parse(@response).posts[i++] {posts} = @response
while postObj = posts[i++]
break if postObj.no is post.ID break if postObj.no is post.ID
unless postObj.no unless postObj.no
return post.kill() return post.kill()

View File

@ -156,10 +156,19 @@ ImageExpand =
return return
timeoutID = setTimeout ImageExpand.expand, 10000, post 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? # XXX CORS for i.4cdn.org WHEN?
$.ajax "//a.4cdn.org/#{post.board}/res/#{post.thread}.json", onload: -> $.ajax "//a.4cdn.org/#{post.board}/res/#{post.thread}.json", onload: ->
return if @status isnt 200 return if @status isnt 200
for postObj in JSON.parse(@response).posts for postObj in @response.posts
break if postObj.no is post.ID break if postObj.no is post.ID
if postObj.no isnt post.ID if postObj.no isnt post.ID
clearTimeout timeoutID clearTimeout timeoutID
@ -167,6 +176,7 @@ ImageExpand =
else if postObj.filedeleted else if postObj.filedeleted
clearTimeout timeoutID clearTimeout timeoutID
post.kill true post.kill true
<% } %>
menu: menu:
init: -> init: ->

View File

@ -38,10 +38,19 @@ ImageHover =
return return
timeoutID = setTimeout (=> @src = post.file.URL + '?' + Date.now()), 3000 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? # XXX CORS for i.4cdn.org WHEN?
$.ajax "//a.4cdn.org/#{post.board}/res/#{post.thread}.json", onload: -> $.ajax "//a.4cdn.org/#{post.board}/res/#{post.thread}.json", onload: ->
return if @status isnt 200 return if @status isnt 200
for postObj in JSON.parse(@response).posts for postObj in @response.posts
break if postObj.no is post.ID break if postObj.no is post.ID
if postObj.no isnt post.ID if postObj.no isnt post.ID
clearTimeout timeoutID clearTimeout timeoutID
@ -49,3 +58,4 @@ ImageHover =
else if postObj.filedeleted else if postObj.filedeleted
clearTimeout timeoutID clearTimeout timeoutID
post.kill true post.kill true
<% } %>

View File

@ -241,7 +241,7 @@ Linkify =
service = Linkify.types[key].title service = Linkify.types[key].title
switch response.status switch response.status
when 200, 304 when 200, 304
text = "#{service.text JSON.parse response.responseText}" text = "#{service.text response.response}"
if Conf['Embedding'] if Conf['Embedding']
embed.dataset.title = text embed.dataset.title = text
when 404 when 404
@ -304,7 +304,7 @@ Linkify =
$.cache "https://mediacru.sh/#{a.dataset.uid}.json", -> $.cache "https://mediacru.sh/#{a.dataset.uid}.json", ->
{status} = @ {status} = @
return div.innerHTML = "ERROR #{status}" unless status in [200, 304] 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 type in ['video/mp4', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'image/svg', 'audio/mpeg']
for file in files for file in files
if file.type is type if file.type is type

View File

@ -1,10 +1,9 @@
PSAHiding = PSAHiding =
init: -> init: ->
return unless Conf['Announcement Hiding'] return unless Conf['Announcement Hiding']
$.addClass doc, 'hide-announcement' $.addClass doc, 'hide-announcement'
$.on d, '4chanXInitFinished', @setup $.on d, '4chanXInitFinished', @setup
setup: -> setup: ->
$.off d, '4chanXInitFinished', PSAHiding.setup $.off d, '4chanXInitFinished', PSAHiding.setup

View File

@ -77,13 +77,13 @@ ExpandThread =
a.textContent = "Error #{req.statusText} (#{req.status})" a.textContent = "Error #{req.statusText} (#{req.status})"
return return
data = JSON.parse(req.response).posts Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler
Build.spoilerRange[thread.board] = data.shift().custom_spoiler
posts = [] posts = []
postsRoot = [] postsRoot = []
filesCount = 0 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] if post = thread.posts[postData.no]
filesCount++ if 'file' of post filesCount++ if 'file' of post
postsRoot.push post.nodes.root postsRoot.push post.nodes.root

View File

@ -77,9 +77,8 @@ ThreadStats =
onThreadsLoad: -> onThreadsLoad: ->
return unless Conf["Page Count in Stats"] and @status is 200 return unless Conf["Page Count in Stats"] and @status is 200
pages = JSON.parse @response for page in @response
for page in pages
for thread in page.threads when thread.no is ThreadStats.thread.ID for thread in page.threads when thread.no is ThreadStats.thread.ID
ThreadStats.pageCountEl.textContent = page.page 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 return

View File

@ -159,7 +159,7 @@ ThreadUpdater =
switch req.status switch req.status
when 200 when 200
g.DEAD = false g.DEAD = false
ThreadUpdater.parse JSON.parse(req.response).posts ThreadUpdater.parse req.response.posts
ThreadUpdater.setInterval() ThreadUpdater.setInterval()
when 404 when 404
g.DEAD = true g.DEAD = true

View File

@ -695,7 +695,7 @@ QR =
# Too many frequent mistyped captchas will auto-ban you! # Too many frequent mistyped captchas will auto-ban you!
# On connection error, the post most likely didn't go through. # On connection error, the post most likely didn't go through.
QR.cooldown.set delay: 2 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.cooldown.auto = if QR.captcha.isEnabled
!!QR.captcha.captchas.length !!QR.captcha.captchas.length
else else