Merge branch 'v3'

Conflicts:
	CHANGELOG.md
	LICENSE
	builds/4chan-X.js
	builds/4chan-X.meta.js
	builds/4chan-X.user.js
	builds/crx/manifest.json
	builds/crx/script.js
	latest.js
	package.json
	src/General/Main.coffee
	src/General/css/style.css
	src/Posting/QuickReply.coffee
This commit is contained in:
Zixaphir 2013-06-20 17:21:01 -07:00
commit ebfaaaacc4
29 changed files with 22637 additions and 236 deletions

View File

@ -1,3 +1,19 @@
**MayhemYDG**:
- Remove /s4s/ from warosu archive
- Fix CAPTCHA duplication on the report page
- Small bug fixes
**Tracerneo**:
- Add ID styling for IDs with black text
**seaweedchan**:
- Add `.active` class to `.menu-button` when clicked (and remove on menu close)
- Move /v/ and /vg/ back to Foolz archive
- Revert Mayhem's updater changes which caused silly issues
- Make thumbnails in QR show (or most of) the whole image
- Rename `Indicate Spoilers` to `Reveal Spoilers`
- If `Reveal Spoilers` is enabled but `Remove Spoilers` is not, act as if the spoiler is hovered
### v2.1.3
*2013-06-04*
@ -27,9 +43,6 @@
## v2.1.0
*2013-06-01*
**seaweedchan**:
- Small bug fixes
**zixaphir**:
- CSS ~70% (maybe?) rewritten to account for class-based options
- Fixed CSS filters on webkit

10647
builds/4chan-X.js Normal file

File diff suppressed because one or more lines are too long

19
builds/4chan-X.meta.js Normal file
View File

@ -0,0 +1,19 @@
// ==UserScript==
// @name 4chan X
// @version 1.2.17
// @namespace 4chan-X
// @description Cross-browser userscript for maximum lurking on 4chan.
// @license MIT; https://github.com/seaweedchan/4chan-x/blob/master/LICENSE
// @match *://api.4chan.org/*
// @match *://boards.4chan.org/*
// @match *://images.4chan.org/*
// @match *://sys.4chan.org/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_openInTab
// @run-at document-start
// @updateURL https://github.com/seaweedchan/4chan-x/raw/stable/builds/4chan-X.meta.js
// @downloadURL https://github.com/seaweedchan/4chan-x/raw/stable/builds/4chan-X.user.js
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwAgMAAAAqbBEUAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAHFJREFUKFOt0LENACEIBdBv4Qju4wgWanEj3D6OcIVMKaitYHEU/jwTCQj8W75kiVCSBvdQ5/AvfVHBin11BgdRq3ysBgfwBDRrj3MCIA+oAQaku/Q1cNctrAmyDl577tOThYt/Y1RBM4DgOHzM0HFTAyLukH/cmRnqAAAAAElFTkSuQmCC
// ==/UserScript==

10643
builds/4chan-X.user.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -113,11 +113,12 @@
*
*/
(function() {
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, MutationObserver, Nav, Notification, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
__slice = [].slice,
__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); }; };
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
__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; };
Config = {
main: {
@ -137,7 +138,7 @@
'Check for Updates': [true, 'Check for updated versions of appchan x.'],
'Color User IDs': [false, 'Assign unique colors to user IDs on boards that use them'],
'Remove Spoilers': [false, 'Remove all spoilers in text.'],
'Indicate Spoilers': [false, 'Indicate spoilers if Remove Spoilers is enabled.']
'Reveal Spoilers': [false, 'Indicate spoilers if Remove Spoilers is enabled, or make the text appear hovered if Remove Spoiler is disabled.']
},
'Linkification': {
'Linkify': [true, 'Convert text into links where applicable.'],
@ -385,6 +386,7 @@
'Open thread tab': ['Shift+o', 'Open thread in new tab.'],
'Next reply': ['j', 'Select next reply.'],
'Previous reply': ['k', 'Select previous reply.'],
'Deselect reply': ['Shift+d', 'Deselect reply.'],
'Hide': ['x', 'Hide thread.']
},
updater: {
@ -429,8 +431,6 @@
posts: {}
};
MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.OMutationObserver;
Mascots = {
'Akiyama_Mio': {
category: 'Anime',
@ -3639,9 +3639,9 @@
var boardID, postID, threadID, val, _base, _base1, _base2;
boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID, val = _arg.val;
if (postID) {
if (postID !== void 0) {
((_base = ((_base1 = this.data.boards)[boardID] || (_base1[boardID] = {})))[threadID] || (_base[threadID] = {}))[postID] = val;
} else if (threadID) {
} else if (threadID !== void 0) {
((_base2 = this.data.boards)[boardID] || (_base2[boardID] = {}))[threadID] = val;
} else {
this.data.boards[boardID] = val;
@ -3679,13 +3679,9 @@
_ref = this.data.boards;
for (boardID in _ref) {
val = _ref[boardID];
if (typeof this.data.boards[boardID] !== 'object') {
delete this.data.boards[boardID];
} else {
this.deleteIfEmpty({
boardID: boardID
});
}
this.deleteIfEmpty({
boardID: boardID
});
}
now = Date.now();
if ((this.data.lastChecked || 0) < now - 2 * $.HOUR) {
@ -4070,7 +4066,7 @@
hashScroll: function() {
var hash, post;
if (!((hash = this.location.hash) && (post = $.id(hash.slice(1))))) {
if (!((hash = this.location.hash.slice(1)) && (post = $.id(hash)))) {
return;
}
if ((Get.postFromRoot(post)).isHidden) {
@ -4169,7 +4165,7 @@
@license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
*/
var a, boardID, capcode, capcodeClass, capcodeStart, closed, comment, container, date, dateUTC, email, emailEnd, emailStart, ext, file, fileDims, fileHTML, fileHtml, fileInfo, fileSize, fileThumb, filename, flag, flagCode, flagName, href, imgSrc, isClosed, isOP, isSticky, name, postID, quote, shortFilename, spoilerRange, staticPath, sticky, subject, threadID, tripcode, uniqueID, userID, _i, _len, _ref;
var a, boardID, capcode, capcodeClass, capcodeStart, closed, comment, container, date, dateUTC, email, emailEnd, emailStart, ext, file, fileDims, fileHTML, fileInfo, fileSize, fileThumb, filename, flag, flagCode, flagName, href, imgSrc, isClosed, isOP, isSticky, name, postID, quote, shortFilename, spoilerRange, staticPath, sticky, subject, threadID, tripcode, uniqueID, userID, _i, _len, _ref;
postID = o.postID, threadID = o.threadID, boardID = o.boardID, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flagCode = o.flagCode, flagName = o.flagName, date = o.date, dateUTC = o.dateUTC, isSticky = o.isSticky, isClosed = o.isClosed, comment = o.comment, file = o.file;
isOP = postID === threadID;
@ -4207,7 +4203,7 @@
}
flag = flagCode ? (" <img src='" + staticPath + "country/" + (boardID === 'pol' ? 'troll/' : '')) + flagCode.toLowerCase() + (".gif' alt=" + flagCode + " title='" + flagName + "' class=countryFlag>") : '';
if (file != null ? file.isDeleted : void 0) {
fileHtml = isOP ? ("<div class=file id=f" + postID + "><div class=fileInfo></div><span class=fileThumb>") + ("<img src='" + staticPath + "filedeleted.gif' alt='File deleted.' class=fileDeletedRes>") + "</span></div>" : ("<div class=file id=f" + postID + "><span class=fileThumb>") + ("<img src='" + staticPath + "filedeleted-res.gif' alt='File deleted.' class=fileDeletedRes>") + "</span></div>";
fileHTML = isOP ? ("<div class=file id=f" + postID + "><div class=fileInfo></div><span class=fileThumb>") + ("<img src='" + staticPath + "filedeleted.gif' alt='File deleted.' class=fileDeletedRes>") + "</span></div>" : ("<div class=file id=f" + postID + "><span class=fileThumb>") + ("<img src='" + staticPath + "filedeleted-res.gif' alt='File deleted.' class=fileDeletedRes>") + "</span></div>";
} else if (file) {
ext = file.name.slice(-3);
if (!file.twidth && !file.theight && ext === 'gif') {
@ -4462,9 +4458,9 @@
case '[/b]':
return '</b>';
case '[spoiler]':
return '<span class=spoiler>';
return '<s>';
case '[/spoiler]':
return '</span>';
return '</s>';
case '[code]':
return '<pre class=prettyprint>';
case '[/code]':
@ -4611,6 +4607,7 @@
entry = _ref[_i];
this.insertEntry(entry, menu, data);
}
$.addClass(lastToggledButton, 'active');
$.on(d, 'click', this.close);
$.on(d, 'CloseMenu', this.close);
Rice.nodes(menu);
@ -4664,6 +4661,7 @@
close = function() {
$.rm(currentMenu);
$.rmClass(lastToggledButton, 'active');
currentMenu = null;
lastToggledButton = null;
return $.off(d, 'click CloseMenu', this.close);
@ -4988,10 +4986,6 @@
}
for (key in Config.filter) {
this.filters[key] = [];
if (Conf[key] === void 0) {
$["delete"](key);
continue;
}
_ref = Conf[key].split('\n');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
filter = _ref[_i];
@ -7902,7 +7896,7 @@
img: imgContainer.firstChild,
input: input
};
if (MutationObserver) {
if (window.MutationObserver) {
observer = new MutationObserver(this.load.bind(this));
observer.observe(this.nodes.challenge, {
childList: true
@ -9388,7 +9382,12 @@
return (thread.fileLimit && !thread.isSticky ? $.addClass : $.rmClass)(fileCountEl, 'warning');
},
fetchPage: function() {
if (ThreadStats.thread.isDead || !Conf["Page Count in Stats"]) {
if (!Conf["Page Count in Stats"]) {
return;
}
if (ThreadStats.thread.isDead) {
ThreadStats.pageCountEl.textContent = 'Dead';
$.addClass(ThreadStats.pageCountEl, 'warning');
return;
}
setTimeout(ThreadStats.fetchPage, 2 * $.MINUTE);
@ -10022,7 +10021,7 @@
}
},
scroll: function() {
var hash, post, posts, prevID, root;
var checkPosition, hash, onload, post, posts, prevID, root;
if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) {
return;
@ -10039,11 +10038,27 @@
break;
}
}
root.scrollIntoView(false);
return;
onload = function() {
if (checkPosition(root)) {
return root.scrollIntoView(false);
}
};
} else {
posts = Object.keys(Unread.thread.posts);
root = Unread.thread.posts[posts[posts.length - 1]].nodes.root;
onload = function() {
if (checkPosition(root)) {
return Header.scrollToPost(root);
}
};
}
posts = Object.keys(Unread.thread.posts);
return Header.scrollToPost(Unread.thread.posts[posts[posts.length - 1]].nodes.root);
checkPosition = function(target) {
var height, top, _ref;
_ref = target.getBoundingClientRect(), top = _ref.top, height = _ref.height;
return top + height - doc.clientHeight > 0;
};
return $.on(window, 'load', onload);
},
sync: function() {
var lastReadPost;
@ -10062,11 +10077,11 @@
Unread.setLine();
return Unread.update();
},
addPosts: function(newPosts) {
var ID, data, post, _i, _len;
addPosts: function(posts) {
var ID, data, post, _i, _len, _ref;
for (_i = 0, _len = newPosts.length; _i < _len; _i++) {
post = newPosts[_i];
for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i];
ID = post.ID;
if (ID <= Unread.lastReadPost || post.isHidden) {
continue;
@ -10085,7 +10100,7 @@
Unread.addPostQuotingYou(post);
}
if (Conf['Unread Line']) {
Unread.setLine(newPosts.contains(Unread.posts[0]));
Unread.setLine((_ref = Unread.posts[0], __indexOf.call(posts, _ref) >= 0));
}
Unread.read();
return Unread.update();
@ -10248,15 +10263,15 @@
archives: {
'Foolz': {
'domain': 'archive.foolz.us',
'http': true,
'http': false,
'https': true,
'software': 'foolfuuka',
'boards': ['a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'vp', 'vr', 'wsg'],
'files': ['a', 'gd', 'jp', 'm', 'q', 'tg', 'vp', 'vr', 'wsg']
'boards': ['a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'v', 'vg', 'vp', 'vr', 'wsg'],
'files': ['a', 'gd', 'jp', 'm', 'q', 'tg', 'vg', 'vp', 'vr', 'wsg']
},
'NSFW Foolz': {
'domain': 'nsfw.foolz.us',
'http': true,
'http': false,
'https': true,
'software': 'foolfuuka',
'boards': ['u'],
@ -10293,13 +10308,6 @@
'boards': ['d', 'h', 'v'],
'files': ['d', 'h', 'v']
},
'nth-chan': {
'domain': 'nth.pensivenonsen.se',
'http': true,
'software': 'foolfuuka',
'boards': ['vg'],
'files': ['vg']
},
'Foolz a Shit': {
'domain': 'archive.foolzashit.com',
'http': true,
@ -10336,8 +10344,8 @@
'http': true,
'https': true,
'software': 'fuuka',
'boards': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 's4s', 'tg', 'vr'],
'files': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 's4s', 'vr']
'boards': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 'tg', 'vr'],
'files': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 'vr']
}
},
to: function(dest, data) {
@ -11457,7 +11465,7 @@
observe: function() {
var onMutationObserver;
if (MutationObserver) {
if (window.MutationObserver) {
Style.observer = new MutationObserver(onMutationObserver = this.wrapper);
return Style.observer.observe(d, {
childList: true,
@ -11968,7 +11976,7 @@
return $.on(d, '4chanXInitFinished', this.setup);
},
setup: function() {
var btn, entry, items, psa;
var btn, entry, psa;
$.off(d, '4chanXInitFinished', PSAHiding.setup);
if (!(psa = $.id('globalMessage'))) {
@ -11996,21 +12004,10 @@
textContent: '[ - ]'
});
$.on(btn, 'click', PSAHiding.toggle);
items = {
hiddenPSA: 0,
hiddenPSAs: null
};
$.get(items, function(_arg) {
var hiddenPSA, hiddenPSAs;
$.get('hiddenPSA', 0, function(_arg) {
var hiddenPSA;
hiddenPSA = _arg.hiddenPSA, hiddenPSAs = _arg.hiddenPSAs;
if (hiddenPSAs) {
$["delete"]('hiddenPSAs');
if (hiddenPSAs.contains(psa.textContent.replace(/\W+/g, '').toLowerCase())) {
hiddenPSA = +$.id('globalMessage').dataset.utc;
$.set('hiddenPSA', hiddenPSA);
}
}
hiddenPSA = _arg.hiddenPSA;
PSAHiding.sync(hiddenPSA);
$.prepend(psa, btn);
return $.rmClass(doc, 'hide-announcement');
@ -12143,7 +12140,7 @@
var rgb;
rgb = IDColor.ids[this] || IDColor.compute(this);
return ("background-color: rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "); color: ") + (rgb[3] ? "black;" : "white; border-radius: 3px; padding: 0px 2px;");
return ("background-color: rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "); color: ") + (rgb[3] ? "black; border-radius: 3px; padding: 0px 2px;" : "white; border-radius: 3px; padding: 0px 2px;");
},
hash: function(str) {
var i, j, msg;
@ -12540,7 +12537,7 @@
}
board = g.BOARD.ID;
if (board === 'g') {
$.globalEval("window.addEventListener('prettyprint', function(e) {\n var pre = e.detail;\n pre.innerHTML = prettyPrintOne(pre.innerHTML.replace(/\\s/g, '&nbsp;'));\n}, false);");
$.globalEval("window.addEventListener('prettyprint', function(e) {\n var pre = e.detail;\n pre.innerHTML = prettyPrintOne(pre.innerHTML);\n}, false);");
Post.prototype.callbacks.push({
name: 'Parse /g/ code',
cb: this.code
@ -12761,6 +12758,9 @@
case Conf['Previous reply']:
Keybinds.hl(-1, threadRoot);
break;
case Conf['Deselect reply']:
Keybinds.hl(0, threadRoot);
break;
case Conf['Hide']:
if (g.VIEW === 'index') {
ThreadHiding.toggle(thread);
@ -12871,6 +12871,12 @@
hl: function(delta, thread) {
var axe, headRect, next, postEl, rect, replies, reply, root, topMargin, _i, _len;
if (!delta) {
if (postEl = $('.reply.highlight', thread)) {
$.rmClass(postEl, 'highlight');
}
return;
}
if (Conf['Fixed Header'] && Conf['Bottom header']) {
topMargin = 0;
} else {
@ -13091,10 +13097,13 @@
RemoveSpoilers = {
init: function() {
if (Conf['Reveal Spoilers'] && !Conf['Remove Spoilers']) {
$.addClass(doc, 'reveal-spoilers');
}
if (!Conf['Remove Spoilers']) {
return;
}
if (Conf['Indicate Spoilers']) {
if (Conf['Reveal Spoilers']) {
this.wrapper = function(text) {
return "[spoiler]" + text + "[/spoiler]";
};
@ -13120,22 +13129,23 @@
Report = {
init: function() {
if (!/report/.test(location.search)) {
if (!(/report/.test(location.search) && d.cookie.indexOf('pass_enabled=1') === -1)) {
return;
}
return $.ready(this.ready);
return $.asap((function() {
return $.id('recaptcha_response_field');
}), Report.ready);
},
ready: function() {
var field, form;
var field;
form = $('form');
field = $.id('recaptcha_response_field');
$.on(field, 'keydown', function(e) {
if (e.keyCode === 8 && !field.value) {
return $.globalEval('Recaptcha.reload("t")');
}
});
return $.on(form, 'submit', function(e) {
return $.on($('form'), 'submit', function(e) {
var response;
e.preventDefault();
@ -13143,7 +13153,7 @@
if (!/\s/.test(response)) {
field.value = "" + response + " " + response;
}
return form.submit();
return this.submit();
});
}
};
@ -14592,16 +14602,17 @@
return;
case 'images.4chan.org':
$.ready(function() {
var url;
var URL;
if (Conf['404 Redirect'] && d.title === '4chan - 404 Not Found') {
Redirect.init();
url = Redirect.to('file', {
boardID: pathname[1],
filename: pathname[3]
pathname = location.pathname.split('/');
URL = Redirect.to('file', {
boardID: g.BOARD.ID,
filename: pathname[pathname.length - 1]
});
if (url) {
return location.href = url;
if (URL) {
return location.replace(URL);
}
}
});
@ -14699,7 +14710,7 @@
threadID: g.THREADID,
postID: +location.hash.match(/\d+/)
});
location.href = href || ("/" + g.BOARD + "/");
location.replace(href || ("/" + g.BOARD + "/"));
}
return;
}

View File

@ -95,7 +95,7 @@
*
*/
(function() {
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, MutationObserver, Nav, Notification, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, CatalogLinks, Clone, Conf, Config, CustomCSS, DataBoard, DataBoards, DeleteLink, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notification, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, 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,
__hasProp = {}.hasOwnProperty,
@ -119,7 +119,7 @@
'Reply Navigation': [false, 'Add buttons to navigate to top / bottom of thread.'],
'Color User IDs': [false, 'Assign unique colors to user IDs on boards that use them'],
'Remove Spoilers': [false, 'Remove all spoilers in text.'],
'Indicate Spoilers': [false, 'Indicate spoilers if Remove Spoilers is enabled.']
'Reveal Spoilers': [false, 'Indicate spoilers if Remove Spoilers is enabled, or make the text appear hovered if Remove Spoiler is disabled.']
},
'Linkification': {
'Linkify': [true, 'Convert text into links where applicable.'],
@ -367,6 +367,7 @@
'Open thread tab': ['Shift+o', 'Open thread in new tab.'],
'Next reply': ['j', 'Select next reply.'],
'Previous reply': ['k', 'Select previous reply.'],
'Deselect reply': ['Shift+d', 'Deselect reply.'],
'Hide': ['x', 'Hide thread.']
},
updater: {
@ -411,8 +412,6 @@
posts: {}
};
MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.OMutationObserver;
Mascots = {
'Akiyama_Mio': {
category: 'Anime',
@ -3141,9 +3140,14 @@
(syncItems || (syncItems = {}))[key] = val;
}
}
items = {};
count = 0;
done = function(item) {
var lastError;
lastError = chrome.runtime.lastError;
if (lastError) {
c.error(lastError, lastError.message || 'No message.');
}
$.extend(items, item);
if (!--count) {
return cb(items);
@ -3642,9 +3646,9 @@
var boardID, postID, threadID, val, _base, _base1, _base2;
boardID = _arg.boardID, threadID = _arg.threadID, postID = _arg.postID, val = _arg.val;
if (postID) {
if (postID !== void 0) {
((_base = ((_base1 = this.data.boards)[boardID] || (_base1[boardID] = {})))[threadID] || (_base[threadID] = {}))[postID] = val;
} else if (threadID) {
} else if (threadID !== void 0) {
((_base2 = this.data.boards)[boardID] || (_base2[boardID] = {}))[threadID] = val;
} else {
this.data.boards[boardID] = val;
@ -3682,13 +3686,9 @@
_ref = this.data.boards;
for (boardID in _ref) {
val = _ref[boardID];
if (typeof this.data.boards[boardID] !== 'object') {
delete this.data.boards[boardID];
} else {
this.deleteIfEmpty({
boardID: boardID
});
}
this.deleteIfEmpty({
boardID: boardID
});
}
now = Date.now();
if ((this.data.lastChecked || 0) < now - 2 * $.HOUR) {
@ -4073,7 +4073,7 @@
hashScroll: function() {
var hash, post;
if (!((hash = this.location.hash) && (post = $.id(hash.slice(1))))) {
if (!((hash = this.location.hash.slice(1)) && (post = $.id(hash)))) {
return;
}
if ((Get.postFromRoot(post)).isHidden) {
@ -4172,7 +4172,7 @@
@license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
*/
var a, boardID, capcode, capcodeClass, capcodeStart, closed, comment, container, date, dateUTC, email, emailEnd, emailStart, ext, file, fileDims, fileHTML, fileHtml, fileInfo, fileSize, fileThumb, filename, flag, flagCode, flagName, href, imgSrc, isClosed, isOP, isSticky, name, postID, quote, shortFilename, spoilerRange, staticPath, sticky, subject, threadID, tripcode, uniqueID, userID, _i, _len, _ref;
var a, boardID, capcode, capcodeClass, capcodeStart, closed, comment, container, date, dateUTC, email, emailEnd, emailStart, ext, file, fileDims, fileHTML, fileInfo, fileSize, fileThumb, filename, flag, flagCode, flagName, href, imgSrc, isClosed, isOP, isSticky, name, postID, quote, shortFilename, spoilerRange, staticPath, sticky, subject, threadID, tripcode, uniqueID, userID, _i, _len, _ref;
postID = o.postID, threadID = o.threadID, boardID = o.boardID, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flagCode = o.flagCode, flagName = o.flagName, date = o.date, dateUTC = o.dateUTC, isSticky = o.isSticky, isClosed = o.isClosed, comment = o.comment, file = o.file;
isOP = postID === threadID;
@ -4210,7 +4210,7 @@
}
flag = flagCode ? (" <img src='" + staticPath + "country/" + (boardID === 'pol' ? 'troll/' : '')) + flagCode.toLowerCase() + (".gif' alt=" + flagCode + " title='" + flagName + "' class=countryFlag>") : '';
if (file != null ? file.isDeleted : void 0) {
fileHtml = isOP ? ("<div class=file id=f" + postID + "><div class=fileInfo></div><span class=fileThumb>") + ("<img src='" + staticPath + "filedeleted.gif' alt='File deleted.' class=fileDeletedRes>") + "</span></div>" : ("<div class=file id=f" + postID + "><span class=fileThumb>") + ("<img src='" + staticPath + "filedeleted-res.gif' alt='File deleted.' class=fileDeletedRes>") + "</span></div>";
fileHTML = isOP ? ("<div class=file id=f" + postID + "><div class=fileInfo></div><span class=fileThumb>") + ("<img src='" + staticPath + "filedeleted.gif' alt='File deleted.' class=fileDeletedRes>") + "</span></div>" : ("<div class=file id=f" + postID + "><span class=fileThumb>") + ("<img src='" + staticPath + "filedeleted-res.gif' alt='File deleted.' class=fileDeletedRes>") + "</span></div>";
} else if (file) {
ext = file.name.slice(-3);
if (!file.twidth && !file.theight && ext === 'gif') {
@ -4465,9 +4465,9 @@
case '[/b]':
return '</b>';
case '[spoiler]':
return '<span class=spoiler>';
return '<s>';
case '[/spoiler]':
return '</span>';
return '</s>';
case '[code]':
return '<pre class=prettyprint>';
case '[/code]':
@ -4614,6 +4614,7 @@
entry = _ref[_i];
this.insertEntry(entry, menu, data);
}
$.addClass(lastToggledButton, 'active');
$.on(d, 'click', this.close);
$.on(d, 'CloseMenu', this.close);
Rice.nodes(menu);
@ -4667,6 +4668,7 @@
close = function() {
$.rm(currentMenu);
$.rmClass(lastToggledButton, 'active');
currentMenu = null;
lastToggledButton = null;
return $.off(d, 'click CloseMenu', this.close);
@ -4991,10 +4993,6 @@
}
for (key in Config.filter) {
this.filters[key] = [];
if (Conf[key] === void 0) {
$["delete"](key);
continue;
}
_ref = Conf[key].split('\n');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
filter = _ref[_i];
@ -7900,7 +7898,7 @@
img: imgContainer.firstChild,
input: input
};
if (MutationObserver) {
if (window.MutationObserver) {
observer = new MutationObserver(this.load.bind(this));
observer.observe(this.nodes.challenge, {
childList: true
@ -9367,7 +9365,12 @@
return (thread.fileLimit && !thread.isSticky ? $.addClass : $.rmClass)(fileCountEl, 'warning');
},
fetchPage: function() {
if (ThreadStats.thread.isDead || !Conf["Page Count in Stats"]) {
if (!Conf["Page Count in Stats"]) {
return;
}
if (ThreadStats.thread.isDead) {
ThreadStats.pageCountEl.textContent = 'Dead';
$.addClass(ThreadStats.pageCountEl, 'warning');
return;
}
setTimeout(ThreadStats.fetchPage, 2 * $.MINUTE);
@ -10001,7 +10004,7 @@
}
},
scroll: function() {
var hash, post, posts, prevID, root;
var checkPosition, hash, onload, post, posts, prevID, root;
if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) {
return;
@ -10018,11 +10021,27 @@
break;
}
}
root.scrollIntoView(false);
return;
onload = function() {
if (checkPosition(root)) {
return root.scrollIntoView(false);
}
};
} else {
posts = Object.keys(Unread.thread.posts);
root = Unread.thread.posts[posts[posts.length - 1]].nodes.root;
onload = function() {
if (checkPosition(root)) {
return Header.scrollToPost(root);
}
};
}
posts = Object.keys(Unread.thread.posts);
return Header.scrollToPost(Unread.thread.posts[posts[posts.length - 1]].nodes.root);
checkPosition = function(target) {
var height, top, _ref;
_ref = target.getBoundingClientRect(), top = _ref.top, height = _ref.height;
return top + height - doc.clientHeight > 0;
};
return $.on(window, 'load', onload);
},
sync: function() {
var lastReadPost;
@ -10041,11 +10060,11 @@
Unread.setLine();
return Unread.update();
},
addPosts: function(newPosts) {
var ID, data, post, _i, _len;
addPosts: function(posts) {
var ID, data, post, _i, _len, _ref;
for (_i = 0, _len = newPosts.length; _i < _len; _i++) {
post = newPosts[_i];
for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i];
ID = post.ID;
if (ID <= Unread.lastReadPost || post.isHidden) {
continue;
@ -10064,7 +10083,7 @@
Unread.addPostQuotingYou(post);
}
if (Conf['Unread Line']) {
Unread.setLine(newPosts.contains(Unread.posts[0]));
Unread.setLine((_ref = Unread.posts[0], __indexOf.call(posts, _ref) >= 0));
}
Unread.read();
return Unread.update();
@ -10232,15 +10251,15 @@
archives: {
'Foolz': {
'domain': 'archive.foolz.us',
'http': true,
'http': false,
'https': true,
'software': 'foolfuuka',
'boards': ['a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'vp', 'vr', 'wsg'],
'files': ['a', 'gd', 'jp', 'm', 'q', 'tg', 'vp', 'vr', 'wsg']
'boards': ['a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'v', 'vg', 'vp', 'vr', 'wsg'],
'files': ['a', 'gd', 'jp', 'm', 'q', 'tg', 'vg', 'vp', 'vr', 'wsg']
},
'NSFW Foolz': {
'domain': 'nsfw.foolz.us',
'http': true,
'http': false,
'https': true,
'software': 'foolfuuka',
'boards': ['u'],
@ -10277,13 +10296,6 @@
'boards': ['d', 'h', 'v'],
'files': ['d', 'h', 'v']
},
'nth-chan': {
'domain': 'nth.pensivenonsen.se',
'http': true,
'software': 'foolfuuka',
'boards': ['vg'],
'files': ['vg']
},
'Foolz a Shit': {
'domain': 'archive.foolzashit.com',
'http': true,
@ -10320,8 +10332,8 @@
'http': true,
'https': true,
'software': 'fuuka',
'boards': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 's4s', 'tg', 'vr'],
'files': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 's4s', 'vr']
'boards': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 'tg', 'vr'],
'files': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 'vr']
}
},
to: function(dest, data) {
@ -11442,7 +11454,7 @@
observe: function() {
var onMutationObserver;
if (MutationObserver) {
if (window.MutationObserver) {
Style.observer = new MutationObserver(onMutationObserver = this.wrapper);
return Style.observer.observe(d, {
childList: true,
@ -11953,7 +11965,7 @@
return $.on(d, '4chanXInitFinished', this.setup);
},
setup: function() {
var btn, entry, items, psa;
var btn, entry, psa;
$.off(d, '4chanXInitFinished', PSAHiding.setup);
if (!(psa = $.id('globalMessage'))) {
@ -11981,21 +11993,10 @@
textContent: '[ - ]'
});
$.on(btn, 'click', PSAHiding.toggle);
items = {
hiddenPSA: 0,
hiddenPSAs: null
};
$.get(items, function(_arg) {
var hiddenPSA, hiddenPSAs;
$.get('hiddenPSA', 0, function(_arg) {
var hiddenPSA;
hiddenPSA = _arg.hiddenPSA, hiddenPSAs = _arg.hiddenPSAs;
if (hiddenPSAs) {
$["delete"]('hiddenPSAs');
if (hiddenPSAs.contains(psa.textContent.replace(/\W+/g, '').toLowerCase())) {
hiddenPSA = +$.id('globalMessage').dataset.utc;
$.set('hiddenPSA', hiddenPSA);
}
}
hiddenPSA = _arg.hiddenPSA;
PSAHiding.sync(hiddenPSA);
$.prepend(psa, btn);
return $.rmClass(doc, 'hide-announcement');
@ -12128,7 +12129,7 @@
var rgb;
rgb = IDColor.ids[this] || IDColor.compute(this);
return ("background-color: rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "); color: ") + (rgb[3] ? "black;" : "white; border-radius: 3px; padding: 0px 2px;");
return ("background-color: rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "); color: ") + (rgb[3] ? "black; border-radius: 3px; padding: 0px 2px;" : "white; border-radius: 3px; padding: 0px 2px;");
},
hash: function(str) {
var i, j, msg;
@ -12525,7 +12526,7 @@
}
board = g.BOARD.ID;
if (board === 'g') {
$.globalEval("window.addEventListener('prettyprint', function(e) {\n var pre = e.detail;\n pre.innerHTML = prettyPrintOne(pre.innerHTML.replace(/\\s/g, '&nbsp;'));\n}, false);");
$.globalEval("window.addEventListener('prettyprint', function(e) {\n var pre = e.detail;\n pre.innerHTML = prettyPrintOne(pre.innerHTML);\n}, false);");
Post.prototype.callbacks.push({
name: 'Parse /g/ code',
cb: this.code
@ -12746,6 +12747,9 @@
case Conf['Previous reply']:
Keybinds.hl(-1, threadRoot);
break;
case Conf['Deselect reply']:
Keybinds.hl(0, threadRoot);
break;
case Conf['Hide']:
if (g.VIEW === 'index') {
ThreadHiding.toggle(thread);
@ -12856,6 +12860,12 @@
hl: function(delta, thread) {
var axe, headRect, next, postEl, rect, replies, reply, root, topMargin, _i, _len;
if (!delta) {
if (postEl = $('.reply.highlight', thread)) {
$.rmClass(postEl, 'highlight');
}
return;
}
if (Conf['Fixed Header'] && Conf['Bottom header']) {
topMargin = 0;
} else {
@ -13076,10 +13086,13 @@
RemoveSpoilers = {
init: function() {
if (Conf['Reveal Spoilers'] && !Conf['Remove Spoilers']) {
$.addClass(doc, 'reveal-spoilers');
}
if (!Conf['Remove Spoilers']) {
return;
}
if (Conf['Indicate Spoilers']) {
if (Conf['Reveal Spoilers']) {
this.wrapper = function(text) {
return "[spoiler]" + text + "[/spoiler]";
};
@ -13105,22 +13118,23 @@
Report = {
init: function() {
if (!/report/.test(location.search)) {
if (!(/report/.test(location.search) && d.cookie.indexOf('pass_enabled=1') === -1)) {
return;
}
return $.ready(this.ready);
return $.asap((function() {
return $.id('recaptcha_response_field');
}), Report.ready);
},
ready: function() {
var field, form;
var field;
form = $('form');
field = $.id('recaptcha_response_field');
$.on(field, 'keydown', function(e) {
if (e.keyCode === 8 && !field.value) {
return $.globalEval('Recaptcha.reload("t")');
}
});
return $.on(form, 'submit', function(e) {
return $.on($('form'), 'submit', function(e) {
var response;
e.preventDefault();
@ -13128,7 +13142,7 @@
if (!/\s/.test(response)) {
field.value = "" + response + " " + response;
}
return form.submit();
return this.submit();
});
}
};
@ -14569,16 +14583,17 @@
return;
case 'images.4chan.org':
$.ready(function() {
var url;
var URL;
if (Conf['404 Redirect'] && d.title === '4chan - 404 Not Found') {
Redirect.init();
url = Redirect.to('file', {
boardID: pathname[1],
filename: pathname[3]
pathname = location.pathname.split('/');
URL = Redirect.to('file', {
boardID: g.BOARD.ID,
filename: pathname[pathname.length - 1]
});
if (url) {
return location.href = url;
if (URL) {
return location.replace(URL);
}
}
});
@ -14676,7 +14691,7 @@
threadID: g.THREADID,
postID: +location.hash.match(/\d+/)
});
location.href = href || ("/" + g.BOARD + "/");
location.replace(href || ("/" + g.BOARD + "/"));
}
return;
}

View File

@ -24,10 +24,10 @@
"grunt-concurrent": "~0.2.0",
"grunt-contrib-clean": "~0.4.1",
"grunt-contrib-coffee": "~0.7.0",
"grunt-contrib-compress": "~0.5.0",
"grunt-contrib-compress": "~0.5.1",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-watch": "~0.4.3",
"grunt-contrib-watch": "~0.4.4",
"grunt-shell": "~0.2.2"
},
"repository": {

View File

@ -26,15 +26,15 @@ Redirect =
archives:
'Foolz':
'domain': 'archive.foolz.us'
'http': true
'http': false
'https': true
'software': 'foolfuuka'
'boards': ['a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'vp', 'vr', 'wsg']
'files': ['a', 'gd', 'jp', 'm', 'q', 'tg', 'vp', 'vr', 'wsg']
'boards': ['a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'v', 'vg', 'vp', 'vr', 'wsg']
'files': ['a', 'gd', 'jp', 'm', 'q', 'tg', 'vg', 'vp', 'vr', 'wsg']
'NSFW Foolz':
'domain': 'nsfw.foolz.us'
'http': true
'http': false
'https': true
'software': 'foolfuuka'
'boards': ['u']
@ -71,13 +71,6 @@ Redirect =
'boards': ['d', 'h', 'v']
'files': ['d', 'h', 'v']
'nth-chan':
'domain': 'nth.pensivenonsen.se'
'http': true
'software': 'foolfuuka'
'boards': ['vg']
'files': ['vg']
'Foolz a Shit':
'domain': 'archive.foolzashit.com'
'http': true
@ -114,8 +107,8 @@ Redirect =
'http': true
'https': true
'software': 'fuuka'
'boards': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 's4s', 'tg', 'vr']
'files': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 's4s', 'vr']
'boards': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 'tg', 'vr']
'files': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 'vr']
to: (dest, data) ->
archive = (if dest is 'search' then Redirect.thread else Redirect[dest])[data.boardID]

View File

@ -8,11 +8,6 @@ Filter =
for key of Config.filter
@filters[key] = []
if Conf[key] is undefined
# XXX hopefully tmp fix for the rare people getting this mysterious error:
# "Filter" initialization crashed. TypeError: Cannot call method 'split' of undefined
$.delete key
continue
for filter in Conf[key].split '\n'
continue if filter[0] is '#'

View File

@ -116,7 +116,7 @@ Build =
''
if file?.isDeleted
fileHtml = if isOP
fileHTML = if isOP
"<div class=file id=f#{postID}><div class=fileInfo></div><span class=fileThumb>" +
"<img src='#{staticPath}filedeleted.gif' alt='File deleted.' class=fileDeletedRes>" +
"</span></div>"

View File

@ -63,9 +63,9 @@ Config =
false
'Remove all spoilers in text.'
]
'Indicate Spoilers': [
'Reveal Spoilers': [
false
'Indicate spoilers if Remove Spoilers is enabled.'
'Indicate spoilers if Remove Spoilers is enabled, or make the text appear hovered if Remove Spoiler is disabled.'
]
'Linkification':
@ -983,6 +983,10 @@ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
'k'
'Select previous reply.'
]
'Deselect reply': [
'Shift+d'
'Deselect reply.'
]
'Hide': [
'x'
'Hide thread.'

View File

@ -163,9 +163,9 @@ Get =
when '[/b]'
'</b>'
when '[spoiler]'
'<span class=spoiler>'
'<s>'
when '[/spoiler]'
'</span>'
'</s>'
when '[code]'
'<pre class=prettyprint>'
when '[/code]'

View File

@ -19,8 +19,6 @@ g =
threads: {}
posts: {}
MutationObserver = window.MutationObserver or window.WebKitMutationObserver or window.OMutationObserver
Mascots =
'Akiyama_Mio':
category: 'Anime'

View File

@ -238,7 +238,7 @@ Header =
$('input[name=boardnav]', settings).focus()
hashScroll: ->
return unless (hash = @location.hash) and post = $.id hash[1..]
return unless (hash = @location.hash[1..]) and post = $.id hash
return if (Get.postFromRoot post).isHidden
Header.scrollToPost post

View File

@ -87,10 +87,11 @@ Main =
$.ready ->
if Conf['404 Redirect'] and d.title is '4chan - 404 Not Found'
Redirect.init()
url = Redirect.to 'file',
boardID: pathname[1]
filename: pathname[3]
location.href = url if url
pathname = location.pathname.split '/'
URL = Redirect.to 'file',
boardID: g.BOARD.ID
filename: pathname[pathname.length - 1]
location.replace URL if URL
return
init = (features) ->
@ -183,7 +184,7 @@ Main =
boardID: g.BOARD.ID
threadID: g.THREADID
postID: +location.hash.match /\d+/ # post number or 0
location.href = href or "/#{g.BOARD}/"
location.replace href or "/#{g.BOARD}/"
return
if board = $ '.board'

View File

@ -57,6 +57,8 @@ UI = do ->
for entry in @entries
@insertEntry entry, menu, data
$.addClass lastToggledButton, 'active'
$.on d, 'click', @close
$.on d, 'CloseMenu', @close
Rice.nodes menu
@ -111,6 +113,7 @@ UI = do ->
close = ->
$.rm currentMenu
$.rmClass lastToggledButton, 'active'
currentMenu = null
lastToggledButton = null
$.off d, 'click CloseMenu', @close

1050
src/General/css/style.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -358,9 +358,11 @@ $.get = (key, val, cb) ->
else
(syncItems or= {})[key] = val
items = {}
count = 0
done = (item) ->
{lastError} = chrome.runtime
if lastError
c.error lastError, lastError.message or 'No message.'
$.extend items, item
cb items unless --count

View File

@ -33,9 +33,9 @@ class DataBoard
delete @data.boards[boardID]
set: ({boardID, threadID, postID, val}) ->
if postID
if postID isnt undefined
((@data.boards[boardID] or= {})[threadID] or= {})[postID] = val
else if threadID
else if threadID isnt undefined
(@data.boards[boardID] or= {})[threadID] = val
else
@data.boards[boardID] = val
@ -60,12 +60,7 @@ class DataBoard
clean: ->
for boardID, val of @data.boards
# XXX tmp fix for users that had the `null`
# value for a board with the Unread features:
if typeof @data.boards[boardID] isnt 'object'
delete @data.boards[boardID]
else
@deleteIfEmpty {boardID}
@deleteIfEmpty {boardID}
now = Date.now()
if (@data.lastChecked or 0) < now - 2 * $.HOUR

View File

@ -30,17 +30,7 @@ PSAHiding =
textContent: '[ - ]'
$.on btn, 'click', PSAHiding.toggle
# XXX remove hiddenPSAs workaround in the future.
items =
hiddenPSA: 0
hiddenPSAs: null
$.get items, ({hiddenPSA, hiddenPSAs}) ->
if hiddenPSAs
$.delete 'hiddenPSAs'
if hiddenPSAs.contains psa.textContent.replace(/\W+/g, '').toLowerCase()
hiddenPSA = +$.id('globalMessage').dataset.utc
$.set 'hiddenPSA', hiddenPSA
$.get 'hiddenPSA', 0, ({hiddenPSA}) ->
PSAHiding.sync hiddenPSA
$.prepend psa, btn
$.rmClass doc, 'hide-announcement'

View File

@ -29,7 +29,7 @@ IDColor =
apply: ->
rgb = IDColor.ids[@] or IDColor.compute @
"background-color: rgb(#{rgb[0]},#{rgb[1]},#{rgb[2]}); color: " + if rgb[3] then "black;" else "white; border-radius: 3px; padding: 0px 2px;"
"background-color: rgb(#{rgb[0]},#{rgb[1]},#{rgb[2]}); color: " + if rgb[3] then "black; border-radius: 3px; padding: 0px 2px;" else "white; border-radius: 3px; padding: 0px 2px;"
hash: (str) ->
msg = 0
@ -38,4 +38,4 @@ IDColor =
while i < j
msg = ((msg << 5) - msg) + str.charCodeAt i
++i
msg
msg

View File

@ -7,7 +7,7 @@ Fourchan =
$.globalEval """
window.addEventListener('prettyprint', function(e) {
var pre = e.detail;
pre.innerHTML = prettyPrintOne(pre.innerHTML.replace(/\\s/g, '&nbsp;'));
pre.innerHTML = prettyPrintOne(pre.innerHTML);
}, false);
"""
Post::callbacks.push

View File

@ -109,6 +109,8 @@ Keybinds =
Keybinds.hl +1, threadRoot
when Conf['Previous reply']
Keybinds.hl -1, threadRoot
when Conf['Deselect reply']
Keybinds.hl 0, threadRoot
when Conf['Hide']
ThreadHiding.toggle thread if g.VIEW is 'index'
else
@ -193,6 +195,10 @@ Keybinds =
location.href = url
hl: (delta, thread) ->
unless delta
if postEl = $ '.reply.highlight', thread
$.rmClass postEl, 'highlight'
return
if Conf['Fixed Header'] and Conf['Bottom header']
topMargin = 0
else

View File

@ -1,8 +1,11 @@
RemoveSpoilers =
init: ->
if Conf['Reveal Spoilers'] and !Conf['Remove Spoilers']
$.addClass doc, 'reveal-spoilers'
return unless Conf['Remove Spoilers']
if Conf['Indicate Spoilers']
if Conf['Reveal Spoilers']
@wrapper = (text) ->
"[spoiler]#{text}[/spoiler]"

View File

@ -1,14 +1,13 @@
Report =
init: ->
return unless /report/.test location.search
$.ready @ready
return unless /report/.test(location.search) and d.cookie.indexOf('pass_enabled=1') is -1
$.asap (-> $.id 'recaptcha_response_field'), Report.ready
ready: ->
form = $ 'form'
field = $.id 'recaptcha_response_field'
$.on field, 'keydown', (e) ->
$.globalEval 'Recaptcha.reload("t")' if e.keyCode is 8 and not field.value
$.on form, 'submit', (e) ->
$.on $('form'), 'submit', (e) ->
e.preventDefault()
response = field.value.trim()
field.value = "#{response} #{response}" unless /\s/.test response
form.submit()
@submit()

View File

@ -48,7 +48,11 @@ ThreadStats =
(if thread.fileLimit and !thread.isSticky then $.addClass else $.rmClass) fileCountEl, 'warning'
fetchPage: ->
return if ThreadStats.thread.isDead or !Conf["Page Count in Stats"]
return if !Conf["Page Count in Stats"]
if ThreadStats.thread.isDead
ThreadStats.pageCountEl.textContent = 'Dead'
$.addClass ThreadStats.pageCountEl, 'warning'
return
setTimeout ThreadStats.fetchPage, 2 * $.MINUTE
$.ajax "//api.4chan.org/#{ThreadStats.thread.board}/threads.json", onload: ThreadStats.onThreadsLoad,
headers: 'If-Modified-Since': ThreadStats.lastModified

View File

@ -43,11 +43,21 @@ Unread =
break if prevID is post.ID
prevID = post.ID
break unless post.isHidden
root.scrollIntoView false
return
# Scroll to the last read post.
posts = Object.keys Unread.thread.posts
Header.scrollToPost Unread.thread.posts[posts[posts.length - 1]].nodes.root
onload = -> root.scrollIntoView false if checkPosition root
else
# Scroll to the last read post.
posts = Object.keys Unread.thread.posts
{root} = Unread.thread.posts[posts[posts.length - 1]].nodes
onload = -> Header.scrollToPost root if checkPosition root
checkPosition = (target) ->
# Don't scroll to the target if
# - it's visible.
# - we've scrolled past it.
{top, height} = target.getBoundingClientRect()
top + height - doc.clientHeight > 0
# Prevent the browser to scroll back to
# the previous scroll location on page load.
$.on window, 'load', onload
sync: ->
lastReadPost = Unread.db.get
@ -61,8 +71,8 @@ Unread =
Unread.setLine()
Unread.update()
addPosts: (newPosts) ->
for post in newPosts
addPosts: (posts) ->
for post in posts
{ID} = post
if ID <= Unread.lastReadPost or post.isHidden
continue
@ -76,7 +86,7 @@ Unread =
Unread.addPostQuotingYou post
if Conf['Unread Line']
# Force line on visible threads if there were no unread posts previously.
Unread.setLine newPosts.contains Unread.posts[0]
Unread.setLine Unread.posts[0] in posts
Unread.read()
Unread.update()

View File

@ -784,7 +784,7 @@ QR =
img: imgContainer.firstChild
input: input
if MutationObserver
if window.MutationObserver
observer = new MutationObserver @load.bind @
observer.observe @nodes.challenge,
childList: true

View File

@ -94,7 +94,7 @@ Style =
@observe()
observe: ->
if MutationObserver
if window.MutationObserver
Style.observer = new MutationObserver onMutationObserver = @wrapper
Style.observer.observe d,
childList: true