Merge branch 'v3' into Av2

Conflicts:
	appchan-x.meta.js
	appchan-x.user.js
	css/style.css
	package.json
	src/features.coffee
	src/globals.coffee
This commit is contained in:
Zixaphir 2013-03-28 20:50:57 -07:00
commit 62b1b1cf90
31 changed files with 1420 additions and 443 deletions

View File

@ -3,10 +3,10 @@
1. Make sure both your **browser** and **4chan X** are up to date.
2. Disable your other extensions & scripts to identify conflicts.
3. If your issue persists, open a [new issue](https://github.com/MayhemYDG/4chan-x/issues) with the following information:
1. Report precise steps to reproduce the problem.
2. Report console errors, if any.
3. Report browser version.
4. Include your exported settings.
1. Precise steps to reproduce the problem.
2. Console errors, if any.
3. Browser version.
4. Your exported settings.
Open your console with:
- `Ctrl + Shift + J` on Chrome.

View File

@ -24,31 +24,18 @@ module.exports = (grunt) ->
]
dest: 'tmp/script.coffee'
manifest:
options:
process:
data: pkg
src: 'src/manifest.json',
dest: 'builds/crx/manifest.json'
metadata:
options:
process:
data: pkg
src: 'src/metadata.js',
dest: '<%= pkg.name %>.meta.js'
crx:
options:
process:
data: pkg
src: [
'src/banner.js'
'tmp/script.js'
]
dest: 'builds/crx/script.js'
files:
'builds/crx/manifest.json': 'src/manifest.json'
'builds/crx/script.js': [
'src/banner.js'
'tmp/script.js'
]
userscript:
userjs:
options:
process:
data: pkg
@ -57,13 +44,27 @@ module.exports = (grunt) ->
'src/banner.js'
'tmp/script.js'
]
dest: '<%= pkg.name %>.user.js'
userjs:
# Lazily copy the userscript
src: '<%= pkg.name %>.user.js'
dest: 'builds/<%= pkg.name %>.js'
userscript:
options:
process:
data: pkg
files:
'<%= pkg.name %>.meta.js': 'src/metadata.js'
'<%= pkg.name %>.user.js': [
'src/metadata.js'
'src/banner.js'
'tmp/script.js'
]
copy:
crx:
src: 'img/*.png'
dest: 'builds/crx/'
expand: true
flatten: true
coffee:
script:
src: 'tmp/script.coffee'
@ -90,35 +91,73 @@ module.exports = (grunt) ->
options:
interrupt: true
files: [
'Gruntfile.js'
'Gruntfile.coffee'
'package.json'
'lib/**/*.coffee'
'src/**/*.coffee'
'src/**/*.js'
'css/**/*.css'
'img/*'
'lib/**/*'
'src/**/*'
'css/**/*'
'img/**/*'
]
tasks: 'default'
tasks: 'build'
compress:
crx:
options:
archive: 'builds/4chan-X.zip'
level: 9
pretty: true
expand: true
cwd: 'builds/crx/'
src: '**'
clean:
tmp: 'tmp'
builds: 'builds'
tmp: 'tmp'
grunt.loadNpmTasks 'grunt-bump'
grunt.loadNpmTasks 'grunt-contrib-clean'
grunt.loadNpmTasks 'grunt-contrib-coffee'
grunt.loadNpmTasks 'grunt-contrib-compress'
grunt.loadNpmTasks 'grunt-contrib-concat'
grunt.loadNpmTasks 'grunt-contrib-copy'
grunt.loadNpmTasks 'grunt-contrib-watch'
grunt.loadNpmTasks 'grunt-exec'
grunt.registerTask 'default', [
'concat:coffee',
'coffee:script',
'concat:manifest',
'concat:crx',
'concat:userscript',
'concat:userjs',
'concat:metadata',
'clean'
grunt.registerTask 'default', ['build']
grunt.registerTask 'set-build', 'Set the build type variable', (type) ->
pkg.type = type;
grunt.log.ok 'pkg.type = %s', type
grunt.registerTask 'build', [
'build-crx'
'build-userjs'
'build-userscript'
]
grunt.registerTask 'build-crx', [
'set-build:crx'
'concat:coffee'
'coffee:script'
'concat:crx'
'copy:crx'
'clean:tmp'
]
grunt.registerTask 'build-userjs', [
'set-build:userjs'
'concat:coffee'
'coffee:script'
'concat:userjs'
'clean:tmp'
]
grunt.registerTask 'build-userscript', [
'set-build:userscript'
'concat:coffee'
'coffee:script'
'concat:userscript'
'clean:tmp'
]
grunt.registerTask 'release', [
@ -141,6 +180,7 @@ module.exports = (grunt) ->
'bump:major'
'updcl:1'
]
grunt.registerTask 'updcl', 'Update the changelog', (i) ->
# Update the `pkg` object with the new version.
pkg = grunt.file.readJSON('package.json');

View File

@ -1,8 +1,8 @@
# 4chan X
Get it [here](http://mayhemydg.github.com/4chan-x/).
Get it [here](https://4chan-x.just-believe.in/).
***
### [MIT License](/4chan-x/blob/master/LICENSE)
### [Contribute](/4chan-x/blob/master/CONTRIBUTING.md)
### [MIT License](/LICENSE)
### [Contribute](/CONTRIBUTING.md)

View File

@ -15,7 +15,7 @@
// @grant GM_deleteValue
// @grant GM_openInTab
// @run-at document-start
// @updateURL https://github.com/zixaphir/appchan-x/raw/Av2/appchan-x.meta.js
// @downloadURL https://github.com/zixaphir/appchan-x/raw/Av2/appchan-x.user.js
// @icon data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAIALAAAAAAQABAAAAIxlI+pq+D9DAgUoFkPDlbs7lGiI2bSVnKglnJMOL6omczxVZK3dH/41AG6Lh7i6qUoAAA7
// @updateURL http://zixaphir.github.com/appchan-x/builds/appchan-x.meta.js
// @downloadURL http://zixaphir.github.com/appchan-x/builds/appchan-x.user.js
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwAgMAAAAqbBEUAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAHFJREFUKFOt0LENACEIBdBv4Qju4wgWanEj3D6OcIVMKaitYHEU/jwTCQj8W75kiVCSBvdQ5/AvfVHBin11BgdRq3ysBgfwBDRrj3MCIA+oAQaku/Q1cNctrAmyDl577tOThYt/Y1RBM4DgOHzM0HFTAyLukH/cmRnqAAAAAElFTkSuQmCC
// ==/UserScript==

View File

@ -15,12 +15,12 @@
// @grant GM_deleteValue
// @grant GM_openInTab
// @run-at document-start
// @updateURL https://github.com/zixaphir/appchan-x/raw/Av2/appchan-x.meta.js
// @downloadURL https://github.com/zixaphir/appchan-x/raw/Av2/appchan-x.user.js
// @icon data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAIALAAAAAAQABAAAAIxlI+pq+D9DAgUoFkPDlbs7lGiI2bSVnKglnJMOL6omczxVZK3dH/41AG6Lh7i6qUoAAA7
// @updateURL http://zixaphir.github.com/appchan-x/builds/appchan-x.meta.js
// @downloadURL http://zixaphir.github.com/appchan-x/builds/appchan-x.user.js
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwAgMAAAAqbBEUAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAHFJREFUKFOt0LENACEIBdBv4Qju4wgWanEj3D6OcIVMKaitYHEU/jwTCQj8W75kiVCSBvdQ5/AvfVHBin11BgdRq3ysBgfwBDRrj3MCIA+oAQaku/Q1cNctrAmyDl577tOThYt/Y1RBM4DgOHzM0HFTAyLukH/cmRnqAAAAAElFTkSuQmCC
// ==/UserScript==
/* appchan x - Version 2.0.0 - 2013-03-20
/* appchan x - Version 2.0.0 - 2013-03-28
* http://zixaphir.github.com/appchan-x/
*
* Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
@ -54,16 +54,15 @@
'Catalog Links': [true, 'Turn Navigation links into links to each board\'s catalog.'],
'External Catalog': [false, 'Link to external catalog instead of the internal one.'],
'Enable 4chan\'s Extension': [false, 'Compatibility between appchan x and 4chan\'s inline extension is NOT guaranteed.'],
'Custom Board Navigation': [true, 'Disable this to always display the full board list.'],
'Custom Board Navigation': [true, 'Show custom links instead of the full board list.'],
'404 Redirect': [true, 'Redirect dead threads and images.'],
'Keybinds': [true, 'Bind actions to keyboard shortcuts.'],
'Time Formatting': [true, 'Localize and format timestamps arbitrarily.'],
'Time Formatting': [true, 'Localize and format timestamps.'],
'Relative Post Dates': [false, 'Display dates like "3 minutes ago". Tooltip shows the timestamp.'],
'File Info Formatting': [true, 'Reformat the file information.'],
'Comment Expansion': [true, 'Can expand too long comments.'],
'Thread Expansion': [true, 'Can expand threads to view all replies.'],
'Index Navigation': [false, 'Navigate to previous / next thread.'],
'Custom CSS': [false, 'Apply custom CSS to 4chan.'],
'Comment Expansion': [true, 'Add buttons to expand long comments.'],
'Thread Expansion': [true, 'Add buttons to expand threads.'],
'Index Navigation': [false, 'Add buttons to navigate between threads.'],
'Check for Updates': [true, 'Check for updated versions of appchan x.']
},
'Linkification': {
@ -72,13 +71,13 @@
'Link Title': [true, 'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud']
},
'Filtering': {
'Anonymize': [false, 'Turn everyone Anonymous.'],
'Anonymize': [false, 'Make everyone Anonymous.'],
'Filter': [true, 'Self-moderation placebo.'],
'Recursive Hiding': [true, 'Hide replies of hidden posts, recursively.'],
'Thread Hiding': [true, 'Hide entire threads.'],
'Reply Hiding': [true, 'Hide single replies.'],
'Hiding Buttons': [true, 'Make buttons to hide threads / replies, in addition to menu links.'],
'Stubs': [true, 'Make stubs of hidden threads / replies.']
'Thread Hiding': [true, 'Add buttons to hide entire threads.'],
'Reply Hiding': [true, 'Add buttons to hide single replies.'],
'Hiding Buttons': [true, 'Add buttons to hide threads / replies, in addition to menu links.'],
'Stubs': [true, 'Show stubs of hidden threads / replies.']
},
'Images': {
'Image Expansion': [true, 'Expand images.'],
@ -90,7 +89,9 @@
'Replace JPG': [false, 'Replace jpgs.']
},
'Menu': {
'Menu': [true, 'Add a drop-down menu in posts.'],
'Menu': [true, 'Add a drop-down menu to posts.'],
'Thread Hiding Link': [true, 'Add a link to hide entire threads.'],
'Reply Hiding Link': [true, 'Add a link to hide single replies.'],
'Report Link': [true, 'Add a report link to the menu.'],
'Delete Link': [true, 'Add post and image deletion links to the menu.'],
'Download Link': [true, 'Add a download with original filename link to the menu. Chrome-only currently.'],
@ -116,7 +117,7 @@
'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'],
'Hide Original Post Form': [true, 'Hide the normal post form.']
},
'Quote links': {
'Quote Links': {
'Quote Backlinks': [true, 'Add quote backlinks.'],
'OP Backlinks': [false, 'Add backlinks to the OP.'],
'Quote Inlining': [true, 'Inline quoted post on click.'],
@ -238,7 +239,8 @@
filesize: '',
MD5: ''
},
sauces: "http://iqdb.org/?url=%turl\nhttp://www.google.com/searchbyimage?image_url=%turl\n#http://tineye.com/search?url=%turl\n#http://saucenao.com/search.php?db=999&url=%turl\n#http://3d.iqdb.org/?url=%turl\n#http://regex.info/exif.cgi?imgurl=%url\n# uploaders:\n#http://imgur.com/upload?url=%url;text:Upload to imgur\n#http://omploader.org/upload?url1=%url;text:Upload to omploader\n# \"View Same\" in archives:\n#//archive.foolz.us/_/search/image/%MD5/;text:View same on foolz\n#//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/\n#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/",
sauces: "http://iqdb.org/?url=%TURL\nhttps://www.google.com/searchbyimage?image_url=%TURL\n#//tineye.com/search?url=%TURL\n#http://saucenao.com/search.php?url=%TURL\n#http://3d.iqdb.org/?url=%TURL\n#http://regex.info/exif.cgi?imgurl=%URL\n# uploaders:\n#http://imgur.com/upload?url=%URL;text:Upload to imgur\n#http://ompldr.org/upload?url1=%URL;text:Upload to ompldr\n# \"View Same\" in archives:\n#//archive.foolz.us/_/search/image/%MD5/;text:View same on foolz\n#//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/\n#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/",
'Custom CSS': false,
'Header auto-hide': false,
'Header catalog links': false,
boardnav: '[ toggle-all ] [current-title]',
@ -292,10 +294,6 @@
mascot: ''
};
if (!/^[a-z]+\.4chan\.org$/.test(location.hostname)) {
return;
}
editTheme = {};
editMascot = {};
@ -308,7 +306,7 @@
d = document;
doc = null;
doc = d.documentElement;
g = {
VERSION: '2.0.0',
@ -2913,7 +2911,7 @@
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)));
$.extend($, {
engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase(),
engine: 'gecko',
id: function(id) {
return d.getElementById(id);
},
@ -3208,16 +3206,9 @@
script = $.el('script', {
textContent: code
});
$.add(d.head, script);
$.add(d.head || doc, script);
return $.rm(script);
},
unsafeWindow: window.opera ? window : typeof unsafeWindow !== "undefined" && unsafeWindow !== null ? unsafeWindow : (function() {
var p;
p = d.createElement('p');
p.setAttribute('onclick', 'return window');
return p.onclick();
})(),
bytesToString: function(size) {
var unit;
@ -3228,70 +3219,37 @@
}
size = unit > 1 ? Math.round(size * 100) / 100 : Math.round(size);
return "" + size + " " + ['B', 'KB', 'MB', 'GB'][unit];
},
"delete": function(key) {
var keys, _i, _len;
if (!(keys instanceof Array)) {
keys = [keys];
}
for (_i = 0, _len = keys.length; _i < _len; _i++) {
key = keys[_i];
key = g.NAMESPACE + key;
localStorage.removeItem(key);
GM_deleteValue(key);
}
},
get: function(key, defaultVal) {
var val;
if (val = GM_getValue(g.NAMESPACE + key)) {
return JSON.parse(val);
} else {
return defaultVal;
}
},
set: function(key, val) {
key = g.NAMESPACE + key;
val = JSON.stringify(val);
localStorage.setItem(key, val);
return GM_setValue(key, val);
}
});
if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) {
$["delete"] = function(name) {
return GM_deleteValue(g.NAMESPACE + name);
};
$.get = function(name, defaultValue) {
var value;
if (value = GM_getValue(g.NAMESPACE + name)) {
return JSON.parse(value);
} else {
return defaultValue;
}
};
$.set = function(name, value) {
name = g.NAMESPACE + name;
value = JSON.stringify(value);
localStorage.setItem(name, value);
return GM_setValue(name, value);
};
} else if (window.opera) {
(function() {
var scriptStorage;
scriptStorage = opera.scriptStorage;
$["delete"] = function(name) {
return delete scriptStorage[g.NAMESPACE + name];
};
$.get = function(name, defaultValue) {
var value;
if (value = scriptStorage[g.NAMESPACE + name]) {
return JSON.parse(value);
} else {
return defaultValue;
}
};
return $.set = function(name, value) {
name = g.NAMESPACE + name;
value = JSON.stringify(value);
localStorage.setItem(name, value);
return scriptStorage[name] = value;
};
})();
} else {
$["delete"] = function(name) {
return localStorage.removeItem(g.NAMESPACE + name);
};
$.get = function(name, defaultValue) {
var value;
if (value = localStorage.getItem(g.NAMESPACE + name)) {
return JSON.parse(value);
} else {
return defaultValue;
}
};
$.set = function(name, value) {
return localStorage.setItem(g.NAMESPACE + name, JSON.stringify(value));
};
}
Polyfill = {
init: function() {
return Polyfill.visibility();
@ -3882,7 +3840,7 @@
};
/*
JSColor
JSColor
http://github.com/hotchpotch/jscolor/tree/master
JSColor is color library for JavaScript.
@ -4919,7 +4877,7 @@
innerHTML: '[<a href=javascript:;> - </a>]\u00A0'
});
$.on(btn, 'click', Header.toggleBoardList);
return $.prepend(fullBoardList, btn);
return $.add(fullBoardList, btn);
} else {
$.rm($('#custom-board-list', nav));
return fullBoardList.hidden = false;
@ -4933,8 +4891,8 @@
if (!text) {
return;
}
as = $$('#full-board-list a', Header.nav);
nodes = text.match(/[\w@]+(-(all|title|full|text:"[^"]+"))?|[^\w@]+/g).map(function(t) {
as = $$('#full-board-list a', Header.nav).slice(0, -2);
nodes = text.match(/[\w@]+(-(all|title|full|index|catalog|text:"[^"]+"))*|[^\w@]+/g).map(function(t) {
var a, board, m, _i, _len;
if (/^[^\w@]/.test(t)) {
@ -4954,12 +4912,21 @@
a = as[_i];
if (a.textContent === board) {
a = a.cloneNode(true);
if (/-title$/.test(t)) {
if (/-title/.test(t)) {
a.textContent = a.title;
} else if (/-full$/.test(t)) {
} else if (/-full/.test(t)) {
a.textContent = "/" + board + "/ - " + a.title;
} else if (m = t.match(/-text:"(.+)"$/)) {
a.textContent = m[1];
} else if (/-(index|catalog|text)/.test(t)) {
if (m = t.match(/-(index|catalog)/)) {
a.setAttribute('data-only', m[1]);
a.href = "//boards.4chan.org/" + board + "/";
if (m[1] === 'catalog') {
a.href += 'catalog';
}
}
if (m = t.match(/-text:"(.+)"/)) {
a.textContent = m[1];
}
} else if (board === '@') {
$.addClass(a, 'navSmall');
}
@ -5107,7 +5074,7 @@
Settings = {
init: function() {
var link, settings;
var link, prevVersion, settings;
link = $.el('a', {
id: 'appchanOptions',
@ -5127,9 +5094,12 @@
return $.prepend($.id('navtopright'), [$.tn(' ['), link, $.tn('] ')]);
});
});
if (!$.get('previousversion')) {
if ((prevVersion = $.get('previousversion', null)) !== g.VERSION) {
$.set('lastupdate', Date.now());
$.set('previousversion', g.VERSION);
$.on(d, '4chanXInitFinished', Settings.open);
if (!prevVersion) {
$.on(d, '4chanXInitFinished', Settings.open);
}
}
Settings.addSection('Main', Settings.main);
Settings.addSection('Filter', Settings.filter);
@ -5167,6 +5137,7 @@
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
section = _ref[_i];
link = $.el('a', {
className: "tab-" + section.hyphenatedTitle,
textContent: section.title,
href: 'javascript:;'
});
@ -5177,12 +5148,8 @@
}
}
links.pop();
if (sectionToOpen) {
sectionToOpen.click();
} else {
links[0].click();
}
$.add($('.sections-list', overlay), links);
(sectionToOpen ? sectionToOpen : links[0]).click();
$.on($('.close', overlay), 'click', Settings.close);
$.on(overlay, 'click', Settings.close);
$.on(overlay.firstElementChild, 'click', function(e) {
@ -5203,22 +5170,28 @@
},
sections: [],
addSection: function(title, open) {
var _ref;
var hyphenatedTitle, _ref;
if (typeof title !== 'string') {
_ref = title.detail, title = _ref.title, open = _ref.open;
}
hyphenatedTitle = title.toLowerCase().replace(/\s+/g, '-');
return Settings.sections.push({
title: title,
hyphenatedTitle: hyphenatedTitle,
open: open
});
},
openSection: function() {
var section;
var section, selected;
if (selected = $('.tab-selected', Settings.dialog)) {
$.rmClass(selected, 'tab-selected');
}
$.addClass($(".tab-" + this.hyphenatedTitle, Settings.dialog), 'tab-selected');
section = $('section', Settings.dialog);
section.innerHTML = null;
section.className = "section-" + (this.title.toLowerCase().replace(/\s+/g, '-'));
section.className = "section-" + this.hyphenatedTitle;
this.open(section, g);
return section.scrollTop = 0;
},
@ -5242,7 +5215,7 @@
div = $.el('div', {
innerHTML: "<label><input type=checkbox name=\"" + key + "\" " + checked + ">" + key + "</label><span class=description>: " + description + "</span>"
});
$.on($('input', div), 'click', $.cb.checked);
$.on($('input', div), 'change', $.cb.checked);
$.add(fs, div);
}
$.add(section, fs);
@ -5266,8 +5239,7 @@
});
$.on($('button', div), 'click', function() {
this.textContent = 'Hidden: 0';
$["delete"]("hiddenThreads." + g.BOARD);
return $["delete"]("hiddenPosts." + g.BOARD);
return $["delete"](["hiddenThreads." + g.BOARD, "hiddenPosts." + g.BOARD]);
});
return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div);
},
@ -5285,7 +5257,7 @@
className: 'warning',
textContent: 'Save me!',
download: "appchan x v" + g.VERSION + "-" + now + ".json",
href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data))))),
href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))))),
target: '_blank'
});
if ($.engine !== 'gecko') {
@ -5449,7 +5421,7 @@
$.add(div, ta);
return;
}
return div.innerHTML = "<div class=warning " + (Conf['Sauce'] ? 'hidden' : '') + "><code>Filter</code> is disabled.</div>\n<p>\n Use <a href=https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions>regular expressions</a>, one per line.<br>\n Lines starting with a <code>#</code> will be ignored.<br>\n For example, <code>/weeaboo/i</code> will filter posts containing the string `<code>weeaboo</code>`, case-insensitive.<br>\n MD5 filtering uses exact string matching, not regular expressions.\n</p>\n<ul>You can use these settings with each regular expression, separate them with semicolons:\n <li>\n Per boards, separate them with commas. It is global if not specified.<br>\n For example: <code>boards:a,jp;</code>.\n </li>\n <li>\n Filter OPs only along with their threads (`only`), replies only (`no`), or both (`yes`, this is default).<br>\n For example: <code>op:only;</code>, <code>op:no;</code> or <code>op:yes;</code>.\n </li>\n <li>\n Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).<br>\n For example: <code>stub:yes;</code> or <code>stub:no;</code>.\n </li>\n <li>\n Highlight instead of hiding. You can specify a class name to use with a userstyle.<br>\n For example: <code>highlight;</code> or <code>highlight:wallpaper;</code>.\n </li>\n <li>\n Highlighted OPs will have their threads put on top of board pages by default.<br>\n For example: <code>top:yes;</code> or <code>top:no;</code>.\n </li>\n</ul>";
return div.innerHTML = "<div class=warning " + (Conf['Filter'] ? 'hidden' : '') + "><code>Filter</code> is disabled.</div>\n<p>\n Use <a href=https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions>regular expressions</a>, one per line.<br>\n Lines starting with a <code>#</code> will be ignored.<br>\n For example, <code>/weeaboo/i</code> will filter posts containing the string `<code>weeaboo</code>`, case-insensitive.<br>\n MD5 filtering uses exact string matching, not regular expressions.\n</p>\n<ul>You can use these settings with each regular expression, separate them with semicolons:\n <li>\n Per boards, separate them with commas. It is global if not specified.<br>\n For example: <code>boards:a,jp;</code>.\n </li>\n <li>\n Filter OPs only along with their threads (`only`), replies only (`no`), or both (`yes`, this is default).<br>\n For example: <code>op:only;</code>, <code>op:no;</code> or <code>op:yes;</code>.\n </li>\n <li>\n Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).<br>\n For example: <code>stub:yes;</code> or <code>stub:no;</code>.\n </li>\n <li>\n Highlight instead of hiding. You can specify a class name to use with a userstyle.<br>\n For example: <code>highlight;</code> or <code>highlight:wallpaper;</code>.\n </li>\n <li>\n Highlighted OPs will have their threads put on top of board pages by default.<br>\n For example: <code>top:yes;</code> or <code>top:no;</code>.\n </li>\n</ul>";
},
sauce: function(section) {
var sauce;
@ -5462,7 +5434,7 @@
rice: function(section) {
var event, input, name, _i, _len, _ref;
section.innerHTML = "<fieldset>\n <legend>Custom Board Navigation <span class=warning " + (Conf['Custom Board Navigation'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=boardnav class=field spellcheck=false></div>\n <div>In the following, <code>board</code> can translate to a board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Status/Twitter link (<code>status</code>, <code>@</code>).</div>\n <div>Board link: <code>board</code></div>\n <div>Title link: <code>board-title</code></div>\n <div>Full text link: <code>board-full</code></div>\n <div>Custom text link: <code>board-text:\"VIP Board\"</code></div>\n <div>Full board list toggle: <code>toggle-all</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Time Formatting <span class=warning " + (Conf['Time Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=time class=field spellcheck=false>: <span class=time-preview></span></div>\n <div>Supported <a href=//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</div>\n <div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div>\n <div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div>\n <div>Year: <code>%y</code></div>\n <div>Hour: <code>%k</code>, <code>%H</code>, <code>%l</code>, <code>%I</code>, <code>%p</code>, <code>%P</code></div>\n <div>Minute: <code>%M</code></div>\n <div>Second: <code>%S</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Quote Backlinks formatting <span class=warning " + (Conf['Quote Backlinks'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=backlink class=field spellcheck=false>: <span class=backlink-preview></span></div>\n</fieldset>\n\n<fieldset>\n <legend>File Info Formatting <span class=warning " + (Conf['File Info Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=fileInfo class=field spellcheck=false>: <span class='fileText file-info-preview'></span></div>\n <div>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div>\n <div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</div>\n <div>Spoiler indicator: <code>%p</code></div>\n <div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div>\n <div>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div>\n</fieldset>\n\n<fieldset>\n <legend>Unread Tab Icon <span class=warning " + (Conf['Unread Tab Icon'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <select name=favicon>\n <option value=ferongr>ferongr</option>\n <option value=xat->xat-</option>\n <option value=Mayhem>Mayhem</option>\n <option value=Original>Original</option>\n </select>\n <span class=favicon-preview></span>\n</fieldset>\n\n<fieldset>\n <legend>Custom CSS <span class=warning " + (Conf['Custom CSS'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <button id=apply-css>Apply CSS</button>\n <textarea name=usercss class=field spellcheck=false></textarea>\n</fieldset>";
section.innerHTML = "<fieldset>\n <legend>Custom Board Navigation <span class=warning " + (Conf['Custom Board Navigation'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=boardnav class=field spellcheck=false></div>\n <div>In the following, <code>board</code> can translate to a board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Status/Twitter link (<code>status</code>, <code>@</code>).</div>\n <div>Board link: <code>board</code></div>\n <div>Title link: <code>board-title</code></div>\n <div>Full text link: <code>board-full</code></div>\n <div>Custom text link: <code>board-text:\"VIP Board\"</code></div>\n <div>Index-only link: <code>board-index</code></div>\n <div>Catalog-only link: <code>board-catalog</code></div>\n <div>Combinations are possible: <code>board-index-text:\"VIP Index\"</code></div>\n <div>Full board list toggle: <code>toggle-all</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Time Formatting <span class=warning " + (Conf['Time Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=time class=field spellcheck=false>: <span class=time-preview></span></div>\n <div>Supported <a href=//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</div>\n <div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div>\n <div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div>\n <div>Year: <code>%y</code></div>\n <div>Hour: <code>%k</code>, <code>%H</code>, <code>%l</code>, <code>%I</code>, <code>%p</code>, <code>%P</code></div>\n <div>Minute: <code>%M</code></div>\n <div>Second: <code>%S</code></div>\n</fieldset>\n\n<fieldset>\n <legend>Quote Backlinks formatting <span class=warning " + (Conf['Quote Backlinks'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=backlink class=field spellcheck=false>: <span class=backlink-preview></span></div>\n</fieldset>\n\n<fieldset>\n <legend>File Info Formatting <span class=warning " + (Conf['File Info Formatting'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <div><input name=fileInfo class=field spellcheck=false>: <span class='fileText file-info-preview'></span></div>\n <div>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div>\n <div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</div>\n <div>Spoiler indicator: <code>%p</code></div>\n <div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div>\n <div>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div>\n</fieldset>\n\n<fieldset>\n <legend>Unread Tab Icon <span class=warning " + (Conf['Unread Tab Icon'] ? 'hidden' : '') + ">is disabled.</span></legend>\n <select name=favicon>\n <option value=ferongr>ferongr</option>\n <option value=xat->xat-</option>\n <option value=Mayhem>Mayhem</option>\n <option value=Original>Original</option>\n </select>\n <span class=favicon-preview></span>\n</fieldset>\n\n<fieldset>\n <legend><input type=checkbox name='Custom CSS' " + (Conf['Custom CSS'] ? 'checked' : '') + "> Custom CSS</legend>\n <button id=apply-css>Apply CSS</button>\n <textarea name=usercss class=field spellcheck=false " + (Conf['Custom CSS'] ? '' : 'disabled') + "></textarea>\n</fieldset>";
_ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
name = _ref[_i];
@ -5475,6 +5447,7 @@
Settings[name].call(input);
}
}
$.on($('input[name="Custom CSS"]', section), 'change', Settings.togglecss);
return $.on($.id('apply-css'), 'click', Settings.usercss);
},
boardnav: function() {
@ -5512,14 +5485,18 @@
if (g.VIEW === 'thread' && Conf['Unread Tab Icon']) {
Unread.update();
}
return this.nextElementSibling.innerHTML = "<img src=" + Favicon.unreadSFW + "> <img src=" + Favicon.unreadNSFW + "> <img src=" + Favicon.unreadDead + ">";
return this.nextElementSibling.innerHTML = "<img src=" + Favicon["default"] + ">\n<img src=" + Favicon.unreadSFW + ">\n<img src=" + Favicon.unreadNSFW + ">\n<img src=" + Favicon.unreadDead + ">";
},
togglecss: function() {
if ($('textarea', this.parentNode.parentNode).disabled = !this.checked) {
CustomCSS.rmStyle();
} else {
CustomCSS.addStyle();
}
return $.cb.checked.call(this);
},
usercss: function() {
if (Conf['Custom CSS']) {
return CustomCSS.update();
} else {
return CustomCSS.rmStyle();
}
return CustomCSS.update();
},
keybinds: function(section) {
var arr, input, key, tbody, tr, _ref;
@ -5565,12 +5542,14 @@
}
board = g.BOARD.ID;
if (board === 'g') {
$.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
});
}
if (board === 'sci') {
$.globalEval("window.addEventListener('jsmath', function(e) {\n if (jsMath.loaded) {\n // process one post\n jsMath.ProcessBeforeShowing(e.detail);\n } else {\n // load jsMath and process whole document\n jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);\n jsMath.Autoload.LoadJsMath();\n }\n}, false);");
return Post.prototype.callbacks.push({
name: 'Parse /sci/ math',
cb: this.math
@ -5586,23 +5565,14 @@
_ref = $$('.prettyprint', this.nodes.comment);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
pre = _ref[_i];
pre.innerHTML = $.unsafeWindow.prettyPrintOne(pre.innerHTML);
$.event('prettyprint', pre, window);
}
},
math: function() {
var jsMath;
if (this.isClone || !$('.math', this.nodes.comment)) {
return;
}
jsMath = $.unsafeWindow.jsMath;
if (jsMath) {
if (jsMath.loaded) {
return jsMath.ProcessBeforeShowing(this.nodes.post);
} else {
return $.globalEval("jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);\njsMath.Autoload.LoadJsMath();");
}
}
return $.event('jsmath', this.nodes.post, window);
},
parseThread: function(threadID, offset, limit) {
return $.event('4chanParsingDone', {
@ -5921,7 +5891,7 @@
ThreadHiding = {
init: function() {
if (g.VIEW !== 'index' || !Conf['Thread Hiding']) {
if (g.VIEW !== 'index' || !Conf['Thread Hiding'] && !Conf['Thread Hiding Link']) {
return;
}
Misc.clearThreads("hiddenThreads." + g.BOARD);
@ -5938,7 +5908,7 @@
if (data = ThreadHiding.hiddenThreads.threads[this]) {
ThreadHiding.hide(this, data.makeStub);
}
if (!Conf['Hiding Buttons']) {
if (!Conf['Thread Hiding']) {
return;
}
return $.prepend(this.OP.nodes.root, ThreadHiding.makeButton(this, 'hide'));
@ -5975,7 +5945,7 @@
init: function() {
var apply, div, makeStub;
if (g.VIEW !== 'index' || !Conf['Menu'] || !Conf['Thread Hiding']) {
if (g.VIEW !== 'index' || !Conf['Menu'] || !Conf['Thread Hiding Link']) {
return;
}
div = $.el('div', {
@ -6111,7 +6081,7 @@
ReplyHiding = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Reply Hiding']) {
if (g.VIEW === 'catalog' || !Conf['Reply Hiding'] && !Conf['Reply Hiding Link']) {
return;
}
Misc.clearThreads("hiddenPosts." + g.BOARD);
@ -6137,7 +6107,7 @@
}
}
}
if (!Conf['Hiding Buttons']) {
if (!Conf['Reply Hiding']) {
return;
}
return $.add($('.postInfo', this.nodes.post), ReplyHiding.makeButton(this, 'hide'));
@ -6151,7 +6121,7 @@
init: function() {
var apply, div, makeStub, replies, thisPost;
if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Reply Hiding']) {
if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Reply Hiding Link']) {
return;
}
div = $.el('div', {
@ -6461,7 +6431,7 @@
QuoteStrikeThrough = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Reply Hiding'] && !Conf['Filter']) {
if (g.VIEW === 'catalog' || !Conf['Reply Hiding'] && !Conf['Reply Hiding Link'] && !Conf['Filter']) {
return;
}
return Post.prototype.callbacks.push({
@ -6874,7 +6844,7 @@
Settings.open();
break;
case Conf['Close']:
if ($.id('settings')) {
if ($.id('fourchanx-settings')) {
Settings.close();
} else if ((notifications = $$('.notification')).length) {
for (_i = 0, _len = notifications.length; _i < _len; _i++) {
@ -7111,8 +7081,7 @@
}
},
focus: function(post) {
$.addClass(post, 'highlight');
return $('a[title="Highlight this post"]', post).focus();
return $.addClass(post, 'highlight');
}
};
@ -7741,15 +7710,15 @@
if (data.lastChecked > Date.now() - 12 * $.HOUR) {
return;
}
return $.ajax("//api.4chan.org/" + g.BOARD + "/catalog.json", {
return $.ajax("//api.4chan.org/" + g.BOARD + "/threads.json", {
onload: function() {
var obj, thread, threads, _i, _j, _len, _len1, _ref, _ref1;
var page, thread, threads, _i, _j, _len, _len1, _ref, _ref1;
threads = {};
_ref = JSON.parse(this.response);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
obj = _ref[_i];
_ref1 = obj.threads;
page = _ref[_i];
_ref1 = page.threads;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
thread = _ref1[_j];
if (thread.no in data.threads) {
@ -8008,7 +7977,7 @@
posts.pop();
for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i];
$.addClass(post.nodes.post, 'qphl');
$.addClass(post.nodes.root, 'qphl');
}
}
quoterID = $.x('ancestor::*[@id][1]', this).id.match(/\d+$/)[0];
@ -8043,7 +8012,7 @@
_ref = [post].concat(post.clones);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
post = _ref[_i];
$.rmClass(post.nodes.post, 'qphl');
$.rmClass(post.nodes.root, 'qphl');
}
}
};
@ -8590,16 +8559,13 @@
link = link.replace(/%(T?URL|MD5|board)/ig, function(parameter) {
switch (parameter) {
case '%TURL':
case '%turl':
return "' + post.file.thumbURL + '";
return "' + encodeURIComponent(post.file.thumbURL) + '";
case '%URL':
case '%url':
return "' + post.file.URL + '";
return "' + encodeURIComponent(post.file.URL) + '";
case '%MD5':
case '%md5':
return "' + encodeURIComponent(post.file.MD5) + '";
case '%board':
return "' + post.board + '";
return "' + encodeURIComponent(post.board) + '";
default:
return parameter;
}
@ -8747,7 +8713,7 @@
rect = thumb.parentNode.getBoundingClientRect();
if (rect.bottom > 0) {
postRect = post.nodes.root.getBoundingClientRect();
headRect = Header.bar.getBoundingClientRect();
headRect = Header.toggle.getBoundingClientRect();
top = postRect.top - headRect.top - headRect.height - 2;
root = $.engine === 'webkit' ? d.body : doc;
if (rect.top < 0) {
@ -8814,7 +8780,7 @@
post = Get.postFromNode(this);
$.rm(this);
delete post.file.fullImage;
if (!$.hasClass(post.file.thumb, 'expanding')) {
if (!($.hasClass(post.file.thumb, 'expanding') || $.hasClass(post.nodes.root, 'expanded-image'))) {
return;
}
ImageExpand.contract(post);
@ -9166,7 +9132,7 @@
if (thread.isSticky) {
return 1;
} else {
switch (g.BOARD) {
switch (g.BOARD.ID) {
case 'b':
case 'vg':
case 'q':
@ -9232,7 +9198,7 @@
Main.callbackNodes(Post, posts);
$.after(a, nodes);
if (Conf['Enable 4chan\'s Extension']) {
return $.unsafeWindow.Parser.parseThread(thread.ID, 1, nodes.length);
return $.globalEval("Parser.parseThread(" + thread.ID + ", 1, " + nodes.length + ")");
} else {
return Fourchan.parseThread(thread.ID, 1, nodes.length);
}
@ -9250,7 +9216,9 @@
});
},
node: function() {
return d.title = Get.threadExcerpt(this);
var excerpt;
return d.title = (excerpt = Get.threadExcerpt(this)).length > 80 ? "" + excerpt.slice(0, 77) + "..." : excerpt;
}
};
@ -9438,35 +9406,35 @@
switch (Conf['favicon']) {
case 'ferongr':
Favicon.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAOMHAOgLAnMFAL8AAOgLAukMA/+AgP+rq////////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==';
Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABEklEQVQ4y6VSu07DQBCcXftQhFNQpEC0UEQpLfEXNFTQ8g0WFY0lyuRPUqRJT8kf5BucCFFhiiAyFNZad+aOFJy0utXN7HMO+OcRc5pCaf55e5AYOcbJDZjvDwFxmCTJaQpl5UDWNblcknXNyoF+taMcI2zHGbfjLCD4wYZHg1/Q2ZA0TG48wyU2HwA8nmi/qGN434lvQwVWI2GpYKngaiRM6ppKUCpoB8CrvecpqXwpm0I5a4MRzqKVK4dfMtn7dDLpO7gSMOjAKi+eauwe7qOjFe9vvd96HapP2i2ek7u5zQWn0tnU+6PBDjYf31g74OYLgAsTrPfEJ7vOL1WQ3IF/+9hdBl4reCF/yGhBMRlT2A8kHPXzaYhj2AAAAABJRU5ErkJggg==';
Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAS1BMVEUAAAAAAAAAAAAJAAASAAAZAQAaAQAiAQAkAQAoFBQyAgAzAgA1AgA4AABBAgBXAwBzBQCEBgGvCAG/AADoCwLpDAP/gID/q6v///9zILr8AAAAA3RSTlMAx9dmesIgAAAAc0lEQVQY02WPgQ6DIBBDmTqnbE70Cvb/v3TAnW5OSKB9ybXg3HUBOAmEEH4FQtrSn4gxi+xjVC9SVOEiSvbZI8zSV+/Xo7icnryZ15GObMxvtWUkB/VJW57kHU7fUcHStm8FkncGE/mwP6CGzq/eauHwvT7sWQt3gZLW+AAAAABJRU5ErkJggg==';
Favicon.unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAOMHAADX8QBwfgC2zADX8QDY8nnl8qLp8v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==';
Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4y6WTv0rDUBTGf7d0aREUNwcHK1UEoQgFwQdwEZw617FrQB2yZHXqA3TNIyh0KN19hJQM2Tq4KEmhHY9Dktub9F47+MEll/N9554/H4F/QulblIq+Xx0qq9qiaWpiMq4K6484NVEqeIH4y0zCr0z8ZSZ4gVSq7dUUAhaZsKgJjOSSN5MburX7R0hiAN5Wu+PrWBLn2skYolSUdT6A0fN2mft4LTJPHeFU6PXzE07FbazrgV5fSgCfZbjptMq0MkqFu46pPLJX9oJdm4r48Xl328FZV6odFJX91xeGP/bJvg+Mopu17rBhtcqGhwGq1Ua12nB5jX0HSQyz99znOuYfyGad/0CdC5w7qHxNbvAk3NwKJ6d/2Fgm2Wx0cL8YR/0BY2szrwAAAABJRU5ErkJggg==';
Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAASFBMVEUAAAAAAAAAAAAACAkAERMAGBsAGR0AISUALzQALzUAMTcANjwAP0cAVF8AcH4AeokAorYAtswA1/EA2PISIyV55fKi6fL////l+pZqAAAAA3RSTlMAx9dmesIgAAAAcklEQVQY02VPARLCIAxjsjnUWdcg6/9/ukIr00nvIMldEhrC/wHwA0BE3wBUtnICOStQnrNx5oqqzmzKx9vDPH1Nae3F9U4ig3OzjCIX51treYvMxou13EQmBPtHE14xLiawjgoPtfgOaKHP+9VrEXA8O1v7CmSPE3u0AAAAAElFTkSuQmCC';
Favicon.unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAOMHAFT+ACh5AEncAFT+AFX/Acz/su7/5v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==';
Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4y6VSPWvCUBQ9L2Qp0iLWLSB1Mkh3oYs/oUscxNGhvyF1rtD/0bGD5Be4dXGra6CLHTvYZKrkdAg3773kpQ698OBy7rlf71zgn6bEiQpQ/FdP46a5OL4E9iubWC/SyokKMIzBHRN+8ZM7Jgxj0Ox2liOEGRVnVBbBTJa4mezJaMMlkKVlwWfcN/YXLEuJ4RLYr8rGyrUfANw+6Y86F6/WMF99gukW7E3KN902441Cdbw3AcUAvAnut0llShkVYNK36nWdncMYDZkEvwmDaoLLkZ7QOqSXdYIHBM7Vjt1D5f986wk9l1QuG8wBv6PgdxSuxhq3CmQp8f7I6h5MO2yAU06cciL/aPl92dt1yoMFeH0HXgR/yChJLhnbYr91gPOcGoNvnQAAAABJRU5ErkJggg==';
Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAS1BMVEUAAAAAAAAAAAADCgAGEgAIGgAJGwALJAANJwASNwASOAATOgAVQQAWRAAeWwAgKBsoeQAwkQA/wABJ3ABU/gBV/wHM/7Lu/+b////r+K2AAAAAA3RSTlMAx9dmesIgAAAAc0lEQVQY02WPgQ6DIBBDmTonbk70Cvb/v3TAnW5OSKB9ybXg3HUBOAmEEH4FQtrSn4gxi+xjVC9SVOEiSvbZI8zSV+/Xo7icnryZ15GObMxvtWUmB/VJW0byDqfvqGBp20mB5J3Bi3zYH1BD38/eauHwvT7sEAt1Fb320QAAAABJRU5ErkJggg==';
break;
case 'xat-':
Favicon.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA2ElEQVQ4y61TQQrCMBDMQ8WDIEV6LbT2A4og2Hq0veo7fIAH04dY9N4xmyYlpGmI2MCQTWYy3Wy2DAD7B2wWAzWgcTgVeZKlZRxHNYFi2jM18oBh0IcKtC6ixf22WT4IFLs0owxswXu9egm0Ls6bwfCFfNsJYJKfqoEkd3vgUgFVLWObtzNgVKyruC+ljSzr5OEnBzjvjcQecaQhbZgBb4CmGQw+PoMkTUtdbd8VSEPakcGxPOcsoIgUKy0LecY29BmdBrqRfjIwZ93KLs5loHvBnL3cLH/jF+C/+z5dgUysAAAAAElFTkSuQmCC';
Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABa0lEQVQ4y8WTv0vDUBDHv6/EoaQ/AsZYHJ06SpUiCP0L/AcsToXSoUPFQdo6iyIIVicpdPJfcIkIrXSwguA/4FpK0yHUhiKkOYfkhTR5Ogne8g7e3efuvvce8N/GAj5x5/K6dToeDTPzT1MGgHhSsbTMxujkqHYWzuUAotBNq3L4rMViCQAYO86sdndfoGhlttRBOGBaLpkAWKrdSYuSAUASJXNLrq2nQ2P6szLvEGoAAHR+AaiqG2IYYI26UD/eAarVSmfPmW/t29JmclVLQ1UZ8nk3bPCKbrFIx7q+mNDiK7ud6+n6E5YA4h0xf4B6t8veDEMCICmKssNDfEA8oViGac1S7Y7ijzAYuJDJBC/Doc+1bXslUuzq5rbpiUnk6kHUbDjUbBAAygLETZblsahhP5kDpuWS6a2SdgOAoOC/avAg2R+emzsAILPIRqMAFsD3Y/F3DngEYHmvhQlA4bdAP9z1vbPwZ7/xG/NNlMkOsFNNAAAAAElFTkSuQmCC';
Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAdVBMVEUAAAAAAACKkJGNkpN0d3d0eHdra2dGRkORZ1wAAACmaV6naV4PDw8LCwsLCwvyZWLyZWIeExEyFBTAWlr/eHj/enkAAAAKAAAoAAA4AAA4GhpMAACRAAD/AAD/enn/h4j/m5z/nJ3/0dL/0tL/0tP/09P///9VK8WFAAAAFnRSTlMAPnp6kpKdtcHEzc3p6u7v8PT7/v7++jx7+QAAAIFJREFUGNONj90OgjAMhStKmU5k/h1UmAzUvv8jSrYBIeGC9qLtl/a0JVphAJKUOU36xNfWWiitlU9GUphZbXF/hxg10Li2QdQgPhQ3133c9XLOJvD9uZfI0YOdiiMiJw+2CKIPkZzGtcbgKYIJaI26LAfQOzOqoYNA4Z49Nguv/gEEhw2/C5BUZgAAAABJRU5ErkJggg==';
Favicon.unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA30lEQVQ4y2P4//8/AyWYgSoGQMF/GJ7Y11VVUVoyKTM9ey4Ig9ggMWQ1YA1IBvzXm34YjkH8mPyJB+Nqlp8FYRAbmxoMF6ArSNrw6T0Qf8Amh9cFMEWVR/7/A+L/uORxhgEIt5/+/3/2lf//5wAxiI0uj+4CBlBgxVUvOwtydgXQZpDmi2/+/7/0GmIQSAwkB1IDUkuUAZeABlx+g2zAZ9wGlAOjChba+LwAUgNSi2HA5Am9VciBhSsQQWyoWgZiovEDsdGI1QBYQiLJAGQalpSxyWEzAJYWkGm8clTJjQCZ1hkoVG0CygAAAABJRU5ErkJggg==';
Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABjklEQVQ4y8VTv0tCURT+rtkPU997w8sfFA1NLUWEtlROjQ3O0dQSEvLEIJJmkRoiIXBz6g9oaTECK4ckAwmCIFrzx3Monw9Nnt4G3xN9PpuCznLPPfc7H+d+5xzgv430+FRz4qcnR4VC2fVVrVsBgGUsstvtKArhg6g+VyOgi4n77stzYB3bQvzOZHfYAKAtlWsXccGnxwAgfRXoATuX1U8AJOlnWKNkADAZJWvm5O2sk7ez+riKpUM1AIDYIwVv6QDEOhDxEkP9zNotsLuXlPnVJbN3c87B21neArLiAggFsiVg6zxNU2fhVqta+fYszN9e36TQRzCsRZpK6cQhEd+ezADMHMd5NEyXgGEsslQTa0k/w2lfyBY7JJU68PHy0CVWFGV0gGBm2l08DglBTcyIlyCSoRQAia0RjDtne4tTBgiCof2ovhOlilRVXXZk0taNy7I8NUBgZEru6l11lznPBkxjEwMYkz6gDgkAwCpm8lYxkwcA6TWHdrOBdrPx625Qo5noiWXU0/dn2/gDsiiJvxnPWcEAAAAASUVORK5CYII=';
Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAdVBMVEUAAAAAAACRjoqTkI13dXR4dXRpZ2tFQ0Zcb5EAAABee6ZefKcPDw8LCwsLCwtisPJisPIRFh4UJDJalMB4xP95xP8AAAABBQcHFx4KISoNLToaKzgaVW4ul8N5xP+Hy/+b1P+c1P/R7P/S6//S7P/T7P////9P0rk0AAAAFnRSTlMAPnp6kpKdtcHEzc3p6u7v8PT7/v7++jx7+QAAAIFJREFUGNONj90OgjAMhStKmU5k/h1UmAzUvv8jSrYBIeGC9qLtl/a0JVphAJKUOU36xNfWWiitlU9GUphZbXF/hxg10Li2QdQgPhQ3133c9XLOJvD9uUrk6MFOxRGRkwdbBNGHSE7jWmPwFMEEtEZdlgPonRmvoYNA4Z49Nguv/gEE3A2/sQ7iRgAAAABJRU5ErkJggg==';
Favicon.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA4ElEQVQ4y2P4//8/AyWYgSoGQMF/GJ7YNbGqrKRiUnp21lwQBrFBYshqwBqQDPifdsYYjkH8mInxB+OWx58FYRAbmxoMF6ArKPmU9B6IP2CTw+sCmKKe/5X/gPg/LnmcYQDCs/63/1/9fzYQzwGz0eXRXcAACqy4ZfFnQc7u+V/xD6T55v+LQHwJbBBIDCQHUgNSS5QBt4Cab/2/jDDgMx4DykrKJ8FCG58XQGpAajEMmNw7uQo5sHAFIogNVctATDR+IDYasRoAS0gkGYBMw5IyNjlsBsDSAjKNV44quREAx58Mr9vt5wQAAAAASUVORK5CYII=';
Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABi0lEQVQ4y8WTzysEYRjHP7M/MI3dGVp2RXJwohysOIhyEGdnpRxWkTaRLGeU1mFzcyAl/4Gbg7QHisIdJSy7CsO0B7tel5mJ2eGkPJf3eZ/3+3zf5yf8t0hfdGEpqZXUwm02E3nJ6wqAKgeN+tq6+/hsfNHpaxGI2HHUflnvOGE4NXLgqaUS4CPL23Z8q9eJAaRvETgBM/roMyAlgxuqmzOAx83ZkkggrEYCYdVpN7HixxoArItlqggBEk/kiEkJ1/p5rdvYxHhj61SL6N+MykNLA+URGqQ2OgnTgM4Tob6AuDnPFP3+8nx3V8/e1cXlDoDv9xZJdpC7c/tS5iTnA3yapnVYGJtAlQOGkTPeksENzUrhjCMzhUeuD+9s4kKh4C/5bW11bd4spogdRwUgkiLxkRQJAQitqUJYoihKtiSCyenJRWcn7l8fdCvAskq7XBiGUVNC4CbZ3eKFqbY3D1bjl70lGI/TYA4JAHJaOZXTyinAzaHOe77Ie774624It5n4YkubZ++fbeMnwHeVVSmTml8AAAAASUVORK5CYII=';
Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAdVBMVEUAAAAAAACRipGTjZN2dHd2dHhna2pDRkVckV8AAABepl9ep18PDw8LCwsLCwt08mJ08mIRHhEYMhRpwFqM/3iM/3kAAAAECAIQIAgWLAseOBoePA86dB1mzDOM/3ma/4er/5ur/5zZ/9HZ/9La/9La/9P///85Jx7jAAAAFnRSTlMAPnp6kpKdtcHEzc3p6u7v8PT7/v7++jx7+QAAAIFJREFUGNONj90OgjAMhStKmU5k/h1UmAzUvv8jSrYBIeGC9qLtl/a0JVphAJKUOU36xNfWWiitlU9GUphZbXF/hxg10Li2QdQgPhQ3133c9XLOJvD9uZfI0YOdiiMiJw+2CKIPkZzGtcbgKYIJaI26LAfQOzOqoYNA4Z49Nguv/gEEhw2/C5BUZgAAAABJRU5ErkJggg==';
break;
case 'Mayhem':
Favicon.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4jZ2ScWuDMBDFgw4pIkU0WsoQkWAYIkXZH4N9/+/V3dmfXSrKYIFHwt17j8vdGWNMIkgFuaDgzgQnwRs4EQs5KdolUQtagRN0givEDBTEOjgtGs0Zq8F7cKqqusVxrMQLaDUWcjBSrXkn8gs51tpJSWLk9b3HUa0aNIL5gPBR1/V4kJvR7lTwl8GmAm1Gf9+c3S+89qBHa8502AsmSrtBaEBPbIbj0ah2madlNAPEccdgJDfAtWifBjqWKShRBT6KoiH8QlEUn/qt0CCjnNdmPUwmFWzj9Oe6LpKuZXcwqq88z78Pch3aZU3dPwwc2sWlfZKCW5tWluV8kGvXClLm6dYN4/aUqfCbnEOzNDGhGZbNargvxCzvMGfRJD8UaDVvgkzo6QAAAABJRU5ErkJggg==';
Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABj0lEQVQ4y42TQUorQRCGv+oekpj43pOhOyIiKoHBxTMkuAnEtWcwx/AY3sUbBIRcwCw8gCfIMkaTOOUiNdgGRRuKoav+v2qq/i4BakBmXweUwDoxLF5ZhVkC64rYBHYMUAIvwKuBMEwdaFiCNbAAngEC0NHkxBi73vsOsG92HGPsphigY1wOzfNhqhpC6AEd730RQuh9hQEOAY6A/jeAs3a7/f+bWB84ckCpqg+I8Osjgqo+AKUDViJS8LkGMcY+sJrNZssYY387LiIFsBLgL9AC/pgaArzZlF+sZgO4BG7sfgvcA3MxUtOStBIpX7cS3Klqd9OBTIEr4DlLOsuAmqpODXQOiHMuy/O8FkLoJth/6Uh2gQPg87Q3k+7leX6hqnpmPvM/GWfXWeWGqj5+oUS9LMs6wF7iHAwGJ9ZW5uxpup+UGwEtEVoijEYjKl66PJujmvIW3vsFwBiYqzJXZTweY5wSU6Bd7UP1KoECODUrJpOJAtPhcKjAtXGaYptWs57qWyv9Zn/it1a5knj5Dm3v4q8APeACAAAAAElFTkSuQmCC';
Favicon.unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCElEQVQ4jZ2S4crCMAxF+0OGDJEPKYrIGKOsiJSx/fJRfSAfTJNyKqXfiuDg0C25N2RJjTGmEVrhTzhw7oStsIEtsVzT4o2Jo9ALThiEM8IdHIgNaHo8mjNWg6/ske8bohPo+63QOLzmooHp8fyAICBSQkVz0QKdsFQEV6WSW/D+7+BbgbIDHcb4Kp61XyjyI16zZ8JemGltQtDBSGxB4/GoN+7TpkkjDCsFArm0IYv3U0BbnYtf8BCy+JytsE0X6VyuKhPPK/GAJ14kvZZDZVV3pZIb8MZr6n4o4PDGKn0S5SdDmyq5PnXQsk+Xbhinp03FFzmHJw6xYRiWm9VxnohZ3vOcxdO8ARmXRvbWdtzQAAAAAElFTkSuQmCC';
Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABlklEQVQ4y4WTTWobQRCFv2qNRiP5J1mJLAzBJMiCYBubLATy0uQM1jYrb30M38CHyA20EngZPAhhCEaXMIkjWUjzvKkWzWzcUPRM13vVXfWqDMiBzPcAVMA6MdwfLWJWwDoSO0DbARWwBF4dhGNaQOEB1sAC+J/54Z6kuYMxs2/AiwfCMTuSHhPMV6DKgJak+endffQh6dHMjv1FAG1JsxpmbmYHAQhmdj69vtg6T+/ukTQDmkCzTp5eX2Bm50AIQCXpIQW8t/yCB6AyoAt8kPSUgjyFRZpCzd8DnjMvVMPMjlwNAzY1FYKZ/QRu/P8W+AcszUkdYMctSvlaU+GXpBO/vQR+RBlJmiWXVCYpWOyDfr9/kmD3U1LwarcllbGYkmZmdubfZfHpc1qCyAnBgxR1qXy13Gh0dreHg8Hg0NPKgkcL70n38fslIS8IecFoNIqvCOnwbJskWYso5d8/v6lWS6rVkvF4jHMqXIEu8EWSgDPfe8ChW28ymQgoh8OhgCvndMwnLfecWrWR3vhLGrVRjhKv3gDhOKP2kgPZ3gAAAABJRU5ErkJggg==';
Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAkFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBQcHFx4KISoNLToaVW4oKCgul8M4ODg7OztMTEyRkZHBwcH///9dzWZ0AAAAI3RSTlMEBggKDA4QEhQWFxkbHR8hIyUmKCosLjAxN1hbYc7P0dLc3mzWzBUAAAC+SURBVBjTNY3pcsIwEIM3ePERx/bG5IIe0NIrhVbv/3Y4Ydj9Ic030ogqpY3mDdGGi1EVsYuSvGE2Pkl0TFYAdLGuY1eMWGowzzN6kX41DYVpNbvdKlO4Jx5gSbi2VO+Vcq2jrc/jNLQhtM+n05PfkrKxG/oFHIEXqwqQsVRy7n+AtwLYL3sYR3wA755Jp3Vvv8cn8Js0GXmA7/P5TwzpiLn8MOALuEZNygkm5JTy/+vl4BRVbJvQ1NbWRSxXN64PGOBlhG0qAAAAAElFTkSuQmCC';
Favicon.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCklEQVQ4jZ2S0WrDMAxF/TBCCKWMYhZKCSGYmFJMSNjD/mhf239qJXNcjBdTWODgRLpXKJKNMaYROuFTOHEehFb4gJZYrunwxsSXMApOmIQzwgOciE1oRjyaM1aDj+yR7xuiHvT9VmgcXnPRwO/9+wWCgEgJFc1FCwzCVhFclUpuw/u3g3cFyg50GPOjePZ+ocjPeM2RCXthpbUFwQAzsQ2Nx6PeuE+bJo0w7BQI5NKGLN5XAW11LX7BQ8jia7bCLl2kc7mqTLzuxAOeeJH0Wk6VVf0oldyEN15T948CDm+sMiZRfjK0pZIbUwcd+3TphnF62lR8kXN44hAbhmG5WQNnT8zynucsnuYJhFpBfkMzqD4AAAAASUVORK5CYII=';
Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABlklEQVQ4y4WTv04bQRDGf7N3/nMmCSIFVRoLZCxFQQJTWHKP8gi4paHOY/AIvANv4ModBXCFRRO5zAskSnL8se+jmbVW13il0d7tfN/sznwzBrSB3PcA1MAqMdwfLWJegVUk9oDCATXwDLw4CMd0gK4HWAEV8D/3w4+Slg7GzL4C/zwQjtmR9JRgDoE6BzqSllcPZ9GHpCcz++YvAigkLRqYpZl9CUAws9Ob0f3GefVwhqQF0AJaTfLN6B4zOwVCAGpJjylg2/ILHoHagH1gV9LPFOQpVGkKDf8A+J17oTIzO3I1DFg3VAhmdgn88P9r4C/wbE7qATtuUcqXhgq3ko799hI4jzKSNEtbUpmkYLEPhsPhcYL9lJKCV7uQVMZiSlqY2Yl/l3v9Ii1B5ITgQbpNqXx13Gh/yDaH4/G472nlwaOFbdIdfv9Mq8hoFRnT6TS+IqTDs2mSZFVRyl93f3ir1rxVa2azGc6pcQX2gQNJAk58HwB9t8F8PhdQTiYTARfO6ZlPWttz6jRGeu0vyRqjHCV+fQf4OaM8g/XFLAAAAABJRU5ErkJggg==';
Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAkFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAECAIQIAgWLAsePA8oKCg4ODg6dB07OztMTExmzDORkZHBwcH///92I3mvAAAAI3RSTlMEBggKDA4QEhQWFxkbHR8hIyUmKCosLjAxN1hbYc7P0dLc3mzWzBUAAAC+SURBVBjTNY3pcsIwEIM3ePERx/bG5IIe0NIT0ur93w4nDLs/pPlGGlGltNG8IdpwMaoidlGSN8zGJ4mOyQqALtZ17IoRSw3meUYv0q+moTCtZrdbZQr3xAMsCdeW6r1SrnW09XmchjaE9vl0evJbUjZ2Q7+AI/BiVQEylkrO/TfwVgD7ZQ/jiA/g3TPptO7t9/gEfpImIw/wez7/iSEdMZcfBnwB16hJOcGEnFL+f70cnKKKbROa2tq6iOXqBuMXGTe4CAUbAAAAAElFTkSuQmCC';
break;
case 'Original':
Favicon.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAsUlEQVQ4y6VTPQ6DIQh9NG5degyPQOJJetZOTj1R5/ctYggVbVISEoTHPwJ/kphAklMpIitwihmGyR54wghJiggYyiI5s2wxJ8AoN01wWzmfiBbUMsT+4hxO9gnyFLP23qmqVFX23vOCswCq6oO/TV+is613yBL1gx7ZkZCDfVcAWN271sqt8yqAhre1WX5d3RPAfXHhZfU5ViN+Afi4w8pnEEr0ttYaAeRrNKfNZ/qyXeAkApbmVGieAAAAAElFTkSuQmCC';
Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAALVBMVEUAAAAAAAAAAAAAAAAKAAAoAAAoKCg4AAA4ODg7OztMAACRAADBwcH/AAD///+WCcPSAAAAA3RSTlMAx9dmesIgAAAAZ0lEQVQI1z2LsQmAUAxEb4Isk0rwp3EPR3ECcRQrh7C3/nAasPwzmCgYuPBy5AH/NALSImqAK+H1oJRqyJVHNAnZqDITVhj7/PrAciJ9il0BHs/jjU+fnB9sQ0IxX6OBO6Xr0xKAxANLZzUanCWzZQAAAABJRU5ErkJggg==';
Favicon.unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA3UlEQVQ4y6VTMQ6DMAw8U9RWVQe2bp0zdepgCbH1M7yI90QMXTox5R9dEAPuQqIoxKVST0KKzueL8QHwJ8gfREQCSUQ5sapZCuGJhVuaUkSEiHDrekTuWHjyzapGEwxtAz+IViMiKnLNW7h1fZgg+37pHrbqQRQjvdVaK8wszCzWWlHH0wyYOTZ/er5Mm328uRQiVCuDdJnxkogIh8s1dBtjjHMOAFBoMabYnc7h7JwL5uWv0VX3B4r9ccUXKTG0Tdbg7V6YpxHzNOrbj/LNfgd1XQsAPUbf9OVnWtU+UtzonSagmcwAAAAASUVORK5CYII=';
Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAALVBMVEUAAAAAAAAAAAAAAAABBQcHFx4KISoNLToaVW4oKCgul8M4ODg7OzvBwcH///8uS/CdAAAAA3RSTlMAx9dmesIgAAAAZ0lEQVQI1z2LsQ2AUAhEbwKWoftRGvdwBEewchM7d9BFbE6pbP4Mgj+R5MjjwgP+qQSkRtQAV8K3lVI2Q648oknIRpWZsMI4988HjgvpU+wO8HgeHzR9cjZYhoRiPkcDd0rXpyUAiRd5YjKC7MvNRgAAAABJRU5ErkJggg==';
Favicon.unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA30lEQVQ4y6VTMQ6CQBCcBTSaWBBfYGFBD8UmfMCX8Ch+c5UW0N8DfIGNBSFrw10ux62YOAnJZXZ2btkB4E+QO4iIeJKIUmJVsxT8Ewq3NIWICBGhG2oE7lh4cs2qRhP0zQg3iFYjIspSzVvohtpPkHy/eA9bdS8KEd9qjBFmFmYWY4yo42kGzBya3x1fxM0u3lQKAcqVQbzMcElEhPJy8N1VVVXWWgBApsUYY3/K/dla682LX6O73s7YHfMVn8VE34xJg+fjhek9Y3rP+vaDfJPfQdu2AkCP0TV9+ZlWtQ9lu+fiaucJAgAAAABJRU5ErkJggg==';
Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAALVBMVEUAAAAAAAAAAAAAAAAECAIQIAgWLAsePA8oKCg4ODg6dB07OztmzDPBwcH///+rsf3XAAAAA3RSTlMAx9dmesIgAAAAZ0lEQVQI1z2LsQ2AUAhEbwKWofRL4x6O4AhuopWb2P4F7E5prP4MgiaSHHlceMA/jYC0iBrgSnjdKaUacuURTUI2qsyEFcaxvD6wnkifYleAx/N449Mn5wfbkFDM52jgTun6tAQg8QAEvjQg42KY2AAAAABJRU5ErkJggg==';
}
if (Favicon.SFW) {
Favicon.unread = Favicon.unreadSFW;
@ -9859,9 +9827,9 @@
var length, threadID;
threadID = ThreadUpdater.thread.ID;
length = ThreadUpdater.root.children.length;
length = $$('.thread > .postContainer', ThreadUpdater.root).length;
if (Conf['Enable 4chan\'s Extension']) {
return $.unsafeWindow.Parser.parseThread(threadID, -count);
return $.globalEval("Parser.parseThread(" + threadID + ", " + (-count) + ")");
} else {
return Fourchan.parseThread(threadID, length - count, length);
}
@ -9909,6 +9877,9 @@
}
},
ready: function() {
if (!Main.isThisPageLegit()) {
return;
}
ThreadWatcher.refresh();
return $.add(d.body, ThreadWatcher.dialog);
},
@ -10899,6 +10870,9 @@
var applyBlob, cv, data, height, i, l, s, ui8a, width, _i;
s = 90 * 2;
if (_this.file.type === 'image/gif') {
s *= 3;
}
height = img.height, width = img.width;
if (height < s || width < s) {
if (window.URL) {
@ -11051,8 +11025,16 @@
}), this.ready.bind(this));
},
ready: function() {
var MutationObserver, imgContainer, input, observer;
var MutationObserver, imgContainer, input, observer, setLifetime,
_this = this;
setLifetime = function(e) {
return _this.lifetime = e.detail;
};
$.on(window, 'captcha:timeout', setLifetime);
$.globalEval('window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))');
$.off(window, 'captcha:timeout', setLifetime);
c.log(this.lifetime);
imgContainer = $.el('div', {
className: 'captcha-img',
title: 'Reload',
@ -11153,7 +11135,7 @@
if (!this.nodes.challenge.firstChild) {
return;
}
this.timeout = Date.now() + $.unsafeWindow.RecaptchaState.timeout * $.SECOND - $.MINUTE;
this.timeout = Date.now() + this.lifetime * $.SECOND - $.MINUTE;
challenge = this.nodes.challenge.firstChild.value;
this.nodes.img.alt = challenge;
this.nodes.img.src = "//www.google.com/recaptcha/api/image?c=" + challenge;
@ -11177,7 +11159,7 @@
return this.nodes.input.alt = count;
},
reload: function(focus) {
$.unsafeWindow.Recaptcha.reload('t');
$.globalEval('Recaptcha.reload("t")');
if (focus) {
return this.nodes.input.focus();
}
@ -11196,7 +11178,7 @@
dialog: function() {
var dialog, mimeTypes, name, node, nodes, thread, _i, _j, _len, _len1, _ref, _ref1;
dialog = UI.dialog('qr', 'top:0;right:0;', "<div id=qrtab>\n <input type=checkbox id=autohide title=Auto-hide>\n <span class=move></span>\n <a href=javascript:; class=close title=Close>×</a>\n</div>\n<form>\n <div class=persona>\n <input id=dump-button type=button title='Dump list' value=+>\n <input name=name data-name=name title=Name placeholder=Name class=field size=1>\n <input name=email data-name=email title=E-mail placeholder=E-mail class=field size=1>\n <input name=sub data-name=sub title=Subject placeholder=Subject class=field size=1>\n </div>\n <div class=textarea>\n <textarea data-name=com title=Comment placeholder=Comment class=field></textarea>\n <span id=char-count></span>\n </div>\n <div id=dump-list-container>\n <div id=dump-list></div>\n <a id=add-post href=javascript:; title=\"Add a post\">+</a>\n </div>\n <div id=file-n-submit>\n <input id=qr-file-button type=button value='Choose files'>\n <span id=qr-filename-container>\n <span id=qr-no-file>No selected file</span>\n <span id=qr-filename></span>\n </span>\n <a id=qr-filerm href=javascript:; title='Remove file' tabindex=-1>×</a>\n <input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=-1>\n <input type=submit>\n </div>\n <input type=file multiple>\n</form>\n<select title='Create a new thread / Reply'>\n <option value=new>New thread</option>\n</select>".replace(/>\s+</g, '><'));
dialog = UI.dialog('qr', 'top:0;right:0;', "<div id=qrtab>\n <input type=checkbox id=autohide title=Auto-hide>\n <span class=move></span>\n <a href=javascript:; class=close title=Close>×</a>\n</div>\n<form>\n <div class=persona>\n <input id=dump-button type=button title='Dump list' value=+>\n <input name=name data-name=name title=Name placeholder=Name class=field size=1>\n <input name=email data-name=email title=E-mail placeholder=E-mail class=field size=1>\n <input name=sub data-name=sub title=Subject placeholder=Subject class=field size=1>\n </div>\n <div class=textarea>\n <textarea data-name=com title=Comment placeholder=Comment class=field></textarea>\n <span id=char-count></span>\n </div>\n <div id=dump-list-container>\n <div id=dump-list></div>\n <a id=add-post href=javascript:; title=\"Add a post\">+</a>\n </div>\n <div id=file-n-submit>\n <input type=submit>\n <input id=qr-file-button type=button value='Choose files'>\n <span id=qr-filename-container>\n <span id=qr-no-file>No selected file</span>\n <span id=qr-filename></span>\n </span>\n <a id=qr-filerm href=javascript:; title='Remove file'>×</a>\n <input type=checkbox id=qr-file-spoiler title='Spoiler image'>\n </div>\n <input type=file multiple>\n</form>\n<select title='Create a new thread / Reply'>\n <option value=new>New thread</option>\n</select>".replace(/>\s+</g, '><'));
QR.nodes = nodes = {
el: dialog,
move: $('.move', dialog),
@ -11366,7 +11348,9 @@
post.unlock();
QR.cooldown.auto = false;
QR.status();
return QR.error('Network error.');
return QR.error($.el('span', {
innerHTML: 'Connection error. You may have been <a href=//www.4chan.org/banned target=_blank>banned</a>.'
}));
}
};
opts = {
@ -11390,7 +11374,7 @@
return QR.status();
},
response: function() {
var URL, ban, board, err, h1, persona, post, postID, req, threadID, tmpDoc, _, _base, _ref, _ref1;
var URL, ban, board, err, h1, isReply, persona, post, postID, req, threadID, tmpDoc, _, _base, _ref, _ref1;
req = QR.req;
delete QR.req;
@ -11441,6 +11425,7 @@
_ref1 = h1.nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = _ref1[0], threadID = _ref1[1], postID = _ref1[2];
postID = +postID;
threadID = +threadID || postID;
isReply = threadID !== postID;
((_base = QR.yourPosts.threads)[threadID] || (_base[threadID] = [])).push(postID);
$.set("yourPosts." + g.BOARD, QR.yourPosts);
ThreadUpdater.postID = postID;
@ -11449,16 +11434,16 @@
threadID: threadID,
postID: postID
}, QR.nodes.el);
QR.cooldown.auto = QR.posts.length > 1;
QR.cooldown.auto = QR.posts.length > 1 && isReply;
post.rm();
QR.cooldown.set({
req: req,
post: post,
isReply: !!threadID
isReply: isReply
});
if (threadID === postID) {
URL = "/" + g.BOARD + "/res/" + threadID;
} else if (g.VIEW === 'index' && !QR.cooldown.auto) {
} else if (g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab']) {
URL = "/" + g.BOARD + "/res/" + threadID + "#p" + postID;
}
if (URL) {
@ -11498,7 +11483,7 @@
field = $.id('recaptcha_response_field');
$.on(field, 'keydown', function(e) {
if (e.keyCode === 8 && !field.value) {
return $.unsafeWindow.Recaptcha.reload('t');
return $.globalEval('Recaptcha.reload("t")');
}
});
return $.on(form, 'submit', function(e) {
@ -11602,9 +11587,9 @@
this.nodes.uniqueID = uniqueID;
this.info.uniqueID = uniqueID.firstElementChild.textContent;
}
if (capcode = $('.capcode', info)) {
if (capcode = $('.capcode.hand', info)) {
this.nodes.capcode = capcode;
this.info.capcode = capcode.textContent;
this.info.capcode = capcode.textContent.replace('## ', '');
}
if (flag = $('.countryFlag', info)) {
this.nodes.flag = flag;
@ -11887,11 +11872,6 @@
init: function() {
var flatten, initFeatures, key, pathname, val;
$.asap((function() {
return d.documentElement;
}), function() {
return doc = d.documentElement;
});
flatten = function(parent, obj) {
var key, val;
@ -12079,7 +12059,8 @@
Main.callbackNodes(Thread, threads);
Main.callbackNodes(Post, posts);
}
return $.event('4chanXInitFinished');
$.event('4chanXInitFinished');
return Main.checkUpdate();
},
callbackNodes: function(klass, nodes) {
var callback, err, errors, i, len, node, _i, _j, _len, _ref;
@ -12112,10 +12093,56 @@
var Klass, obj;
obj = e.detail;
Klass = obj.type === 'Post' ? Post : Thread;
if (typeof obj.callback.name !== 'string') {
throw new Error("Invalid callback name: " + obj.callback.name);
}
switch (obj.type) {
case 'Post':
Klass = Post;
break;
case 'Thread':
Klass = Thread;
break;
default:
return;
}
obj.callback.isAddon = true;
return Klass.prototype.callbacks.push(obj.callback);
},
checkUpdate: function() {
var freq, now;
if (!Main.isThisPageLegit()) {
return;
}
now = Date.now();
freq = 7 * $.DAY;
if ($.get('lastupdate', 0) > now - freq || $.get('lastchecked', 0) > now - $.DAY) {
return;
}
return $.ajax('http://zixaphir.github.com/appchan-x/builds/version', {
onload: function() {
var el, version;
if (this.status !== 200) {
return;
}
version = this.response;
if (!/^\d\.\d+\.\d+$/.test(version)) {
return;
}
if (g.VERSION === version) {
$.set('lastupdate', now);
return;
}
$.set('lastchecked', now);
el = $.el('span', {
innerHTML: "Update: appchan x v" + version + " is out, get it <a href=http://zixaphir.github.com/appchan-x/ target=_blank>here</a>."
});
return new Notification('info', el, 2 * $.MINUTE);
}
});
},
handleErrors: function(errors) {
var div, error, logs, _i, _len;
@ -12129,7 +12156,7 @@
return;
}
div = $.el('div', {
innerHTML: "" + errors.length + " errors occured. [<a href=javascript:;>show</a>]"
innerHTML: "" + errors.length + " errors occurred. [<a href=javascript:;>show</a>]"
});
$.on(div.lastElementChild, 'click', function() {
if (this.textContent === 'show') {
@ -12164,8 +12191,10 @@
return [message, error];
},
isThisPageLegit: function() {
var _ref;
if (!('thisPageIsLegit' in Main)) {
Main.thisPageIsLegit = !$('link[href*="favicon-status.ico"]', d.head) && d.title !== '4chan - Temporarily Offline';
Main.thisPageIsLegit = location.hostname === 'boards.4chan.org' && !$('link[href*="favicon-status.ico"]', d.head) && ((_ref = d.title) !== '4chan - Temporarily Offline' && _ref !== '4chan - Error');
}
return Main.thisPageIsLegit;
}

816
css/style.css Normal file
View File

@ -0,0 +1,816 @@
/* General */
.dialog {
box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
border: 1px solid;
display: block;
padding: 0;
}
.field {
border: 1px solid #CCC;
-moz-box-sizing: border-box;
box-sizing: border-box;
color: #333;
font: 13px sans-serif;
margin: 0;
padding: 2px 4px 3px;
outline: none;
-webkit-transition: color .25s, border-color .25s, -webkit-flex .25s;
transition: color .25s, border-color .25s, flex .25s;
}
.field::-moz-placeholder,
.field:hover::-moz-placeholder {
color: #AAA !important;
}
.field:hover {
border-color: #999;
}
.field:hover, .field:focus {
color: #000;
}
.field[disabled] {
background-color: #F2F2F2;
color: #888;
}
.move {
cursor: move;
}
label, .favicon {
cursor: pointer;
}
a[href="javascript:;"] {
text-decoration: none;
}
.warning {
color: red;
}
/* 4chan style fixes */
.opContainer, .op {
display: block !important;
}
.post {
overflow: visible !important;
}
[hidden] {
display: none !important;
}
/* fixed, z-index */
#overlay,
#qp, #ihover,
#updater, #thread-stats,
#navlinks, #header,
#qr {
position: fixed;
}
#overlay {
z-index: 999;
}
#notifications {
z-index: 70;
}
#qp, #ihover {
z-index: 60;
}
#menu {
z-index: 50;
}
#navlinks, #updater, #thread-stats {
z-index: 40;
}
#qr {
z-index: 30;
}
#watcher:hover {
z-index: 20;
}
#header {
z-index: 10;
}
#watcher {
z-index: 5;
}
/* Header */
.fourchan-x body {
-moz-box-sizing: border-box;
box-sizing: border-box;
}
#header {
top: 0;
right: 0;
left: 0;
}
#header-bar {
border-width: 0 0 1px;
display: -webkit-flex;
display: flex;
padding: 3px 4px 4px;
position: relative;
-webkit-transition: all .1s .05s ease-in-out;
transition: all .1s .05s ease-in-out;
}
#board-list {
-webkit-flex: 1;
flex: 1;
text-align: center;
}
#header-bar.autohide:not(:hover) {
box-shadow: none;
margin-bottom: -1em;
-webkit-transform: translateY(-100%);
transform: translateY(-100%);
-webkit-transition: all .8s .6s cubic-bezier(.55, .055, .675, .19);
transition: all .8s .6s cubic-bezier(.55, .055, .675, .19);
}
#toggle-header-bar {
cursor: n-resize;
left: 0;
right: 0;
bottom: -8px;
height: 10px;
position: absolute;
}
#header-bar.autohide:not(:hover) #toggle-header-bar, #toggle-header-bar:hover {
bottom: -16px;
height: 18px;
}
#header-bar.autohide #toggle-header-bar {
cursor: s-resize;
}
#header-bar a:not(.entry) {
text-decoration: none;
padding: 1px;
}
#shortcuts:empty {
display: none;
}
.brackets-wrap::before {
content: "\\00a0[";
}
.brackets-wrap::after {
content: "]\\00a0";
}
.disabled,
.expand-all-shortcut {
opacity: .45;
}
/* Notifications */
#notifications {
height: 0;
text-align: center;
position: fixed;
top: 0;
right: 0;
left: 0;
}
.notification {
color: #FFF;
font-weight: 700;
text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
border-radius: 2px;
margin: 1px auto;
width: 500px;
max-width: 100%;
position: relative;
-webkit-transition: all .25s ease-in-out;
transition: all .25s ease-in-out;
}
.notification.error {
background-color: hsla(0, 100%, 38%, .9);
}
.notification.warning {
background-color: hsla(36, 100%, 38%, .9);
}
.notification.info {
background-color: hsla(200, 100%, 38%, .9);
}
.notification.success {
background-color: hsla(104, 100%, 38%, .9);
}
.notification a {
color: white;
}
.notification > .close {
padding: 6px;
top: 0;
right: 0;
position: absolute;
}
.message {
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 6px 20px;
max-height: 200px;
width: 100%;
overflow: auto;
}
/* Settings */
#overlay {
background-color: rgba(0, 0, 0, .5);
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
}
#fourchanx-settings {
-moz-box-sizing: border-box;
box-sizing: border-box;
box-shadow: 0 0 15px rgba(0, 0, 0, .15);
height: 600px;
min-height: 0;
max-height: 100%;
width: 900px;
min-width: 0;
max-width: 100%;
padding: 3px;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
}
#fourchanx-settings > nav {
display: -webkit-flex;
display: flex;
padding: 2px 2px 0;
}
#fourchanx-settings > nav a {
text-decoration: underline;
}
#fourchanx-settings > nav a.close {
text-decoration: none;
padding: 2px;
}
.sections-list {
-webkit-flex: 1;
flex: 1;
}
.tab-selected {
font-weight: 700;
}
.section-container {
-webkit-flex: 1;
flex: 1;
position: relative;
}
.section-container > section {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
}
.section-sauce ul,
.section-rice ul {
list-style: none;
margin: 0;
padding: 8px;
}
.section-sauce li,
.section-rice li {
padding-left: 4px;
}
.section-main label {
text-decoration: underline;
}
.section-filter ul {
padding: 0;
}
.section-filter li {
margin: 10px 40px;
}
.section-filter textarea {
height: 500px;
}
.section-sauce textarea {
height: 350px;
}
.section-rice .field[name="boardnav"] {
width: 100%;
}
.section-rice textarea {
height: 150px;
}
#fourchanx-settings fieldset {
border: 1px solid;
border-radius: 3px;
}
#fourchanx-settings legend {
font-weight: 700;
}
#fourchanx-settings textarea {
font-family: monospace;
min-width: 100%;
max-width: 100%;
}
#fourchanx-settings code {
color: #000;
background-color: #FFF;
padding: 0 2px;
}
.unscroll {
overflow: hidden;
}
/* Unread */
#unread-line {
margin: 0;
}
/* Thread Updater */
#updater:not(:hover) {
background: none;
border: none;
box-shadow: none;
}
#updater > .move {
padding: 0 3px;
}
#updater > div:last-child {
text-align: center;
}
#updater input[type=number] {
width: 4em;
}
#updater:not(:hover) > div:not(.move) {
display: none;
}
.new {
color: limegreen;
}
/* Thread Watcher */
#watcher {
padding-bottom: 3px;
overflow: hidden;
white-space: nowrap;
}
#watcher:not(:hover) {
max-height: 220px;
}
#watcher > .move {
padding-top: 3px;
}
#watcher > div {
max-width: 200px;
overflow: hidden;
padding-left: 3px;
padding-right: 3px;
text-overflow: ellipsis;
}
#watcher a {
text-decoration: none;
}
/* Thread Stats */
#thread-stats {
background: none;
border: none;
box-shadow: none;
}
/* Quote */
.deadlink {
text-decoration: none !important;
}
.backlink.deadlink:not(.forwardlink), .quotelink.deadlink:not(.forwardlink) {
text-decoration: underline !important;
}
.inlined {
opacity: .5;
}
#qp input, .forwarded {
display: none;
}
.quotelink.forwardlink,
.backlink.forwardlink {
text-decoration: none;
border-bottom: 1px dashed;
}
.filtered {
text-decoration: underline line-through;
}
.inline {
border: 1px solid;
display: table;
margin: 2px 0;
}
.inline .post {
border: 0 !important;
background-color: transparent !important;
display: table !important;
margin: 0 !important;
padding: 1px 2px !important;
}
#qp > .opContainer::after {
content: '';
clear: both;
display: table;
}
#qp .post {
border: none;
margin: 0;
padding: 2px 2px 5px;
}
#qp img {
max-height: 300px;
max-width: 500px;
}
.qphl > .post {
outline: 2px solid rgba(216, 94, 49, .7);
}
/* File */
.fileText:hover .fntrunc,
.fileText:not(:hover) .fnfull,
.expanded-image > .post > .file > .fileThumb > img[data-md5],
:not(.expanded-image) > .post > .file > .fileThumb > .full-image {
display: none;
}
.expanding {
opacity: .5;
}
.expanded-image {
clear: both;
}
.expanded-image > .op > .file::after {
content: '';
clear: both;
display: table;
}
:root.fit-width .full-image {
max-width: 100%;
}
:root.gecko.fit-width .full-image,
:root.presto.fit-width .full-image {
width: 100%;
}
#ihover {
-moz-box-sizing: border-box;
box-sizing: border-box;
max-height: 100%;
max-width: 75%;
padding-bottom: 16px;
}
/* Index/Reply Navigation */
#navlinks {
font-size: 16px;
top: 25px;
right: 10px;
}
/* Filter */
.opContainer.filter-highlight {
box-shadow: inset 5px 0 rgba(255, 0, 0, .5);
}
.filter-highlight > .reply {
box-shadow: -5px 0 rgba(255, 0, 0, .5);
}
/* Thread & Reply Hiding */
.hide-thread-button,
.hide-reply-button {
float: left;
margin-right: 2px;
}
.stub ~ .sideArrows,
.stub ~ .hide-reply-button,
.stub ~ .post {
display: none !important;
}
.stub input {
display: inline-block;
}
/* QR */
.hide-original-post-form #postForm,
.hide-original-post-form .postingMode,
#qr.autohide:not(:hover) > form {
display: none;
}
#qr select, #dump-button, .remove, .captcha-img {
cursor: pointer;
}
#qr > div {
min-width: 300px;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
}
#qr .move {
-webkit-align-self: stretch;
align-self: stretch;
-webkit-flex: 1;
flex: 1;
}
#qr select {
margin: 0;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
background: none;
}
.presto #qr select {
height: 1em;
}
#qr .close {
padding: 0 3px;
}
#qr > form {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
}
.persona {
display: -webkit-flex;
display: flex;
}
.persona .field {
-webkit-flex: 1;
flex: 1;
}
.persona .field:focus {
-webkit-flex: 4;
flex: 4;
}
#dump-button {
background: -webkit-linear-gradient(#EEE, #CCC);
background: linear-gradient(#EEE, #CCC);
border: 1px solid #CCC;
margin: 0;
padding: 2px 4px 3px;
outline: none;
width: 30px;
}
#dump-button:hover, #dump-button:focus {
background: -webkit-linear-gradient(#FFF, #DDD);
background: linear-gradient(#FFF, #DDD);
}
#dump-button:active, .dump #dump-button:not(:hover):not(:focus) {
background: -webkit-linear-gradient(#CCC, #DDD);
background: linear-gradient(#CCC, #DDD);
}
.gecko #dump-button {
padding: 0;
}
#qr:not(.dump) #dump-list-container {
display: none;
}
#dump-list-container {
height: 100px;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-o-user-select: none;
user-select: none;
}
#dump-list {
counter-reset: qrpreviews;
top: 0; right: 0; bottom: 0; left: 0;
overflow: hidden;
position: absolute;
white-space: nowrap;
}
#dump-list:hover {
bottom: -12px;
overflow-x: auto;
z-index: 1;
}
#dump-list::-webkit-scrollbar {
height: 12px;
}
#dump-list::-webkit-scrollbar-thumb {
border: 1px solid;
}
.qr-preview {
background-position: 50% 20%;
background-size: cover;
border: 1px solid #808080;
color: #FFF !important;
font-size: 12px;
-moz-box-sizing: border-box;
box-sizing: border-box;
cursor: move;
display: inline-block;
height: 92px; width: 92px;
margin: 4px; padding: 2px;
opacity: .6;
outline: none;
overflow: hidden;
position: relative;
text-shadow: 0 1px 1px #000;
-webkit-transition: opacity .25s ease-in-out;
transition: opacity .25s ease-in-out;
vertical-align: top;
white-space: pre;
}
.qr-preview:hover, .qr-preview:focus {
opacity: .9;
color: #FFF !important;
}
.qr-preview#selected {
opacity: 1;
}
.qr-preview::before {
counter-increment: qrpreviews;
content: counter(qrpreviews);
font-weight: 700;
text-shadow: 0 0 3px #000, 0 0 5px #000;
position: absolute;
top: 3px; right: 3px;
}
.qr-preview.drag {
border-color: red;
border-style: dashed;
}
.qr-preview.over {
border-color: #FFF;
border-style: dashed;
}
.remove {
color: #E00 !important;
font-weight: 700;
padding: 3px;
}
.remove:hover::after {
content: ' Remove';
}
.qr-preview > label {
background: rgba(0, 0, 0, .5);
right: 0; bottom: 0; left: 0;
position: absolute;
text-align: center;
}
.qr-preview > label > input {
margin: 1px 0;
vertical-align: bottom;
}
#add-post {
display: inline-block;
font-size: 30px;
height: 30px;
width: 30px;
line-height: 1;
text-align: center;
position: absolute;
right: 0; bottom: 0;
z-index: 1;
}
#qr textarea {
min-height: 160px;
min-width: 100%;
display: block;
}
#qr.has-captcha textarea {
min-height: 120px;
}
.textarea {
position: relative;
}
#char-count {
color: #000;
background: hsla(0, 0%, 100%, .5);
font-size: 8pt;
position: absolute;
bottom: 1px;
right: 1px;
pointer-events: none;
}
#char-count.warning {
color: red;
}
.captcha-img {
background: #FFF;
outline: 1px solid #CCC;
outline-offset: -1px;
}
.captcha-img > img {
display: block;
height: 57px;
width: 300px;
}
#file-n-submit > input {
margin: 0;
}
#file-n-submit.has-file #qr-no-file {
visibility: hidden;
}
#file-n-submit:not(.has-file) #qr-filename,
#file-n-submit:not(.has-file) #qr-file-spoiler,
#file-n-submit:not(.has-file) #qr-filerm {
display: none;
}
#file-n-submit {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: row;
flex-direction: row;
-webkit-align-items: center;
align-items: center;
}
#qr-no-file, #qr-filename-container {
-webkit-flex: 1;
flex: 1;
}
#qr-filename-container {
cursor: default;
position: relative;
margin-left: 2px;
}
#qr-filename {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
#qr-filerm {
padding: 0 2px;
}
#file-n-submit > #qr-file-spoiler {
margin: 0 2px;
}
#file-n-submit input[type='submit'] {
min-width: 40px;
-webkit-order: 1;
order: 1;
}
#qr input[type='file'] {
position: absolute;
visibility: hidden;
}
/* Menu */
.menu-button {
display: inline-block;
position: relative;
}
.menu-button i {
border-top: 6px solid;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
display: inline-block;
margin: 2px;
vertical-align: middle;
}
#menu {
border-bottom: 0;
display: -webkit-flex;
display: flex;
margin: 2px 0;
-webkit-flex-direction: column;
flex-direction: column;
position: absolute;
outline: none;
}
.entry {
cursor: pointer;
outline: none;
padding: 3px 7px;
position: relative;
text-decoration: none;
white-space: nowrap;
}
.entry.has-submenu {
padding-right: 20px;
}
.has-submenu::after {
content: '';
border-left: 6px solid;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
display: inline-block;
margin: 4px;
position: absolute;
right: 3px;
}
.has-submenu:not(.focused) > .submenu {
display: none;
}
.submenu {
border-bottom: 0;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
position: absolute;
margin: -1px 0;
}
.entry input {
margin: 0;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 B

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 B

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 B

After

Width:  |  Height:  |  Size: 349 B

BIN
img/icon128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

BIN
img/icon16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

BIN
img/icon48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

View File

@ -51,7 +51,7 @@ $.extend String::,
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)))
$.extend $,
engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase()
engine: '<% if (type === 'crx') { %>webkit<% } else if (type === 'userjs') { %>presto<% } else { %>gecko<% } %>'
id: (id) ->
d.getElementById id
ready: (fc) ->
@ -227,19 +227,8 @@ $.extend $,
globalEval: (code) ->
script = $.el 'script',
textContent: code
$.add d.head, script
$.add (d.head or doc), script
$.rm script
# http://mths.be/unsafewindow
unsafeWindow:
if window.opera # Opera
window
else if unsafeWindow? # Firefox
unsafeWindow
else # Chrome
do ->
p = d.createElement 'p'
p.setAttribute 'onclick', 'return window'
p.onclick()
bytesToString: (size) ->
unit = 0 # Bytes
while size >= 1024
@ -256,50 +245,65 @@ $.extend $,
Math.round size
"#{size} #{['B', 'KB', 'MB', 'GB'][unit]}"
if GM_deleteValue?
$.delete = (name) ->
GM_deleteValue g.NAMESPACE + name
$.get = (name, defaultValue) ->
if value = GM_getValue g.NAMESPACE + name
JSON.parse value
<% if (type === 'crx') { %>
delete: (keys) ->
chrome.storage.sync.remove keys
get: (key, defaultVal) ->
if val = localStorage.getItem g.NAMESPACE + key
JSON.parse val
else
defaultValue
$.set = (name, value) ->
name = g.NAMESPACE + name
value = JSON.stringify value
defaultVal
set: (key, val) ->
item = {}
item[key] = val
chrome.storage.sync.set item
<% } else if (type === 'userjs') { %>
do ->
# http://www.opera.com/docs/userjs/specs/#scriptstorage
# http://www.opera.com/docs/userjs/using/#securepages
# The scriptStorage object is available only during
# the main User JavaScript thread, being therefore
# accessible only in the main body of the user script.
# To access the storage object later, keep a reference
# to the object.
{scriptStorage} = opera
$.delete = (keys) ->
unless keys instanceof Array
keys = [keys]
for key in keys
key = g.NAMESPACE + key
localStorage.removeItem key
delete scriptStorage[key]
return
$.get = (key, defaultVal) ->
if val = scriptStorage[g.NAMESPACE + key]
JSON.parse val
else
defaultVal
$.set = (key, val) ->
key = g.NAMESPACE + key
val = JSON.stringify val
# for `storage` events
localStorage.setItem name, value
GM_setValue name, value
else if window.opera
do ->
# http://www.opera.com/docs/userjs/specs/#scriptstorage
# http://www.opera.com/docs/userjs/using/#securepages
# >The scriptStorage object is available only during
# the main User JavaScript thread, being therefore
# accessible only in the main body of the user script.
# To access the storage object later, keep a reference
# to the object.
{scriptStorage} = opera
$.delete = (name) ->
delete scriptStorage[g.NAMESPACE + name]
$.get = (name, defaultValue) ->
if value = scriptStorage[g.NAMESPACE + name]
JSON.parse value
else
defaultValue
$.set = (name, value) ->
name = g.NAMESPACE + name
value = JSON.stringify value
# for `storage` events
localStorage.setItem name, value
scriptStorage[name] = value
else
$.delete = (name) ->
localStorage.removeItem g.NAMESPACE + name
$.get = (name, defaultValue) ->
if value = localStorage.getItem g.NAMESPACE + name
JSON.parse value
localStorage.setItem key, val
scriptStorage[key] = val
<% } else { %>
delete: (key) ->
unless keys instanceof Array
keys = [keys]
for key in keys
key = g.NAMESPACE + key
localStorage.removeItem key
GM_deleteValue key
return
get: (key, defaultVal) ->
if val = GM_getValue g.NAMESPACE + key
JSON.parse val
else
defaultValue
$.set = (name, value) ->
localStorage.setItem g.NAMESPACE + name, JSON.stringify value
defaultVal
set: (key, val) ->
key = g.NAMESPACE + key
val = JSON.stringify val
# for `storage` events
localStorage.setItem key, val
GM_setValue key, val
<% } %>

View File

@ -7,6 +7,7 @@
"namespace": "zixaphir",
"repo": "https://github.com/zixaphir/appchan-x/",
"page": "http://zixaphir.github.com/appchan-x/",
"buildsPath": "builds/",
"mainBranch": "Av2",
"matches": [
"*://api.4chan.org/*",
@ -16,12 +17,14 @@
]
},
"devDependencies": {
"grunt": "~0.4.0",
"grunt": "~0.4.1",
"grunt-bump": "~0.0.0",
"grunt-contrib-clean": "~0.4.0",
"grunt-contrib-coffee": "~0.6.2",
"grunt-contrib-concat": "~0.1.0",
"grunt-contrib-watch": "~0.3.0",
"grunt-contrib-coffee": "~0.6.4",
"grunt-contrib-compress": "~0.4.5",
"grunt-contrib-concat": "~0.1.3",
"grunt-contrib-copy": "~0.4.0",
"grunt-contrib-watch": "~0.3.1",
"grunt-exec": "~0.4.0"
},
"repository": {

View File

@ -15,7 +15,7 @@ Config =
]
'Custom Board Navigation': [
true
'Disable this to always display the full board list.'
'Show custom links instead of the full board list.'
]
'404 Redirect': [
true
@ -27,7 +27,7 @@ Config =
]
'Time Formatting': [
true
'Localize and format timestamps arbitrarily.'
'Localize and format timestamps.'
]
'Relative Post Dates': [
false
@ -39,19 +39,15 @@ Config =
]
'Comment Expansion': [
true
'Can expand too long comments.'
'Add buttons to expand long comments.'
]
'Thread Expansion': [
true
'Can expand threads to view all replies.'
'Add buttons to expand threads.'
]
'Index Navigation': [
false
'Navigate to previous / next thread.'
]
'Custom CSS': [
false
'Apply custom CSS to 4chan.'
'Add buttons to navigate between threads.'
]
'Check for Updates': [
true
@ -75,7 +71,7 @@ Config =
'Filtering':
'Anonymize': [
false
'Turn everyone Anonymous.'
'Make everyone Anonymous.'
]
'Filter': [
true
@ -87,19 +83,19 @@ Config =
]
'Thread Hiding': [
true
'Hide entire threads.'
'Add buttons to hide entire threads.'
]
'Reply Hiding': [
true
'Hide single replies.'
'Add buttons to hide single replies.'
]
'Hiding Buttons': [
true
'Make buttons to hide threads / replies, in addition to menu links.'
'Add buttons to hide threads / replies, in addition to menu links.'
]
'Stubs': [
true
'Make stubs of hidden threads / replies.'
'Show stubs of hidden threads / replies.'
]
'Images':
@ -135,8 +131,16 @@ Config =
'Menu':
'Menu': [
true
'Add a drop-down menu in posts.'
'Add a drop-down menu to posts.'
]
'Thread Hiding Link': [
true
'Add a link to hide entire threads.'
]
'Reply Hiding Link': [
true
'Add a link to hide single replies.'
]
'Report Link': [
true
'Add a report link to the menu.'
@ -222,7 +226,7 @@ Config =
'Hide the normal post form.'
]
'Quote links':
'Quote Links':
'Quote Backlinks': [
true
'Add quote backlinks.'
@ -681,21 +685,23 @@ Config =
MD5: ''
sauces: """
http://iqdb.org/?url=%turl
http://www.google.com/searchbyimage?image_url=%turl
#http://tineye.com/search?url=%turl
#http://saucenao.com/search.php?db=999&url=%turl
#http://3d.iqdb.org/?url=%turl
#http://regex.info/exif.cgi?imgurl=%url
http://iqdb.org/?url=%TURL
https://www.google.com/searchbyimage?image_url=%TURL
#//tineye.com/search?url=%TURL
#http://saucenao.com/search.php?url=%TURL
#http://3d.iqdb.org/?url=%TURL
#http://regex.info/exif.cgi?imgurl=%URL
# uploaders:
#http://imgur.com/upload?url=%url;text:Upload to imgur
#http://omploader.org/upload?url1=%url;text:Upload to omploader
#http://imgur.com/upload?url=%URL;text:Upload to imgur
#http://ompldr.org/upload?url1=%URL;text:Upload to ompldr
# "View Same" in archives:
#//archive.foolz.us/_/search/image/%MD5/;text:View same on foolz
#//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/
#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/
"""
'Custom CSS': false
'Header auto-hide': false
'Header catalog links': false

View File

@ -31,7 +31,7 @@ Header =
className: 'hide-board-list-button'
innerHTML: '[<a href=javascript:;> - </a>]\u00A0'
$.on btn, 'click', Header.toggleBoardList
$.prepend fullBoardList, btn
$.add fullBoardList, btn
else
$.rm $ '#custom-board-list', nav
fullBoardList.hidden = false
@ -40,8 +40,8 @@ Header =
list = $ '#custom-board-list', Header.nav
list.innerHTML = null
return unless text
as = $$('#full-board-list a', Header.nav)
nodes = text.match(/[\w@]+(-(all|title|full|text:"[^"]+"))?|[^\w@]+/g).map (t) ->
as = $$('#full-board-list a', Header.nav)[0...-2] # ignore the Settings and Home links
nodes = text.match(/[\w@]+(-(all|title|full|index|catalog|text:"[^"]+"))*|[^\w@]+/g).map (t) ->
if /^[^\w@]/.test t
return $.tn t
if t is 'toggle-all'
@ -58,12 +58,17 @@ Header =
for a in as
if a.textContent is board
a = a.cloneNode true
if /-title$/.test t
if /-title/.test t
a.textContent = a.title
else if /-full$/.test t
else if /-full/.test t
a.textContent = "/#{board}/ - #{a.title}"
else if m = t.match /-text:"(.+)"$/
a.textContent = m[1]
else if /-(index|catalog|text)/.test t
if m = t.match /-(index|catalog)/
a.setAttribute 'data-only', m[1]
a.href = "//boards.4chan.org/#{board}/"
a.href += 'catalog' if m[1] is 'catalog'
if m = t.match /-text:"(.+)"/
a.textContent = m[1]
else if board is '@'
$.addClass a, 'navSmall'
return a
@ -186,9 +191,10 @@ Settings =
$.asap (-> $.id 'boardNavMobile'), ->
$.prepend $.id('navtopright'), [$.tn(' ['), link, $.tn('] ')]
unless $.get 'previousversion'
if (prevVersion = $.get 'previousversion', null) isnt g.VERSION
$.set 'lastupdate', Date.now()
$.set 'previousversion', g.VERSION
$.on d, '4chanXInitFinished', Settings.open
$.on d, '4chanXInitFinished', Settings.open unless prevVersion
Settings.addSection 'Main', Settings.main
Settings.addSection 'Filter', Settings.filter
@ -231,17 +237,15 @@ Settings =
links = []
for section in Settings.sections
link = $.el 'a',
className: "tab-#{section.hyphenatedTitle}"
textContent: section.title
href: 'javascript:;'
$.on link, 'click', Settings.openSection.bind section
links.push link, $.tn ' | '
sectionToOpen = link if section.title is openSection
links.pop()
if sectionToOpen
sectionToOpen.click()
else
links[0].click()
$.add $('.sections-list', overlay), links
(if sectionToOpen then sectionToOpen else links[0]).click()
$.on $('.close', overlay), 'click', Settings.close
$.on overlay, 'click', Settings.close
@ -261,11 +265,15 @@ Settings =
addSection: (title, open) ->
if typeof title isnt 'string'
{title, open} = title.detail
Settings.sections.push {title, open}
hyphenatedTitle = title.toLowerCase().replace /\s+/g, '-'
Settings.sections.push {title, hyphenatedTitle, open}
openSection: ->
if selected = $ '.tab-selected', Settings.dialog
$.rmClass selected, 'tab-selected'
$.addClass $(".tab-#{@hyphenatedTitle}", Settings.dialog), 'tab-selected'
section = $ 'section', Settings.dialog
section.innerHTML = null
section.className = "section-#{@title.toLowerCase().replace /\s+/g, '-'}"
section.className = "section-#{@hyphenatedTitle}"
@open section, g
section.scrollTop = 0
@ -290,7 +298,7 @@ Settings =
description = arr[1]
div = $.el 'div',
innerHTML: "<label><input type=checkbox name=\"#{key}\" #{checked}>#{key}</label><span class=description>: #{description}</span>"
$.on $('input', div), 'click', $.cb.checked
$.on $('input', div), 'change', $.cb.checked
$.add fs, div
$.add section, fs
@ -304,8 +312,7 @@ Settings =
innerHTML: "<button>Hidden: #{hiddenNum}</button><span class=description>: Clear manually hidden threads and posts on /#{g.BOARD}/."
$.on $('button', div), 'click', ->
@textContent = 'Hidden: 0'
$.delete "hiddenThreads.#{g.BOARD}"
$.delete "hiddenPosts.#{g.BOARD}"
$.delete ["hiddenThreads.#{g.BOARD}", "hiddenPosts.#{g.BOARD}"]
$.after $('input[name="Stubs"]', section).parentNode.parentNode, div
export: ->
now = Date.now()
@ -318,7 +325,7 @@ Settings =
className: 'warning'
textContent: 'Save me!'
download: "<%= meta.name %> v#{g.VERSION}-#{now}.json"
href: "data:application/json;base64,#{btoa unescape encodeURIComponent JSON.stringify data}"
href: "data:application/json;base64,#{btoa unescape encodeURIComponent JSON.stringify data, null, 2}"
target: '_blank'
if $.engine isnt 'gecko'
a.click()
@ -458,7 +465,7 @@ Settings =
$.add div, ta
return
div.innerHTML = """
<div class=warning #{if Conf['Sauce'] then 'hidden' else ''}><code>Filter</code> is disabled.</div>
<div class=warning #{if Conf['Filter'] then 'hidden' else ''}><code>Filter</code> is disabled.</div>
<p>
Use <a href=https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions>regular expressions</a>, one per line.<br>
Lines starting with a <code>#</code> will be ignored.<br>
@ -516,6 +523,9 @@ Settings =
<div>Title link: <code>board-title</code></div>
<div>Full text link: <code>board-full</code></div>
<div>Custom text link: <code>board-text:"VIP Board"</code></div>
<div>Index-only link: <code>board-index</code></div>
<div>Catalog-only link: <code>board-catalog</code></div>
<div>Combinations are possible: <code>board-index-text:"VIP Index"</code></div>
<div>Full board list toggle: <code>toggle-all</code></div>
</fieldset>
@ -558,9 +568,9 @@ Settings =
</fieldset>
<fieldset>
<legend>Custom CSS <span class=warning #{if Conf['Custom CSS'] then 'hidden' else ''}>is disabled.</span></legend>
<legend><input type=checkbox name='Custom CSS' #{if Conf['Custom CSS'] then 'checked' else ''}> Custom CSS</legend>
<button id=apply-css>Apply CSS</button>
<textarea name=usercss class=field spellcheck=false></textarea>
<textarea name=usercss class=field spellcheck=false #{if Conf['Custom CSS'] then '' else 'disabled'}></textarea>
</fieldset>
"""
for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss']
@ -574,6 +584,7 @@ Settings =
unless 'usercss' is name
$.on input, event, Settings[name]
Settings[name].call input
$.on $('input[name="Custom CSS"]', section), 'change', Settings.togglecss
$.on $.id('apply-css'), 'click', Settings.usercss
boardnav: ->
Header.generateBoardList @value
@ -598,12 +609,20 @@ Settings =
favicon: ->
Favicon.switch()
Unread.update() if g.VIEW is 'thread' and Conf['Unread Tab Icon']
@nextElementSibling.innerHTML = "<img src=#{Favicon.unreadSFW}> <img src=#{Favicon.unreadNSFW}> <img src=#{Favicon.unreadDead}>"
usercss: ->
if Conf['Custom CSS']
CustomCSS.update()
else
@nextElementSibling.innerHTML = """
<img src=#{Favicon.default}>
<img src=#{Favicon.unreadSFW}>
<img src=#{Favicon.unreadNSFW}>
<img src=#{Favicon.unreadDead}>
"""
togglecss: ->
if $('textarea', @parentNode.parentNode).disabled = !@checked
CustomCSS.rmStyle()
else
CustomCSS.addStyle()
$.cb.checked.call @
usercss: ->
CustomCSS.update()
keybinds: (section) ->
section.innerHTML = """
@ -639,33 +658,40 @@ Fourchan =
board = g.BOARD.ID
if board is 'g'
$.globalEval """
window.addEventListener('prettyprint', function(e) {
var pre = e.detail;
pre.innerHTML = prettyPrintOne(pre.innerHTML);
}, false);
"""
Post::callbacks.push
name: 'Parse /g/ code'
cb: @code
if board is 'sci'
# https://github.com/MayhemYDG/4chan-x/issues/645#issuecomment-13704562
$.globalEval """
window.addEventListener('jsmath', function(e) {
if (jsMath.loaded) {
// process one post
jsMath.ProcessBeforeShowing(e.detail);
} else {
// load jsMath and process whole document
jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);
jsMath.Autoload.LoadJsMath();
}
}, false);
"""
Post::callbacks.push
name: 'Parse /sci/ math'
cb: @math
code: ->
return if @isClone
for pre in $$ '.prettyprint', @nodes.comment
pre.innerHTML = $.unsafeWindow.prettyPrintOne pre.innerHTML
$.event 'prettyprint', pre, window
return
math: ->
return if @isClone or !$ '.math', @nodes.comment
# https://github.com/MayhemYDG/4chan-x/issues/645#issuecomment-13704562
{jsMath} = $.unsafeWindow
if jsMath
if jsMath.loaded
# process one post
jsMath.ProcessBeforeShowing @nodes.post
else
# load jsMath and process whole document
# Yes this requires to be globalEval'd, don't ask me why.
$.globalEval """
jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);
jsMath.Autoload.LoadJsMath();
"""
$.event 'jsmath', @nodes.post, window
parseThread: (threadID, offset, limit) ->
# Fix /sci/
# Fix /g/
@ -966,7 +992,7 @@ Filter =
ThreadHiding =
init: ->
return if g.VIEW isnt 'index' or !Conf['Thread Hiding']
return if g.VIEW isnt 'index' or !Conf['Thread Hiding'] and !Conf['Thread Hiding Link']
Misc.clearThreads "hiddenThreads.#{g.BOARD}"
@getHiddenThreads()
@ -978,7 +1004,7 @@ ThreadHiding =
node: ->
if data = ThreadHiding.hiddenThreads.threads[@]
ThreadHiding.hide @, data.makeStub
return unless Conf['Hiding Buttons']
return unless Conf['Thread Hiding']
$.prepend @OP.nodes.root, ThreadHiding.makeButton @, 'hide'
getHiddenThreads: ->
@ -1006,7 +1032,7 @@ ThreadHiding =
menu:
init: ->
return if g.VIEW isnt 'index' or !Conf['Menu'] or !Conf['Thread Hiding']
return if g.VIEW isnt 'index' or !Conf['Menu'] or !Conf['Thread Hiding Link']
div = $.el 'div',
className: 'hide-thread-link'
@ -1108,7 +1134,7 @@ ThreadHiding =
ReplyHiding =
init: ->
return if g.VIEW is 'catalog' or !Conf['Reply Hiding']
return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] and !Conf['Reply Hiding Link']
Misc.clearThreads "hiddenPosts.#{g.BOARD}"
@getHiddenPosts()
@ -1125,7 +1151,7 @@ ReplyHiding =
else
Recursive.apply ReplyHiding.hide, @, data.makeStub, true
Recursive.add ReplyHiding.hide, @, data.makeStub, true
return unless Conf['Hiding Buttons']
return unless Conf['Reply Hiding']
$.add $('.postInfo', @nodes.post), ReplyHiding.makeButton @, 'hide'
getHiddenPosts: ->
@ -1133,7 +1159,7 @@ ReplyHiding =
menu:
init: ->
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Reply Hiding']
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Reply Hiding Link']
# Hide
div = $.el 'div',
@ -1343,7 +1369,7 @@ Recursive =
QuoteStrikeThrough =
init: ->
return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] and !Conf['Filter']
return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] and !Conf['Reply Hiding Link'] and !Conf['Filter']
Post::callbacks.push
name: 'Strike-through Quotes'
@ -1631,7 +1657,7 @@ Keybinds =
when Conf['Open settings']
Settings.open()
when Conf['Close']
if $.id 'settings'
if $.id 'fourchanx-settings'
Settings.close()
else if (notifications = $$ '.notification').length
for notification in notifications
@ -1798,7 +1824,6 @@ Keybinds =
focus: (post) ->
$.addClass post, 'highlight'
$('a[title="Highlight this post"]', post).focus()
Nav =
init: ->
@ -2426,10 +2451,10 @@ Misc = # super semantic
return if data.lastChecked > Date.now() - 12 * $.HOUR
$.ajax "//api.4chan.org/#{g.BOARD}/catalog.json", onload: ->
$.ajax "//api.4chan.org/#{g.BOARD}/threads.json", onload: ->
threads = {}
for obj in JSON.parse @response
for thread in obj.threads
for page in JSON.parse @response
for thread in page.threads
if thread.no of data.threads
threads[thread.no] = data.threads[thread.no]
unless Object.keys(threads).length
@ -2637,7 +2662,7 @@ QuotePreview =
# Remove the clone that's in the qp from the array.
posts.pop()
for post in posts
$.addClass post.nodes.post, 'qphl'
$.addClass post.nodes.root, 'qphl'
quoterID = $.x('ancestor::*[@id][1]', @).id.match(/\d+$/)[0]
clone = Get.postFromRoot qp.firstChild
@ -2658,7 +2683,7 @@ QuotePreview =
return unless Conf['Quote Highlighting']
for post in [post].concat post.clones
$.rmClass post.nodes.post, 'qphl'
$.rmClass post.nodes.root, 'qphl'
return
QuoteBacklink =
@ -3062,14 +3087,15 @@ Sauce =
createSauceLink: (link) ->
link = link.replace /%(T?URL|MD5|board)/ig, (parameter) ->
switch parameter
when '%TURL', '%turl'
"' + post.file.thumbURL + '"
when '%URL', '%url'
"' + post.file.URL + '"
when '%MD5', '%md5'
when '%TURL'
"' + encodeURIComponent(post.file.thumbURL) + '"
when '%URL'
"' + encodeURIComponent(post.file.URL) + '"
when '%MD5'
"' + encodeURIComponent(post.file.MD5) + '"
when '%board'
"' + post.board + '"
"' + encodeURIComponent(post.board) + '"
else
parameter
text = if m = link.match(/;text:(.+)$/) then m[1] else link.match(/(\w+)\.\w+\//)[1]
@ -3178,7 +3204,7 @@ ImageExpand =
# Scroll back to the thumbnail when contracting the image
# to avoid being left miles away from the relevant post.
postRect = post.nodes.root.getBoundingClientRect()
headRect = Header.bar.getBoundingClientRect()
headRect = Header.toggle.getBoundingClientRect()
top = postRect.top - headRect.top - headRect.height - 2
root = if $.engine is 'webkit'
d.body
@ -3229,7 +3255,10 @@ ImageExpand =
post = Get.postFromNode @
$.rm @
delete post.file.fullImage
unless $.hasClass post.file.thumb, 'expanding'
# Images can error:
# - before the image started loading.
# - after the image started loading.
unless $.hasClass(post.file.thumb, 'expanding') or $.hasClass post.nodes.root, 'expanded-image'
# Don't try to re-expend if it was already contracted.
return
ImageExpand.contract post
@ -3452,7 +3481,7 @@ ExpandThread =
#goddamit moot
num = if thread.isSticky
1
else switch g.BOARD
else switch g.BOARD.ID
# XXX boards config
when 'b', 'vg', 'q' then 3
when 't' then 1
@ -3500,7 +3529,7 @@ ExpandThread =
# Enable 4chan features.
if Conf['Enable 4chan\'s Extension']
$.unsafeWindow.Parser.parseThread thread.ID, 1, nodes.length
$.globalEval "Parser.parseThread(#{thread.ID}, 1, #{nodes.length})"
else
Fourchan.parseThread thread.ID, 1, nodes.length
@ -3512,7 +3541,10 @@ ThreadExcerpt =
name: 'Thread Excerpt'
cb: @node
node: ->
d.title = Get.threadExcerpt @
d.title = if (excerpt = Get.threadExcerpt @).length > 80
"#{excerpt[...77]}..."
else
excerpt
Unread =
init: ->
@ -3634,10 +3666,12 @@ Unread =
else
Favicon.default
<% if (type !== 'crx') { %>
# `favicon.href = href` doesn't work on Firefox.
# `favicon.href = href` isn't enough on Opera.
# Opera won't always update the favicon if the href didn't change.
$.add d.head, Favicon.el
<% } %>
Favicon =
init: ->
@ -4004,9 +4038,9 @@ ThreadUpdater =
$.queueTask ->
# Enable 4chan features.
threadID = ThreadUpdater.thread.ID
{length} = ThreadUpdater.root.children
{length} = $$ '.thread > .postContainer', ThreadUpdater.root
if Conf['Enable 4chan\'s Extension']
$.unsafeWindow.Parser.parseThread threadID, -count
$.globalEval "Parser.parseThread(#{threadID}, #{-count})"
else
Fourchan.parseThread threadID, length - count, length
@ -4045,6 +4079,7 @@ ThreadWatcher =
$.delete 'AutoWatch'
ready: ->
return unless Main.isThisPageLegit()
ThreadWatcher.refresh()
$.add d.body, ThreadWatcher.dialog

View File

@ -1,6 +1,8 @@
<% if (type === 'userjs') { %>
# Opera doesn't support the @match metadata key,
# return 4chan X here if we're not on 4chan.
return unless /^[a-z]+\.4chan\.org$/.test location.hostname
<% } %>
editTheme = {} # Currently editted theme.
editMascot = {} # Which mascot we're editting.
@ -8,8 +10,8 @@ userNavigation = {} # ...
Conf = {}
c = console
d = document
doc = null
g =
doc = d.documentElement
g =
VERSION: '<%= version %>'
NAMESPACE: '<%= meta.name %>.'.replace ' ', '_'
TYPE: 'sfw'
@ -2759,4 +2761,4 @@ Icons =
png: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA'
themes:
oneechan: 'A8AAACWBAMAAADzkc/yAAAAMFBMVEVoaGhsbGxsbGxsbGxsbGxsbGxsbGxsbGxqampsbGxsbGxsbGxlZWVsbGxsbGxsbGzdpWE1AAAAD3RSTlMAfEDYHcNYkhP0pz8MJYTrg0d+AAACwElEQVR4Xp3RT2gTaRjH8W9r/rVJNTl42UvHvYinVGLR7SoZ6+JBD0kpsafVgJale4m67r/DkgEp7EEMsqy2KEwEUdjt0kBZqovY3oQedEBE8GA9iV42TQaTVm0f33lfCAgLZfuDgc/85n2f5zDAonnYIQ5LIwCykrj5hyqc5dWlz35QaMr7c3eKEBGReVVASaRZBOCxiKfRJ7KuMV4x1eGvE7pKjHo8bvwDXXsh0Xqu4AGP/mKT7DRgwTc4IgaXRSSTeQ2i0zbNv2UBjonP9gDMGQC7DQAiGbacbwpjRYB7rsh0DSLN+pnSqxbk7reIfEjb7Iyvwvtw0jSrC7Y6Uy6WzreAPVl1y+rM2STjmaGa3uX/+PNbB+JrHqSnoDJAfy3mQ92hbJG2GFS26KnSIHjtHaClUCM6wBA8RX0KDtf14coUp73YdDDQgvRb4ImMnRJxgPnRsXFZwaTbAl4UTnoASzKYbTsQy14gVD4AvS0g0oblsxCvztrUbUKztVySUSi12ZanQEg+EM6rpleK9OTptxd+AnUmV/0TULeies5GMPmWun8AeCS/uxsOwOHCiM0nOV4zWBp5Y2ncJnYIIJSHeYB4EsIaL2ESgH3wSmPOutzQCH1/ov8gJmF/0SCWXTfgYtuAvl/4jyyLzzYRj5xcJ+6KrfBucbsEcKVaF9dm8oH48u5vB6iLzJhN4qMzIWtF4OFR95p79dkilS+kOLGWdajIVSKuBDhEKKuQkzZRUUj8ekk/MPy5io2qg7Qhd8GGr8o2aRsgZ5EeloZB9zqwQ6PrxkkN8QanTEMpaRD7TTfDsnFwv7rVmdOZ3NnV2a6KZL/Iyv9DKpWydqVSSTrpNFsbCN9mpk8ATIrKWWA2QAsiEqQBYY0Z6FG4cheIytBEC4AE4TUP0GXeoEvaBnG1xSgrTYOS+Hz6M2PffclH1wyYp51t8R8AAAAASUVORK5CYII='
"4chan SS": 'A8AAACWBAMAAADzkc/yAAAAMFBMVEVmZmZnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2djY2NkZGRnZ2dnZ2dnZ2eHsc5jAAAAD3RSTlMAgEDLkve+G+dSqwcRdCZzKH/3AAACz0lEQVR4Xp2PQWhTdxzHP0vTliQvMaE4NjdoutMOA6NrpeghQaazp4R1XkL15VDR4UbeKiJ4UXCuzG7kgd2lONLbGNmWgKwqGyMQN0QRqruIuKYXi9iDtkmeaaD97Z9/2nfy0n0u+fDl//3+XnA5E+XIoA30bPDnVceC0EfgkynAl6AyulICHhjDi/kYEHx7Ab8NhPpW0bxfbRQAfEcfSasEVGv3RTIQlMlAVlagv7lIRcSi+An05hy4awN3f+a1TKNhqbFvfAG4LW1+BVNLI0pRNDE8cS0p8F364bEpF4FbkJdZAIxs0wIIvSU/AuAdPci2GRj6ooz+jpp8A4RyzvKSFOCZrB46KLUyWUmZKVMybuK+cVvAwAm1s12MXUP73wXYKSKNMHhF0iJ1qIpMqWyebEcymCJjSi4SFzmuk6p+LPP8JXKs0zI3dx7eePOyWn4900ODNsD5xmC8ZUHv5Se/B81J8Lf6xkdOpSE/kmvtWq7aJK0Xze/KNxN8iNeZpyfGpwT+vYc3xT4I/GP7YxwqQTL9W4J8GO6sqJb/OHSPpNvL7/C8OKlvjZ9Qt9zrEDx1Rf+G4iKtMtAlIzOSAKprUF0FsgmV1oGcDW84W9ICirNwfQ3IrxNIvgL8sqdPVgAjKYow4Dn805c6AujXEaq/JURg/BpgTGdINhbhqSTI10pQEZs78dMY8eEyvXNOqeLcAALZkyoEmGs4JS295hiaik5Qlc+zpwFPxSnNqRZncwVVVDt6+brYFJubt36Jbl5n4j2FBV5pU4cLH1twLmmx2wK4EGX3hKx3xLMB7NASHBvUIstff9tJyM93xLg0tkO36lNfqZa74y67t9zrKgj3i7zcnkQikehAJBLGxU3+3yDMDB/9DOBvUSSAYlvWwSeb0qWlAN1Kjn0AeOXK9+sA/EFXYxHQ4QE0QamjCakraOKyiqYqNTQ9W2Ic2ct/3c/SAwz13v8AAAAASUVORK5CYII='
"4chan SS": 'A8AAACWBAMAAADzkc/yAAAAMFBMVEVmZmZnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2djY2NkZGRnZ2dnZ2dnZ2eHsc5jAAAAD3RSTlMAgEDLkve+G+dSqwcRdCZzKH/3AAACz0lEQVR4Xp2PQWhTdxzHP0vTliQvMaE4NjdoutMOA6NrpeghQaazp4R1XkL15VDR4UbeKiJ4UXCuzG7kgd2lONLbGNmWgKwqGyMQN0QRqruIuKYXi9iDtkmeaaD97Z9/2nfy0n0u+fDl//3+XnA5E+XIoA30bPDnVceC0EfgkynAl6AyulICHhjDi/kYEHx7Ab8NhPpW0bxfbRQAfEcfSasEVGv3RTIQlMlAVlagv7lIRcSi+An05hy4awN3f+a1TKNhqbFvfAG4LW1+BVNLI0pRNDE8cS0p8F364bEpF4FbkJdZAIxs0wIIvSU/AuAdPci2GRj6ooz+jpp8A4RyzvKSFOCZrB46KLUyWUmZKVMybuK+cVvAwAm1s12MXUP73wXYKSKNMHhF0iJ1qIpMqWyebEcymCJjSi4SFzmuk6p+LPP8JXKs0zI3dx7eePOyWn4900ODNsD5xmC8ZUHv5Se/B81J8Lf6xkdOpSE/kmvtWq7aJK0Xze/KNxN8iNeZpyfGpwT+vYc3xT4I/GP7YxwqQTL9W4J8GO6sqJb/OHSPpNvL7/C8OKlvjZ9Qt9zrEDx1Rf+G4iKtMtAlIzOSAKprUF0FsgmV1oGcDW84W9ICirNwfQ3IrxNIvgL8sqdPVgAjKYow4Dn805c6AujXEaq/JURg/BpgTGdINhbhqSTI10pQEZs78dMY8eEyvXNOqeLcAALZkyoEmGs4JS295hiaik5Qlc+zpwFPxSnNqRZncwVVVDt6+brYFJubt36Jbl5n4j2FBV5pU4cLH1twLmmx2wK4EGX3hKx3xLMB7NASHBvUIstff9tJyM93xLg0tkO36lNfqZa74y67t9zrKgj3i7zcnkQikehAJBLGxU3+3yDMDB/9DOBvUSSAYlvWwSeb0qWlAN1Kjn0AeOXK9+sA/EFXYxHQ4QE0QamjCakraOKyiqYqNTQ9W2Ic2ct/3c/SAwz13v8AAAAASUVORK5CYII='

View File

@ -41,28 +41,28 @@ class Post
backlinks: info.getElementsByClassName 'backlink'
@info = {}
if subject = $ '.subject', info
if subject = $ '.subject', info
@nodes.subject = subject
@info.subject = subject.textContent
if name = $ '.name', info
if name = $ '.name', info
@nodes.name = name
@info.name = name.textContent
if email = $ '.useremail', info
if email = $ '.useremail', info
@nodes.email = email
@info.email = decodeURIComponent email.href[7..]
if tripcode = $ '.postertrip', info
if tripcode = $ '.postertrip', info
@nodes.tripcode = tripcode
@info.tripcode = tripcode.textContent
if uniqueID = $ '.posteruid', info
if uniqueID = $ '.posteruid', info
@nodes.uniqueID = uniqueID
@info.uniqueID = uniqueID.firstElementChild.textContent
if capcode = $ '.capcode', info
if capcode = $ '.capcode.hand', info
@nodes.capcode = capcode
@info.capcode = capcode.textContent
if flag = $ '.countryFlag', info
@info.capcode = capcode.textContent.replace '## ', ''
if flag = $ '.countryFlag', info
@nodes.flag = flag
@info.flag = flag.title
if date = $ '.dateTime', info
if date = $ '.dateTime', info
@nodes.date = date
@info.date = new Date date.dataset.utc * 1000
if Conf['Quick Reply']
@ -284,9 +284,6 @@ class Clone extends Post
Main =
init: ->
$.asap (-> d.documentElement), ->
doc = d.documentElement
# flatten Config into Conf
# and get saved or default values
flatten = (parent, obj) ->
@ -344,7 +341,7 @@ Main =
for name, module of features
# c.time "#{name} initialization"
try
module.init()
do module.init
catch err
Main.handleErrors
message: "\"#{name}\" initialization crashed."
@ -447,6 +444,7 @@ Main =
Main.callbackNodes Post, posts
$.event '4chanXInitFinished'
Main.checkUpdate()
callbackNodes: (klass, nodes) ->
# get the nodes' length only once
@ -467,14 +465,42 @@ Main =
Main.handleErrors errors if errors
addCallback: (e) ->
obj = e.detail
Klass = if obj.type is 'Post'
Post
else
Thread
obj = e.detail
unless typeof obj.callback.name is 'string'
throw new Error "Invalid callback name: #{obj.callback.name}"
switch obj.type
when 'Post'
Klass = Post
when 'Thread'
Klass = Thread
else
return
obj.callback.isAddon = true
Klass::callbacks.push obj.callback
checkUpdate: ->
return unless Main.isThisPageLegit()
# Check for updates after:
# - 6 hours since the last update on Opera because it lacks auto-updating.
# - 7 days since the last update on Chrome/Firefox.
# After that, check for updates every day if we still haven't updated.
now = Date.now()
freq = <% if (type === 'userjs') { %>6 * $.HOUR<% } else { %>7 * $.DAY<% } %>
if $.get('lastupdate', 0) > now - freq or $.get('lastchecked', 0) > now - $.DAY
return
$.ajax '<%= meta.page %><%= meta.buildsPath %>version', onload: ->
return unless @status is 200
version = @response
return unless /^\d\.\d+\.\d+$/.test version
if g.VERSION is version
# Don't check for updates too frequently if there wasn't one in a 'long' time.
$.set 'lastupdate', now
return
$.set 'lastchecked', now
el = $.el 'span',
innerHTML: "Update: <%= meta.name %> v#{version} is out, get it <a href=<%= meta.page %> target=_blank>here</a>."
new Notification 'info', el, 2 * $.MINUTE
handleErrors: (errors) ->
unless 'length' of errors
error = errors
@ -485,7 +511,7 @@ Main =
return
div = $.el 'div',
innerHTML: "#{errors.length} errors occured. [<a href=javascript:;>show</a>]"
innerHTML: "#{errors.length} errors occurred. [<a href=javascript:;>show</a>]"
$.on div.lastElementChild, 'click', ->
if @textContent is 'show'
@textContent = 'hide'
@ -514,7 +540,9 @@ Main =
isThisPageLegit: ->
# 404 error page or similar.
unless 'thisPageIsLegit' of Main
Main.thisPageIsLegit = !$('link[href*="favicon-status.ico"]', d.head) and d.title isnt '4chan - Temporarily Offline'
Main.thisPageIsLegit = location.hostname is 'boards.4chan.org' and
!$('link[href*="favicon-status.ico"]', d.head) and
d.title not in ['4chan - Temporarily Offline', '4chan - Error']
Main.thisPageIsLegit

View File

@ -3,12 +3,19 @@
"version": "<%= version %>",
"manifest_version": 2,
"description": "<%= description %>",
"icons": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
},
"content_scripts": [{
"js": ["script.js"],
"matches": <%= JSON.stringify(meta.matches) %>,
"run_at": "document_start"
}],
"homepage_url": "<%= meta.page %>",
"incognito": "spanning",
"minimum_chrome_version": "25"
"minimum_chrome_version": "25",
"permissions": [
"storage"
]
}

View File

@ -16,7 +16,7 @@
// @grant GM_deleteValue
// @grant GM_openInTab
// @run-at document-start
// @updateURL <%= meta.repo %>raw/<%= meta.mainBranch %>/<%= name %>.meta.js
// @downloadURL <%= meta.repo %>raw/<%= meta.mainBranch %>/<%= name %>.user.js
// @icon data:image/gif;base64,<%= grunt.file.read('img/icon.gif', {encoding: 'base64'}) %>
// @updateURL <%= meta.page %><%= meta.buildsPath %><%= name %>.meta.js
// @downloadURL <%= meta.page %><%= meta.buildsPath %><%= name %>.user.js
// @icon data:image/png;base64,<%= grunt.file.read('img/icon48.png', {encoding: 'base64'}) %>
// ==/UserScript==

View File

@ -486,6 +486,7 @@ QR =
# so we generate thumbnails `s` times bigger then expected
# to avoid crappy resized quality.
s = 90*2
s *= 3 if @file.type is 'image/gif' # let them animate
{height, width} = img
if height < s or width < s
@URL = fileURL if window.URL
@ -581,6 +582,12 @@ QR =
return unless @isEnabled = !!$.id 'captchaFormPart'
$.asap (-> $.id 'recaptcha_challenge_field_holder'), @ready.bind @
ready: ->
setLifetime = (e) => @lifetime = e.detail
$.on window, 'captcha:timeout', setLifetime
$.globalEval 'window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))'
$.off window, 'captcha:timeout', setLifetime
c.log @lifetime
imgContainer = $.el 'div',
className: 'captcha-img'
title: 'Reload'
@ -648,7 +655,7 @@ QR =
load: ->
return unless @nodes.challenge.firstChild
# -1 minute to give upload some time.
@timeout = Date.now() + $.unsafeWindow.RecaptchaState.timeout * $.SECOND - $.MINUTE
@timeout = Date.now() + @lifetime * $.SECOND - $.MINUTE
challenge = @nodes.challenge.firstChild.value
@nodes.img.alt = challenge
@nodes.img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}"
@ -666,7 +673,7 @@ QR =
@nodes.input.alt = count # For XTRM RICE.
reload: (focus) ->
# the 't' argument prevents the input from being focused
$.unsafeWindow.Recaptcha.reload 't'
$.globalEval 'Recaptcha.reload("t")'
# Focus if we meant to.
@nodes.input.focus() if focus
keydown: (e) ->
@ -701,14 +708,14 @@ QR =
<a id=add-post href=javascript:; title="Add a post">+</a>
</div>
<div id=file-n-submit>
<input type=submit>
<input id=qr-file-button type=button value='Choose files'>
<span id=qr-filename-container>
<span id=qr-no-file>No selected file</span>
<span id=qr-filename></span>
</span>
<a id=qr-filerm href=javascript:; title='Remove file' tabindex=-1>×</a>
<input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=-1>
<input type=submit>
<a id=qr-filerm href=javascript:; title='Remove file'>×</a>
<input type=checkbox id=qr-file-spoiler title='Spoiler image'>
</div>
<input type=file multiple>
</form>
@ -877,12 +884,14 @@ QR =
callbacks =
onload: QR.response
onerror: ->
# Connection error, or
# www.4chan.org/banned
delete QR.req
post.unlock()
QR.cooldown.auto = false
QR.status()
# Connection error.
QR.error 'Network error.'
QR.error $.el 'span',
innerHTML: 'Connection error. You may have been <a href=//www.4chan.org/banned target=_blank>banned</a>.'
opts =
form: $.formData postData
upCallbacks:
@ -966,6 +975,7 @@ QR =
[_, threadID, postID] = h1.nextSibling.textContent.match /thread:(\d+),no:(\d+)/
postID = +postID
threadID = +threadID or postID
isReply = threadID isnt postID
(QR.yourPosts.threads[threadID] or= []).push postID
$.set "yourPosts.#{g.BOARD}", QR.yourPosts
@ -980,18 +990,15 @@ QR =
}, QR.nodes.el
# Enable auto-posting if we have stuff to post, disable it otherwise.
QR.cooldown.auto = QR.posts.length > 1
QR.cooldown.auto = QR.posts.length > 1 and isReply
post.rm()
QR.cooldown.set
req: req
post: post
isReply: !!threadID
QR.cooldown.set {req, post, isReply}
if threadID is postID # new thread
URL = "/#{g.BOARD}/res/#{threadID}"
else if g.VIEW is 'index' and !QR.cooldown.auto # posting from the index
else if g.VIEW is 'index' and !QR.cooldown.auto and Conf['Open Post in New Tab'] # replying from the index
URL = "/#{g.BOARD}/res/#{threadID}#p#{postID}"
if URL
if Conf['Open Post in New Tab']

View File

@ -6,7 +6,7 @@ Report =
form = $ 'form'
field = $.id 'recaptcha_response_field'
$.on field, 'keydown', (e) ->
$.unsafeWindow.Recaptcha.reload 't' if e.keyCode is 8 and not field.value
$.globalEval 'Recaptcha.reload("t")' if e.keyCode is 8 and not field.value
$.on form, 'submit', (e) ->
e.preventDefault()
response = field.value.trim()