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
*2014-01-19*
=======
**MayhemYDG**:
- Added a `Reset Settings` button in the settings.
- More stability update.
- Stability update.
>>>>>>> v3
**Zixaphir**:
- 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.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE

View File

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

View File

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

View File

@ -2,7 +2,7 @@
// ==UserScript==
// @name appchan x
// @version 2.8.7
// @minGMVer 1.13
// @minGMVer 1.14
// @minFFVer 26
// @namespace zixaphir
// @description The most comprehensive 4chan userscript.
@ -14,6 +14,7 @@
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_openInTab
// @run-at document-start
// @updateURL https://github.com/zixaphir/appchan-x/raw/stable/builds/appchan-x.meta.js
@ -22,7 +23,7 @@
// ==/UserScript==
/*
* appchan x - Version 2.8.7 - 2014-01-21
* appchan x - Version 2.8.7 - 2014-01-26
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@ -111,8 +112,8 @@
(function() {
var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, Callbacks, CatalogLinks, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, InfiniScroll, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__slice = [].slice,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
@ -2624,6 +2625,9 @@
return lastModified[url] = r.getResponseHeader('Last-Modified');
});
}
if (/\.json$/.test(url)) {
r.responseType = 'json';
}
$.extend(r, options);
$.extend(r.upload, upCallbacks);
r.send(form);
@ -2712,12 +2716,16 @@
return d.evaluate(path, root, null, 7, null);
};
$.addClass = function(el, className) {
return el.classList.add(className);
$.addClass = function() {
var className, el, _ref;
el = arguments[0], className = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
return (_ref = el.classList).add.apply(_ref, className);
};
$.rmClass = function(el, className) {
return el.classList.remove(className);
$.rmClass = function() {
var className, el, _ref;
el = arguments[0], className = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
return (_ref = el.classList).remove.apply(_ref, className);
};
$.toggleClass = function(el, className) {
@ -2742,12 +2750,7 @@
})();
$.rmAll = function(root) {
var node, _i, _len, _ref;
_ref = __slice.call(root.childNodes);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
root.removeChild(node);
}
return root.textContent = '';
};
$.tn = function(s) {
@ -2980,6 +2983,13 @@
};
})();
$.clear = function(cb) {
$["delete"](GM_listValues().map(function(key) {
return key.replace(g.NAMESPACE, '');
}));
return typeof cb === "function" ? cb() : void 0;
};
$.remove = function(arr, value) {
var i;
i = arr.indexOf(value);
@ -3622,7 +3632,7 @@
}
board = _this.data.boards[boardID];
threads = {};
_ref = JSON.parse(e.target.response);
_ref = e.target.response;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = _ref[_i];
_ref1 = page.threads;
@ -4190,18 +4200,16 @@
toggleHideBarOnScroll: function(e) {
var hide;
hide = this.checked;
$.set('Header auto-hide on scroll', hide);
$.cb.checked.call(this);
return Header.setHideBarOnScroll(hide);
},
hideBarOnScroll: function() {
var offsetY;
offsetY = window.pageYOffset;
if (offsetY > (Header.previousOffset || 0)) {
$.addClass(Header.bar, 'autohide');
$.addClass(Header.bar, 'scroll');
$.addClass(Header.bar, 'autohide', 'scroll');
} else {
$.rmClass(Header.bar, 'autohide');
$.rmClass(Header.bar, 'scroll');
$.rmClass(Header.bar, 'autohide', 'scroll');
}
return Header.previousOffset = offsetY;
},
@ -4249,14 +4257,38 @@
return Header.scrollTo(post);
},
scrollTo: function(root, down, needed) {
var x;
var height, x;
if (down) {
x = Header.getBottomOf(root);
if (Conf['Header auto-hide on scroll'] && Conf['Bottom header']) {
height = Header.bar.getBoundingClientRect().height;
if (x <= 0) {
if (!Header.isHidden()) {
x += height;
}
} else {
if (Header.isHidden()) {
x -= height;
}
}
}
if (!(needed && x >= 0)) {
return window.scrollBy(0, -x);
}
} else {
x = Header.getTopOf(root);
if (Conf['Header auto-hide on scroll'] && !Conf['Bottom header']) {
height = Header.bar.getBoundingClientRect().height;
if (x >= 0) {
if (!Header.isHidden()) {
x += height;
}
} else {
if (Header.isHidden()) {
x -= height;
}
}
}
if (!(needed && x >= 0)) {
return window.scrollBy(0, x);
}
@ -4284,6 +4316,15 @@
}
return bottom;
},
isHidden: function() {
var top;
top = Header.bar.getBoundingClientRect().top;
if (Conf['Bottom header']) {
return top === doc.clientHeight;
} else {
return top < 0;
}
},
addShortcut: function(el, icon) {
$.addClass(el, 'shortcut');
return $.add(Header[icon ? 'icons' : 'stats'], el);
@ -4731,7 +4772,7 @@
Index.board = "" + g.BOARD;
try {
if (req.status === 200) {
Index.parse(JSON.parse(req.response), pageNum);
Index.parse(req.response, pageNum);
} else if (req.status === 304 && (pageNum != null)) {
Index.pageNav(pageNum);
}
@ -5009,6 +5050,9 @@
pageNum = Index.getCurrentPage();
}
} else {
if (!Index.searchInput.dataset.searching) {
return;
}
pageNum = Index.pageBeforeSearch;
delete Index.pageBeforeSearch;
Index.searchInput.removeAttribute('data-searching');
@ -5378,6 +5422,7 @@
return $.cache(url, function() {
return Get.archivedPost(this, boardID, postID, root, context);
}, {
responseType: 'json',
withCredentials: url.archive.withCredentials
});
}
@ -5418,7 +5463,7 @@
}
return;
}
posts = JSON.parse(req.response).posts;
posts = req.response.posts;
Build.spoilerRange[boardID] = posts[0].custom_spoiler;
for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i];
@ -5454,7 +5499,7 @@
Get.insert(post, root, context);
return;
}
data = JSON.parse(req.response);
data = req.response;
if (data.error) {
$.addClass(root, 'warning');
root.textContent = data.error;
@ -5559,6 +5604,7 @@
this.type = type;
this.rmEntry = __bind(this.rmEntry, this);
this.addEntry = __bind(this.addEntry, this);
this.onFocus = __bind(this.onFocus, this);
this.keybinds = __bind(this.keybinds, this);
this.close = __bind(this.close, this);
$.on(d, 'AddMenuEntry', this.addEntry);
@ -5723,6 +5769,11 @@
return e.stopPropagation();
};
Menu.prototype.onFocus = function(e) {
e.stopPropagation();
return this.focus(e.target);
};
Menu.prototype.focus = function(entry) {
var bottom, cHeight, cWidth, eRect, focused, left, right, sRect, style, submenu, top, _i, _len, _ref, _ref1, _ref2;
while (focused = $.x('parent::*/child::*[contains(@class,"focused")]', entry)) {
@ -5774,10 +5825,7 @@
var el, subEntries, subEntry, _i, _len;
el = entry.el, subEntries = entry.subEntries;
$.addClass(el, 'entry');
$.on(el, 'focus mouseover', (function(e) {
e.stopPropagation();
return this.focus(el);
}).bind(this));
$.on(el, 'focus mouseover', this.onFocus);
el.style.order = entry.order || 100;
if (!subEntries) {
return;
@ -6684,7 +6732,7 @@
return;
}
threads = {};
_ref = JSON.parse(this.response);
_ref = this.response;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = _ref[_i];
_ref1 = page.threads;
@ -7888,7 +7936,7 @@
switch (response.status) {
case 200:
case 304:
text = "" + (service.text(JSON.parse(response.responseText)));
text = "" + (service.text(response.response));
if (Conf['Embedding']) {
embed.dataset.title = text;
}
@ -7984,7 +8032,7 @@
if (status !== 200 && status !== 304) {
return div.innerHTML = "ERROR " + status;
}
files = JSON.parse(this.response).files;
files = this.response.files;
_ref = ['video/mp4', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'image/svg', 'audio/mpeg'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
type = _ref[_i];
@ -8889,7 +8937,7 @@
QR.cooldown.set({
delay: 2
});
} else if (err.textContent && (m = err.textContent.match(/wait\s(\d+)\ssecond/i))) {
} else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i))) {
QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true;
QR.cooldown.set({
delay: m[1]
@ -10028,12 +10076,13 @@
}
return $.ajax("//api.4chan.org/" + post.board + "/res/" + post.thread + ".json", {
onload: function() {
var i, postObj;
var i, postObj, posts;
if (this.status !== 200) {
return;
}
i = 0;
while (postObj = JSON.parse(this.response).posts[i++]) {
posts = this.response.posts;
while (postObj = posts[i++]) {
if (postObj.no === post.ID) {
break;
}
@ -10310,7 +10359,7 @@
if (this.status !== 200) {
return;
}
_ref = JSON.parse(this.response).posts;
_ref = this.response.posts;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
postObj = _ref[_i];
if (postObj.no === post.ID) {
@ -10438,7 +10487,7 @@
if (this.status !== 200) {
return;
}
_ref = JSON.parse(this.response).posts;
_ref = this.response.posts;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
postObj = _ref[_i];
if (postObj.no === post.ID) {
@ -11061,21 +11110,21 @@
});
},
onThreadsLoad: function() {
var page, pages, thread, _i, _j, _len, _len1, _ref;
var page, thread, _i, _j, _len, _len1, _ref, _ref1;
if (!(Conf["Page Count in Stats"] && this.status === 200)) {
return;
}
pages = JSON.parse(this.response);
for (_i = 0, _len = pages.length; _i < _len; _i++) {
page = pages[_i];
_ref = page.threads;
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
thread = _ref[_j];
_ref = this.response;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = _ref[_i];
_ref1 = page.threads;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
thread = _ref1[_j];
if (!(thread.no === ThreadStats.thread.ID)) {
continue;
}
ThreadStats.pageCountEl.textContent = page.page;
(page.page === pages.length - 1 ? $.addClass : $.rmClass)(ThreadStats.pageCountEl, 'warning');
(page.page === this.response.length - 1 ? $.addClass : $.rmClass)(ThreadStats.pageCountEl, 'warning');
return;
}
}
@ -11279,7 +11328,7 @@
switch (req.status) {
case 200:
g.DEAD = false;
ThreadUpdater.parse(JSON.parse(req.response).posts);
ThreadUpdater.parse(req.response.posts);
ThreadUpdater.setInterval();
break;
case 404:
@ -12334,8 +12383,8 @@
}
}, {
name: "4plebs",
boards: ["hr", "o", "pol", "s4s", "tg", "tv", "x"],
files: ["hr", "o", "pol", "s4s", "tg", "tv", "x"],
boards: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"],
files: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"],
data: {
domain: "archive.4plebs.org",
http: true,
@ -12352,6 +12401,16 @@
https: true,
software: "foolfuuka"
}
}, {
name: "Love is Over",
boards: ["d", "i"],
files: ["d", "i"],
data: {
domain: "loveisover.me",
http: true,
https: true,
software: "foolfuuka"
}
}, {
name: "Install Gentoo",
boards: ["diy", "g", "sci"],
@ -12367,7 +12426,7 @@
boards: ["cgl", "g", "mu", "w"],
files: ["cgl", "g", "mu", "w"],
data: {
domain: "rbt.asia",
domain: "archive.rebeccablacktech.com",
http: true,
https: true,
software: "fuuka"
@ -12387,10 +12446,37 @@
files: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"],
data: {
domain: "fuuka.warosu.org",
http: true,
https: true,
software: "fuuka"
}
}, {
name: "fgts",
boards: ["soc"],
files: ["soc"],
data: {
domain: "fgts.eu",
http: true,
https: true,
software: "foolfuuka"
}
}, {
name: "maware",
boards: ["t"],
files: ["t"],
data: {
domain: "archive.mawa.re",
http: true,
software: "foolfuuka"
}
}, {
name: "installgentoo.com",
boards: ["g", "t"],
files: ["g", "t"],
data: {
domain: "chan.installgentoo.com",
http: true,
software: "foolfuuka"
}
}, {
name: "Foolz Beta",
boards: ["a", "co", "d", "gd", "h", "jp", "m", "mlp", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"],
@ -12402,16 +12488,6 @@
withCredentials: true,
software: "foolfuuka"
}
}, {
name: "Love is Over",
boards: ["d", "i"],
files: ["d", "i"],
data: {
domain: "loveisover.me",
http: true,
https: true,
software: "foolfuuka"
}
}
],
to: function(dest, data) {
@ -14503,18 +14579,21 @@
return a.textContent = ExpandThread.text('+', postsCount, filesCount);
},
parse: function(req, thread, a) {
var data, filesCount, post, postData, posts, postsCount, postsRoot, root, _i, _len, _ref;
var filesCount, post, postData, posts, postsCount, postsRoot, root, _i, _len, _ref, _ref1;
if ((_ref = req.status) !== 200 && _ref !== 304) {
a.textContent = "Error " + req.statusText + " (" + req.status + ")";
return;
}
data = JSON.parse(req.response).posts;
Build.spoilerRange[thread.board] = data.shift().custom_spoiler;
Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler;
posts = [];
postsRoot = [];
filesCount = 0;
for (_i = 0, _len = data.length; _i < _len; _i++) {
postData = data[_i];
_ref1 = req.response.posts;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
postData = _ref1[_i];
if (postData.no === thread.ID) {
continue;
}
if (post = thread.posts[postData.no]) {
if ('file' in post) {
filesCount++;
@ -15643,8 +15722,6 @@
}
},
clean: function() {
var posts, threads;
posts = g.posts, threads = g.threads;
g.threads.forEach(function(thread) {
return thread.collect();
});
@ -15746,7 +15823,6 @@
},
updateBoard: function(boardID) {
var fullBoardList, onload, req;
req = null;
fullBoardList = $('#full-board-list', Header.boardList);
$.rmClass($('.current', fullBoardList), 'current');
$.addClass($("a[href*='/" + boardID + "/']", fullBoardList), 'current');
@ -15762,7 +15838,7 @@
return;
}
try {
_ref = JSON.parse(req.response).boards;
_ref = req.response.boards;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
aboard = _ref[_i];
if (!(aboard.board === boardID)) {
@ -15905,7 +15981,7 @@
}
Navigate.title();
try {
return Navigate.parse(JSON.parse(req.response).posts);
return Navigate.parse(req.response.posts);
} catch (_error) {
err = _error;
console.error('Navigate failure:');
@ -15983,59 +16059,27 @@
Settings = {
init: function() {
var addSection, check, el, key, settings, value, _ref;
var check, el, settings,
_this = this;
el = $.el('a', {
className: 'settings-link',
href: 'javascript:;',
textContent: 'Settings'
});
$.on(el, 'click', Settings.open);
$.on(el, 'click', this.open);
$.event('AddMenuEntry', {
type: 'header',
el: el,
order: 1
});
$.get('previousversion', null, function(item) {
var changelog, curr, prev, previous;
if (previous = item['previousversion']) {
if (previous === g.VERSION) {
return;
}
prev = previous.match(/\d+/g).map(Number);
curr = g.VERSION.match(/\d+/g).map(Number);
if (!(prev[0] <= curr[0] && prev[1] <= curr[1] && prev[2] <= curr[2])) {
return;
}
changelog = 'https://github.com/zixaphir/appchan-x/blob/master/CHANGELOG.md';
el = $.el('span', {
innerHTML: "appchan x has been updated to <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);
this.addSection('Main', this.main);
this.addSection('Filter', this.filter);
this.addSection('Sauce', this.sauce);
this.addSection('Advanced', this.advanced);
this.addSection('Keybinds', this.keybinds);
$.on(d, 'AddSettingsSection', this.addSection);
$.on(d, 'OpenSettings', function(e) {
return Settings.open(e.detail);
return _this.open(e.detail);
});
settings = JSON.parse(localStorage.getItem('4chan-settings')) || {};
if (!settings.disableAll) {
@ -16071,13 +16115,14 @@
Settings.dialog = dialog = $.el('div', {
id: 'appchanx-settings',
"class": 'dialog',
innerHTML: "<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', {
id: 'overlay'
});
$.on($('.export', dialog), 'click', Settings["export"]);
$.on($('.import', dialog), 'click', Settings["import"]);
$.on($('.reset', dialog), 'click', Settings.reset);
$.on($('input', dialog), 'change', Settings.onImport);
links = [];
_ref = Settings.sections;
@ -16137,7 +16182,7 @@
return $.event('OpenSettings', null, section);
},
main: function(section) {
var arr, button, description, div, fs, hiddenNum, input, inputs, items, key, obj, _ref;
var arr, button, description, div, fs, input, inputs, items, key, obj, _ref;
items = {};
inputs = {};
_ref = Config.main;
@ -16173,45 +16218,34 @@
innerHTML: "<button></button><span class=description>: Clear manually-hidden threads and posts on all boards. Reload the page to apply."
});
button = $('button', div);
hiddenNum = 0;
$.get('hiddenThreads', {
boards: {}
}, function(item) {
var ID, board, thread, _ref1;
_ref1 = item.hiddenThreads.boards;
$.get({
hiddenThreads: {},
hiddenPosts: {}
}, function(_arg) {
var ID, board, hiddenNum, hiddenPosts, hiddenThreads, thread, _ref1, _ref2;
hiddenThreads = _arg.hiddenThreads, hiddenPosts = _arg.hiddenPosts;
hiddenNum = 0;
_ref1 = hiddenThreads.boards;
for (ID in _ref1) {
board = _ref1[ID];
for (ID in board) {
thread = board[ID];
hiddenNum++;
}
hiddenNum += Object.keys(board).length;
}
return button.textContent = "Hidden: " + hiddenNum;
});
$.get('hiddenPosts', {
boards: {}
}, function(item) {
var ID, board, post, thread, _ref1;
_ref1 = item.hiddenPosts.boards;
for (ID in _ref1) {
board = _ref1[ID];
_ref2 = hiddenPosts.boards;
for (ID in _ref2) {
board = _ref2[ID];
for (ID in board) {
thread = board[ID];
for (ID in thread) {
post = thread[ID];
hiddenNum++;
}
hiddenNum += Object.keys(thread).length;
}
}
return button.textContent = "Hidden: " + hiddenNum;
});
$.on(button, 'click', function() {
this.textContent = 'Hidden: 0';
return $.get('hiddenThreads', {
boards: {}
}, function(item) {
var boardID;
for (boardID in item.hiddenThreads.boards) {
return $.get('hiddenThreads', {}, function(_arg) {
var boardID, hiddenThreads;
hiddenThreads = _arg.hiddenThreads;
for (boardID in hiddenThreads.boards) {
localStorage.removeItem("4chan-hide-t-" + boardID);
}
return $["delete"](['hiddenThreads', 'hiddenPosts']);
@ -16219,41 +16253,29 @@
});
return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div);
},
"export": function(now, data) {
var a, db, span, _i, _len, _ref;
if (typeof now !== 'number') {
now = Date.now();
data = {
"export": function() {
return $.get(Conf, function(Conf) {
delete Conf['archives'];
return Settings.downloadExport({
version: g.VERSION,
date: now
};
_ref = DataBoard.keys;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
db = _ref[_i];
Conf[db] = {
boards: {}
};
}
$.get(Conf, function(Conf) {
delete Conf['archives'];
data.Conf = Conf;
return Settings["export"](now, data);
date: Date.now(),
Conf: Conf
});
return;
}
a = $.el('a', {
className: 'warning',
textContent: 'Save me!',
download: "appchan x v" + g.VERSION + "-" + now + ".json",
href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))))),
target: '_blank'
});
span = $('.imp-exp-result', Settings.dialog);
$.rmAll(span);
return $.add(span, a);
},
downloadExport: function(data) {
var a, p;
a = $.el('a', {
download: "appchan x v" + g.VERSION + "-" + data.date + ".json",
href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2)))))
});
p = $('.imp-exp-result', Settings.dialog);
$.rmAll(p);
$.add(p, a);
return a.click();
},
"import": function() {
return this.nextElementSibling.click();
return $('input', this.parentNode).click();
},
onImport: function() {
var file, output, reader;
@ -16267,10 +16289,9 @@
}
reader = new FileReader();
reader.onload = function(e) {
var data, err;
var err;
try {
data = JSON.parse(e.target.result);
Settings.loadSettings(data);
Settings.loadSettings(JSON.parse(e.target.result));
if (confirm('Import successful. Reload now?')) {
return window.location.reload();
}
@ -16291,16 +16312,14 @@
}
return $.set(data.Conf);
},
convertSettings: function(data, map) {
var newKey, prevKey;
for (prevKey in map) {
newKey = map[prevKey];
if (newKey) {
data.Conf[newKey] = data.Conf[prevKey];
}
delete data.Conf[prevKey];
reset: function() {
if (confirm('Your current settings will be entirely wiped, are you sure?')) {
return $.clear(function() {
if (confirm('Reset successful. Reload now?')) {
return window.location.reload();
}
});
}
return data;
},
filter: function(section) {
var select;
@ -17233,7 +17252,7 @@
return $.ready(Main.initReady);
},
initReady: function() {
var GMver, err, href, i, passLink, styleSelector, v, _i, _len, _ref, _ref1;
var GMver, err, href, i, passLink, styleSelector, test, v, _i, _len, _ref, _ref1;
if ((_ref = d.title) === '4chan - Temporarily Offline' || _ref === '4chan - 404 Not Found') {
if (Conf['404 Redirect'] && g.VIEW === 'thread') {
href = Redirect.to('thread', {
@ -17260,14 +17279,19 @@
} else {
$.event('4chanXInitFinished');
}
test = $.el('span');
test.classList.add('a', 'b');
if (test.className !== 'a b') {
new Notice('warning', "Your version of Firefox is outdated (v26 minimum) and appchan x may not operate correctly.", 30);
}
GMver = GM_info.version.split('.');
_ref1 = "1.13".split('.');
_ref1 = "1.14".split('.');
for (i = _i = 0, _len = _ref1.length; _i < _len; i = ++_i) {
v = _ref1[i];
if (v === GMver[i]) {
continue;
}
(v < GMver[i]) || new Notice('warning', "Your version of Greasemonkey is outdated (v" + GM_info.version + " instead of v1.13 minimum) and appchan x may not operate correctly.", 30);
(v < GMver[i]) || new Notice('warning', "Your version of Greasemonkey is outdated (v" + GM_info.version + " instead of v1.14 minimum) and appchan x may not operate correctly.", 30);
break;
}
try {
@ -17308,10 +17332,27 @@
Main.handleErrors(errors);
}
Main.callbackNodes(Thread, threads);
return Main.callbackNodesDB(Post, posts, function() {
Main.callbackNodesDB(Post, posts, function() {
return $.event('4chanXInitFinished');
});
}
return $.get('previousversion', null, function(_arg) {
var changelog, el, previousversion;
previousversion = _arg.previousversion;
if (previousversion === g.VERSION) {
return;
}
if (previousversion) {
changelog = 'https://github.com/zixaphir/appchan-x/blob/master/CHANGELOG.md';
el = $.el('span', {
innerHTML: "appchan x has been updated to <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) {
var cb, i, node;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -125,12 +125,18 @@ Main =
'left=0,top=0,width=500,height=255,toolbar=0,resizable=0'
$.before styleSelector.previousSibling, [$.tn '['; passLink, $.tn ']\u00A0\u00A0']
# Parse HTML or skip it and start building from JSON.
unless Conf['JSON Navigation'] and g.VIEW is 'index'
Main.initThread()
else
$.event '4chanXInitFinished'
<% if (type === 'userscript') { %>
test = $.el 'span'
test.classList.add 'a', 'b'
if test.className isnt 'a b'
new Notice 'warning', "Your version of Firefox is outdated (v<%= meta.min.firefox %> minimum) and <%= meta.name %> may not operate correctly.", 30
GMver = GM_info.version.split '.'
for v, i in "<%= meta.min.greasemonkey %>".split '.'
continue if v is GMver[i]
@ -167,6 +173,17 @@ Main =
Main.callbackNodesDB Post, posts, ->
$.event '4chanXInitFinished'
$.get 'previousversion', null, ({previousversion}) ->
return if previousversion is g.VERSION
if previousversion
changelog = '<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md'
el = $.el 'span',
innerHTML: "<%= meta.name %> has been updated to <a href='#{changelog}' target=_blank>version #{g.VERSION}</a>."
new Notice 'info', el, 15
else
Settings.open()
$.set 'previousversion', g.VERSION
callbackNodes: (klass, nodes) ->
i = 0
cb = klass.callbacks

View File

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

View File

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

View File

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

View File

@ -653,6 +653,15 @@ span.hide-announcement {
text-decoration: none;
border-bottom: 1px dashed;
}
@supports (text-decoration-style: dashed) or (-moz-text-decoration-style: dashed) {
.quotelink.forwardlink,
.backlink.forwardlink {
text-decoration: underline;
-moz-text-decoration-style: dashed;
text-decoration-style: dashed;
border-bottom: none;
}
}
.filtered {
text-decoration: underline line-through;
}
@ -1189,7 +1198,7 @@ a:only-of-type > .remove {
left: 0px;
width: 200px;
}
.export, .import {
.export, .import, .reset {
cursor: pointer;
text-decoration: none !important;
}

View File

@ -2,9 +2,10 @@
<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 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='<%= 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 %>/README.md#reporting-bugs-and-suggestions' target=_blank>Issues</a> |

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -77,9 +77,8 @@ ThreadStats =
onThreadsLoad: ->
return unless Conf["Page Count in Stats"] and @status is 200
pages = JSON.parse @response
for page in pages
for page in @response
for thread in page.threads when thread.no is ThreadStats.thread.ID
ThreadStats.pageCountEl.textContent = page.page
(if page.page is pages.length - 1 then $.addClass else $.rmClass) ThreadStats.pageCountEl, 'warning'
(if page.page is @response.length - 1 then $.addClass else $.rmClass) ThreadStats.pageCountEl, 'warning'
return

View File

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

View File

@ -695,7 +695,7 @@ QR =
# Too many frequent mistyped captchas will auto-ban you!
# On connection error, the post most likely didn't go through.
QR.cooldown.set delay: 2
else if err.textContent and m = err.textContent.match /wait\s(\d+)\ssecond/i
else if err.textContent and m = err.textContent.match /wait\s+(\d+)\s+second/i
QR.cooldown.auto = if QR.captcha.isEnabled
!!QR.captcha.captchas.length
else