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. 1. Make sure both your **browser** and **4chan X** are up to date.
2. Disable your other extensions & scripts to identify conflicts. 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: 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. 1. Precise steps to reproduce the problem.
2. Report console errors, if any. 2. Console errors, if any.
3. Report browser version. 3. Browser version.
4. Include your exported settings. 4. Your exported settings.
Open your console with: Open your console with:
- `Ctrl + Shift + J` on Chrome. - `Ctrl + Shift + J` on Chrome.

View File

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

View File

@ -1,8 +1,8 @@
# 4chan X # 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) ### [MIT License](/LICENSE)
### [Contribute](/4chan-x/blob/master/CONTRIBUTING.md) ### [Contribute](/CONTRIBUTING.md)

View File

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

View File

@ -15,12 +15,12 @@
// @grant GM_deleteValue // @grant GM_deleteValue
// @grant GM_openInTab // @grant GM_openInTab
// @run-at document-start // @run-at document-start
// @updateURL https://github.com/zixaphir/appchan-x/raw/Av2/appchan-x.meta.js // @updateURL http://zixaphir.github.com/appchan-x/builds/appchan-x.meta.js
// @downloadURL https://github.com/zixaphir/appchan-x/raw/Av2/appchan-x.user.js // @downloadURL http://zixaphir.github.com/appchan-x/builds/appchan-x.user.js
// @icon data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAIALAAAAAAQABAAAAIxlI+pq+D9DAgUoFkPDlbs7lGiI2bSVnKglnJMOL6omczxVZK3dH/41AG6Lh7i6qUoAAA7 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwAgMAAAAqbBEUAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAHFJREFUKFOt0LENACEIBdBv4Qju4wgWanEj3D6OcIVMKaitYHEU/jwTCQj8W75kiVCSBvdQ5/AvfVHBin11BgdRq3ysBgfwBDRrj3MCIA+oAQaku/Q1cNctrAmyDl577tOThYt/Y1RBM4DgOHzM0HFTAyLukH/cmRnqAAAAAElFTkSuQmCC
// ==/UserScript== // ==/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/ * http://zixaphir.github.com/appchan-x/
* *
* Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com> * 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.'], '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.'], '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.'], '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.'], '404 Redirect': [true, 'Redirect dead threads and images.'],
'Keybinds': [true, 'Bind actions to keyboard shortcuts.'], '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.'], 'Relative Post Dates': [false, 'Display dates like "3 minutes ago". Tooltip shows the timestamp.'],
'File Info Formatting': [true, 'Reformat the file information.'], 'File Info Formatting': [true, 'Reformat the file information.'],
'Comment Expansion': [true, 'Can expand too long comments.'], 'Comment Expansion': [true, 'Add buttons to expand long comments.'],
'Thread Expansion': [true, 'Can expand threads to view all replies.'], 'Thread Expansion': [true, 'Add buttons to expand threads.'],
'Index Navigation': [false, 'Navigate to previous / next thread.'], 'Index Navigation': [false, 'Add buttons to navigate between threads.'],
'Custom CSS': [false, 'Apply custom CSS to 4chan.'],
'Check for Updates': [true, 'Check for updated versions of appchan x.'] 'Check for Updates': [true, 'Check for updated versions of appchan x.']
}, },
'Linkification': { 'Linkification': {
@ -72,13 +71,13 @@
'Link Title': [true, 'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud'] 'Link Title': [true, 'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud']
}, },
'Filtering': { 'Filtering': {
'Anonymize': [false, 'Turn everyone Anonymous.'], 'Anonymize': [false, 'Make everyone Anonymous.'],
'Filter': [true, 'Self-moderation placebo.'], 'Filter': [true, 'Self-moderation placebo.'],
'Recursive Hiding': [true, 'Hide replies of hidden posts, recursively.'], 'Recursive Hiding': [true, 'Hide replies of hidden posts, recursively.'],
'Thread Hiding': [true, 'Hide entire threads.'], 'Thread Hiding': [true, 'Add buttons to hide entire threads.'],
'Reply Hiding': [true, 'Hide single replies.'], 'Reply Hiding': [true, 'Add buttons to hide single replies.'],
'Hiding Buttons': [true, 'Make buttons to hide threads / replies, in addition to menu links.'], 'Hiding Buttons': [true, 'Add buttons to hide threads / replies, in addition to menu links.'],
'Stubs': [true, 'Make stubs of hidden threads / replies.'] 'Stubs': [true, 'Show stubs of hidden threads / replies.']
}, },
'Images': { 'Images': {
'Image Expansion': [true, 'Expand images.'], 'Image Expansion': [true, 'Expand images.'],
@ -90,7 +89,9 @@
'Replace JPG': [false, 'Replace jpgs.'] 'Replace JPG': [false, 'Replace jpgs.']
}, },
'Menu': { '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.'], 'Report Link': [true, 'Add a report link to the menu.'],
'Delete Link': [true, 'Add post and image deletion links 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.'], '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.'], 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'],
'Hide Original Post Form': [true, 'Hide the normal post form.'] 'Hide Original Post Form': [true, 'Hide the normal post form.']
}, },
'Quote links': { 'Quote Links': {
'Quote Backlinks': [true, 'Add quote backlinks.'], 'Quote Backlinks': [true, 'Add quote backlinks.'],
'OP Backlinks': [false, 'Add backlinks to the OP.'], 'OP Backlinks': [false, 'Add backlinks to the OP.'],
'Quote Inlining': [true, 'Inline quoted post on click.'], 'Quote Inlining': [true, 'Inline quoted post on click.'],
@ -238,7 +239,8 @@
filesize: '', filesize: '',
MD5: '' 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 auto-hide': false,
'Header catalog links': false, 'Header catalog links': false,
boardnav: '[ toggle-all ] [current-title]', boardnav: '[ toggle-all ] [current-title]',
@ -292,10 +294,6 @@
mascot: '' mascot: ''
}; };
if (!/^[a-z]+\.4chan\.org$/.test(location.hostname)) {
return;
}
editTheme = {}; editTheme = {};
editMascot = {}; editMascot = {};
@ -308,7 +306,7 @@
d = document; d = document;
doc = null; doc = d.documentElement;
g = { g = {
VERSION: '2.0.0', VERSION: '2.0.0',
@ -2913,7 +2911,7 @@
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000))); $.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)));
$.extend($, { $.extend($, {
engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase(), engine: 'gecko',
id: function(id) { id: function(id) {
return d.getElementById(id); return d.getElementById(id);
}, },
@ -3208,16 +3206,9 @@
script = $.el('script', { script = $.el('script', {
textContent: code textContent: code
}); });
$.add(d.head, script); $.add(d.head || doc, script);
return $.rm(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) { bytesToString: function(size) {
var unit; var unit;
@ -3228,70 +3219,37 @@
} }
size = unit > 1 ? Math.round(size * 100) / 100 : Math.round(size); size = unit > 1 ? Math.round(size * 100) / 100 : Math.round(size);
return "" + size + " " + ['B', 'KB', 'MB', 'GB'][unit]; 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 = { Polyfill = {
init: function() { init: function() {
return Polyfill.visibility(); return Polyfill.visibility();
@ -3882,7 +3840,7 @@
}; };
/* /*
JSColor JSColor
http://github.com/hotchpotch/jscolor/tree/master http://github.com/hotchpotch/jscolor/tree/master
JSColor is color library for JavaScript. JSColor is color library for JavaScript.
@ -4919,7 +4877,7 @@
innerHTML: '[<a href=javascript:;> - </a>]\u00A0' innerHTML: '[<a href=javascript:;> - </a>]\u00A0'
}); });
$.on(btn, 'click', Header.toggleBoardList); $.on(btn, 'click', Header.toggleBoardList);
return $.prepend(fullBoardList, btn); return $.add(fullBoardList, btn);
} else { } else {
$.rm($('#custom-board-list', nav)); $.rm($('#custom-board-list', nav));
return fullBoardList.hidden = false; return fullBoardList.hidden = false;
@ -4933,8 +4891,8 @@
if (!text) { if (!text) {
return; return;
} }
as = $$('#full-board-list a', Header.nav); as = $$('#full-board-list a', Header.nav).slice(0, -2);
nodes = text.match(/[\w@]+(-(all|title|full|text:"[^"]+"))?|[^\w@]+/g).map(function(t) { nodes = text.match(/[\w@]+(-(all|title|full|index|catalog|text:"[^"]+"))*|[^\w@]+/g).map(function(t) {
var a, board, m, _i, _len; var a, board, m, _i, _len;
if (/^[^\w@]/.test(t)) { if (/^[^\w@]/.test(t)) {
@ -4954,12 +4912,21 @@
a = as[_i]; a = as[_i];
if (a.textContent === board) { if (a.textContent === board) {
a = a.cloneNode(true); a = a.cloneNode(true);
if (/-title$/.test(t)) { if (/-title/.test(t)) {
a.textContent = a.title; a.textContent = a.title;
} else if (/-full$/.test(t)) { } else if (/-full/.test(t)) {
a.textContent = "/" + board + "/ - " + a.title; a.textContent = "/" + board + "/ - " + a.title;
} else if (m = t.match(/-text:"(.+)"$/)) { } else if (/-(index|catalog|text)/.test(t)) {
a.textContent = m[1]; 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 === '@') { } else if (board === '@') {
$.addClass(a, 'navSmall'); $.addClass(a, 'navSmall');
} }
@ -5107,7 +5074,7 @@
Settings = { Settings = {
init: function() { init: function() {
var link, settings; var link, prevVersion, settings;
link = $.el('a', { link = $.el('a', {
id: 'appchanOptions', id: 'appchanOptions',
@ -5127,9 +5094,12 @@
return $.prepend($.id('navtopright'), [$.tn(' ['), link, $.tn('] ')]); 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); $.set('previousversion', g.VERSION);
$.on(d, '4chanXInitFinished', Settings.open); if (!prevVersion) {
$.on(d, '4chanXInitFinished', Settings.open);
}
} }
Settings.addSection('Main', Settings.main); Settings.addSection('Main', Settings.main);
Settings.addSection('Filter', Settings.filter); Settings.addSection('Filter', Settings.filter);
@ -5167,6 +5137,7 @@
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
section = _ref[_i]; section = _ref[_i];
link = $.el('a', { link = $.el('a', {
className: "tab-" + section.hyphenatedTitle,
textContent: section.title, textContent: section.title,
href: 'javascript:;' href: 'javascript:;'
}); });
@ -5177,12 +5148,8 @@
} }
} }
links.pop(); links.pop();
if (sectionToOpen) {
sectionToOpen.click();
} else {
links[0].click();
}
$.add($('.sections-list', overlay), links); $.add($('.sections-list', overlay), links);
(sectionToOpen ? sectionToOpen : links[0]).click();
$.on($('.close', overlay), 'click', Settings.close); $.on($('.close', overlay), 'click', Settings.close);
$.on(overlay, 'click', Settings.close); $.on(overlay, 'click', Settings.close);
$.on(overlay.firstElementChild, 'click', function(e) { $.on(overlay.firstElementChild, 'click', function(e) {
@ -5203,22 +5170,28 @@
}, },
sections: [], sections: [],
addSection: function(title, open) { addSection: function(title, open) {
var _ref; var hyphenatedTitle, _ref;
if (typeof title !== 'string') { if (typeof title !== 'string') {
_ref = title.detail, title = _ref.title, open = _ref.open; _ref = title.detail, title = _ref.title, open = _ref.open;
} }
hyphenatedTitle = title.toLowerCase().replace(/\s+/g, '-');
return Settings.sections.push({ return Settings.sections.push({
title: title, title: title,
hyphenatedTitle: hyphenatedTitle,
open: open open: open
}); });
}, },
openSection: function() { 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 = $('section', Settings.dialog);
section.innerHTML = null; section.innerHTML = null;
section.className = "section-" + (this.title.toLowerCase().replace(/\s+/g, '-')); section.className = "section-" + this.hyphenatedTitle;
this.open(section, g); this.open(section, g);
return section.scrollTop = 0; return section.scrollTop = 0;
}, },
@ -5242,7 +5215,7 @@
div = $.el('div', { div = $.el('div', {
innerHTML: "<label><input type=checkbox name=\"" + key + "\" " + checked + ">" + key + "</label><span class=description>: " + description + "</span>" 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(fs, div);
} }
$.add(section, fs); $.add(section, fs);
@ -5266,8 +5239,7 @@
}); });
$.on($('button', div), 'click', function() { $.on($('button', div), 'click', function() {
this.textContent = 'Hidden: 0'; this.textContent = 'Hidden: 0';
$["delete"]("hiddenThreads." + g.BOARD); return $["delete"](["hiddenThreads." + g.BOARD, "hiddenPosts." + g.BOARD]);
return $["delete"]("hiddenPosts." + g.BOARD);
}); });
return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div); return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div);
}, },
@ -5285,7 +5257,7 @@
className: 'warning', className: 'warning',
textContent: 'Save me!', textContent: 'Save me!',
download: "appchan x v" + g.VERSION + "-" + now + ".json", 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' target: '_blank'
}); });
if ($.engine !== 'gecko') { if ($.engine !== 'gecko') {
@ -5449,7 +5421,7 @@
$.add(div, ta); $.add(div, ta);
return; 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) { sauce: function(section) {
var sauce; var sauce;
@ -5462,7 +5434,7 @@
rice: function(section) { rice: function(section) {
var event, input, name, _i, _len, _ref; 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']; _ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
name = _ref[_i]; name = _ref[_i];
@ -5475,6 +5447,7 @@
Settings[name].call(input); Settings[name].call(input);
} }
} }
$.on($('input[name="Custom CSS"]', section), 'change', Settings.togglecss);
return $.on($.id('apply-css'), 'click', Settings.usercss); return $.on($.id('apply-css'), 'click', Settings.usercss);
}, },
boardnav: function() { boardnav: function() {
@ -5512,14 +5485,18 @@
if (g.VIEW === 'thread' && Conf['Unread Tab Icon']) { if (g.VIEW === 'thread' && Conf['Unread Tab Icon']) {
Unread.update(); 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() { usercss: function() {
if (Conf['Custom CSS']) { return CustomCSS.update();
return CustomCSS.update();
} else {
return CustomCSS.rmStyle();
}
}, },
keybinds: function(section) { keybinds: function(section) {
var arr, input, key, tbody, tr, _ref; var arr, input, key, tbody, tr, _ref;
@ -5565,12 +5542,14 @@
} }
board = g.BOARD.ID; board = g.BOARD.ID;
if (board === 'g') { 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({ Post.prototype.callbacks.push({
name: 'Parse /g/ code', name: 'Parse /g/ code',
cb: this.code cb: this.code
}); });
} }
if (board === 'sci') { 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({ return Post.prototype.callbacks.push({
name: 'Parse /sci/ math', name: 'Parse /sci/ math',
cb: this.math cb: this.math
@ -5586,23 +5565,14 @@
_ref = $$('.prettyprint', this.nodes.comment); _ref = $$('.prettyprint', this.nodes.comment);
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
pre = _ref[_i]; pre = _ref[_i];
pre.innerHTML = $.unsafeWindow.prettyPrintOne(pre.innerHTML); $.event('prettyprint', pre, window);
} }
}, },
math: function() { math: function() {
var jsMath;
if (this.isClone || !$('.math', this.nodes.comment)) { if (this.isClone || !$('.math', this.nodes.comment)) {
return; return;
} }
jsMath = $.unsafeWindow.jsMath; return $.event('jsmath', this.nodes.post, window);
if (jsMath) {
if (jsMath.loaded) {
return jsMath.ProcessBeforeShowing(this.nodes.post);
} else {
return $.globalEval("jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);\njsMath.Autoload.LoadJsMath();");
}
}
}, },
parseThread: function(threadID, offset, limit) { parseThread: function(threadID, offset, limit) {
return $.event('4chanParsingDone', { return $.event('4chanParsingDone', {
@ -5921,7 +5891,7 @@
ThreadHiding = { ThreadHiding = {
init: function() { init: function() {
if (g.VIEW !== 'index' || !Conf['Thread Hiding']) { if (g.VIEW !== 'index' || !Conf['Thread Hiding'] && !Conf['Thread Hiding Link']) {
return; return;
} }
Misc.clearThreads("hiddenThreads." + g.BOARD); Misc.clearThreads("hiddenThreads." + g.BOARD);
@ -5938,7 +5908,7 @@
if (data = ThreadHiding.hiddenThreads.threads[this]) { if (data = ThreadHiding.hiddenThreads.threads[this]) {
ThreadHiding.hide(this, data.makeStub); ThreadHiding.hide(this, data.makeStub);
} }
if (!Conf['Hiding Buttons']) { if (!Conf['Thread Hiding']) {
return; return;
} }
return $.prepend(this.OP.nodes.root, ThreadHiding.makeButton(this, 'hide')); return $.prepend(this.OP.nodes.root, ThreadHiding.makeButton(this, 'hide'));
@ -5975,7 +5945,7 @@
init: function() { init: function() {
var apply, div, makeStub; var apply, div, makeStub;
if (g.VIEW !== 'index' || !Conf['Menu'] || !Conf['Thread Hiding']) { if (g.VIEW !== 'index' || !Conf['Menu'] || !Conf['Thread Hiding Link']) {
return; return;
} }
div = $.el('div', { div = $.el('div', {
@ -6111,7 +6081,7 @@
ReplyHiding = { ReplyHiding = {
init: function() { init: function() {
if (g.VIEW === 'catalog' || !Conf['Reply Hiding']) { if (g.VIEW === 'catalog' || !Conf['Reply Hiding'] && !Conf['Reply Hiding Link']) {
return; return;
} }
Misc.clearThreads("hiddenPosts." + g.BOARD); Misc.clearThreads("hiddenPosts." + g.BOARD);
@ -6137,7 +6107,7 @@
} }
} }
} }
if (!Conf['Hiding Buttons']) { if (!Conf['Reply Hiding']) {
return; return;
} }
return $.add($('.postInfo', this.nodes.post), ReplyHiding.makeButton(this, 'hide')); return $.add($('.postInfo', this.nodes.post), ReplyHiding.makeButton(this, 'hide'));
@ -6151,7 +6121,7 @@
init: function() { init: function() {
var apply, div, makeStub, replies, thisPost; 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; return;
} }
div = $.el('div', { div = $.el('div', {
@ -6461,7 +6431,7 @@
QuoteStrikeThrough = { QuoteStrikeThrough = {
init: function() { 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;
} }
return Post.prototype.callbacks.push({ return Post.prototype.callbacks.push({
@ -6874,7 +6844,7 @@
Settings.open(); Settings.open();
break; break;
case Conf['Close']: case Conf['Close']:
if ($.id('settings')) { if ($.id('fourchanx-settings')) {
Settings.close(); Settings.close();
} else if ((notifications = $$('.notification')).length) { } else if ((notifications = $$('.notification')).length) {
for (_i = 0, _len = notifications.length; _i < _len; _i++) { for (_i = 0, _len = notifications.length; _i < _len; _i++) {
@ -7111,8 +7081,7 @@
} }
}, },
focus: function(post) { focus: function(post) {
$.addClass(post, 'highlight'); return $.addClass(post, 'highlight');
return $('a[title="Highlight this post"]', post).focus();
} }
}; };
@ -7741,15 +7710,15 @@
if (data.lastChecked > Date.now() - 12 * $.HOUR) { if (data.lastChecked > Date.now() - 12 * $.HOUR) {
return; return;
} }
return $.ajax("//api.4chan.org/" + g.BOARD + "/catalog.json", { return $.ajax("//api.4chan.org/" + g.BOARD + "/threads.json", {
onload: function() { onload: function() {
var obj, thread, threads, _i, _j, _len, _len1, _ref, _ref1; var page, thread, threads, _i, _j, _len, _len1, _ref, _ref1;
threads = {}; threads = {};
_ref = JSON.parse(this.response); _ref = JSON.parse(this.response);
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
obj = _ref[_i]; page = _ref[_i];
_ref1 = obj.threads; _ref1 = page.threads;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
thread = _ref1[_j]; thread = _ref1[_j];
if (thread.no in data.threads) { if (thread.no in data.threads) {
@ -8008,7 +7977,7 @@
posts.pop(); posts.pop();
for (_i = 0, _len = posts.length; _i < _len; _i++) { for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i]; post = posts[_i];
$.addClass(post.nodes.post, 'qphl'); $.addClass(post.nodes.root, 'qphl');
} }
} }
quoterID = $.x('ancestor::*[@id][1]', this).id.match(/\d+$/)[0]; quoterID = $.x('ancestor::*[@id][1]', this).id.match(/\d+$/)[0];
@ -8043,7 +8012,7 @@
_ref = [post].concat(post.clones); _ref = [post].concat(post.clones);
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
post = _ref[_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) { link = link.replace(/%(T?URL|MD5|board)/ig, function(parameter) {
switch (parameter) { switch (parameter) {
case '%TURL': case '%TURL':
case '%turl': return "' + encodeURIComponent(post.file.thumbURL) + '";
return "' + post.file.thumbURL + '";
case '%URL': case '%URL':
case '%url': return "' + encodeURIComponent(post.file.URL) + '";
return "' + post.file.URL + '";
case '%MD5': case '%MD5':
case '%md5':
return "' + encodeURIComponent(post.file.MD5) + '"; return "' + encodeURIComponent(post.file.MD5) + '";
case '%board': case '%board':
return "' + post.board + '"; return "' + encodeURIComponent(post.board) + '";
default: default:
return parameter; return parameter;
} }
@ -8747,7 +8713,7 @@
rect = thumb.parentNode.getBoundingClientRect(); rect = thumb.parentNode.getBoundingClientRect();
if (rect.bottom > 0) { if (rect.bottom > 0) {
postRect = post.nodes.root.getBoundingClientRect(); postRect = post.nodes.root.getBoundingClientRect();
headRect = Header.bar.getBoundingClientRect(); headRect = Header.toggle.getBoundingClientRect();
top = postRect.top - headRect.top - headRect.height - 2; top = postRect.top - headRect.top - headRect.height - 2;
root = $.engine === 'webkit' ? d.body : doc; root = $.engine === 'webkit' ? d.body : doc;
if (rect.top < 0) { if (rect.top < 0) {
@ -8814,7 +8780,7 @@
post = Get.postFromNode(this); post = Get.postFromNode(this);
$.rm(this); $.rm(this);
delete post.file.fullImage; delete post.file.fullImage;
if (!$.hasClass(post.file.thumb, 'expanding')) { if (!($.hasClass(post.file.thumb, 'expanding') || $.hasClass(post.nodes.root, 'expanded-image'))) {
return; return;
} }
ImageExpand.contract(post); ImageExpand.contract(post);
@ -9166,7 +9132,7 @@
if (thread.isSticky) { if (thread.isSticky) {
return 1; return 1;
} else { } else {
switch (g.BOARD) { switch (g.BOARD.ID) {
case 'b': case 'b':
case 'vg': case 'vg':
case 'q': case 'q':
@ -9232,7 +9198,7 @@
Main.callbackNodes(Post, posts); Main.callbackNodes(Post, posts);
$.after(a, nodes); $.after(a, nodes);
if (Conf['Enable 4chan\'s Extension']) { 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 { } else {
return Fourchan.parseThread(thread.ID, 1, nodes.length); return Fourchan.parseThread(thread.ID, 1, nodes.length);
} }
@ -9250,7 +9216,9 @@
}); });
}, },
node: function() { 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']) { switch (Conf['favicon']) {
case 'ferongr': case 'ferongr':
Favicon.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAOMHAOgLAnMFAL8AAOgLAukMA/+AgP+rq////////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw=='; 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.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.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; break;
case 'xat-': case 'xat-':
Favicon.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA2ElEQVQ4y61TQQrCMBDMQ8WDIEV6LbT2A4og2Hq0veo7fIAH04dY9N4xmyYlpGmI2MCQTWYy3Wy2DAD7B2wWAzWgcTgVeZKlZRxHNYFi2jM18oBh0IcKtC6ixf22WT4IFLs0owxswXu9egm0Ls6bwfCFfNsJYJKfqoEkd3vgUgFVLWObtzNgVKyruC+ljSzr5OEnBzjvjcQecaQhbZgBb4CmGQw+PoMkTUtdbd8VSEPakcGxPOcsoIgUKy0LecY29BmdBrqRfjIwZ93KLs5loHvBnL3cLH/jF+C/+z5dgUysAAAAAElFTkSuQmCC'; 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.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.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; break;
case 'Mayhem': case 'Mayhem':
Favicon.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4jZ2ScWuDMBDFgw4pIkU0WsoQkWAYIkXZH4N9/+/V3dmfXSrKYIFHwt17j8vdGWNMIkgFuaDgzgQnwRs4EQs5KdolUQtagRN0givEDBTEOjgtGs0Zq8F7cKqqusVxrMQLaDUWcjBSrXkn8gs51tpJSWLk9b3HUa0aNIL5gPBR1/V4kJvR7lTwl8GmAm1Gf9+c3S+89qBHa8502AsmSrtBaEBPbIbj0ah2madlNAPEccdgJDfAtWifBjqWKShRBT6KoiH8QlEUn/qt0CCjnNdmPUwmFWzj9Oe6LpKuZXcwqq88z78Pch3aZU3dPwwc2sWlfZKCW5tWluV8kGvXClLm6dYN4/aUqfCbnEOzNDGhGZbNargvxCzvMGfRJD8UaDVvgkzo6QAAAABJRU5ErkJggg=='; 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.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.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.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; break;
case 'Original': case 'Original':
Favicon.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; 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.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.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) { if (Favicon.SFW) {
Favicon.unread = Favicon.unreadSFW; Favicon.unread = Favicon.unreadSFW;
@ -9859,9 +9827,9 @@
var length, threadID; var length, threadID;
threadID = ThreadUpdater.thread.ID; threadID = ThreadUpdater.thread.ID;
length = ThreadUpdater.root.children.length; length = $$('.thread > .postContainer', ThreadUpdater.root).length;
if (Conf['Enable 4chan\'s Extension']) { if (Conf['Enable 4chan\'s Extension']) {
return $.unsafeWindow.Parser.parseThread(threadID, -count); return $.globalEval("Parser.parseThread(" + threadID + ", " + (-count) + ")");
} else { } else {
return Fourchan.parseThread(threadID, length - count, length); return Fourchan.parseThread(threadID, length - count, length);
} }
@ -9909,6 +9877,9 @@
} }
}, },
ready: function() { ready: function() {
if (!Main.isThisPageLegit()) {
return;
}
ThreadWatcher.refresh(); ThreadWatcher.refresh();
return $.add(d.body, ThreadWatcher.dialog); return $.add(d.body, ThreadWatcher.dialog);
}, },
@ -10899,6 +10870,9 @@
var applyBlob, cv, data, height, i, l, s, ui8a, width, _i; var applyBlob, cv, data, height, i, l, s, ui8a, width, _i;
s = 90 * 2; s = 90 * 2;
if (_this.file.type === 'image/gif') {
s *= 3;
}
height = img.height, width = img.width; height = img.height, width = img.width;
if (height < s || width < s) { if (height < s || width < s) {
if (window.URL) { if (window.URL) {
@ -11051,8 +11025,16 @@
}), this.ready.bind(this)); }), this.ready.bind(this));
}, },
ready: function() { 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', { imgContainer = $.el('div', {
className: 'captcha-img', className: 'captcha-img',
title: 'Reload', title: 'Reload',
@ -11153,7 +11135,7 @@
if (!this.nodes.challenge.firstChild) { if (!this.nodes.challenge.firstChild) {
return; return;
} }
this.timeout = Date.now() + $.unsafeWindow.RecaptchaState.timeout * $.SECOND - $.MINUTE; this.timeout = Date.now() + this.lifetime * $.SECOND - $.MINUTE;
challenge = this.nodes.challenge.firstChild.value; challenge = this.nodes.challenge.firstChild.value;
this.nodes.img.alt = challenge; this.nodes.img.alt = challenge;
this.nodes.img.src = "//www.google.com/recaptcha/api/image?c=" + challenge; this.nodes.img.src = "//www.google.com/recaptcha/api/image?c=" + challenge;
@ -11177,7 +11159,7 @@
return this.nodes.input.alt = count; return this.nodes.input.alt = count;
}, },
reload: function(focus) { reload: function(focus) {
$.unsafeWindow.Recaptcha.reload('t'); $.globalEval('Recaptcha.reload("t")');
if (focus) { if (focus) {
return this.nodes.input.focus(); return this.nodes.input.focus();
} }
@ -11196,7 +11178,7 @@
dialog: function() { dialog: function() {
var dialog, mimeTypes, name, node, nodes, thread, _i, _j, _len, _len1, _ref, _ref1; 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 = { QR.nodes = nodes = {
el: dialog, el: dialog,
move: $('.move', dialog), move: $('.move', dialog),
@ -11366,7 +11348,9 @@
post.unlock(); post.unlock();
QR.cooldown.auto = false; QR.cooldown.auto = false;
QR.status(); 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 = { opts = {
@ -11390,7 +11374,7 @@
return QR.status(); return QR.status();
}, },
response: function() { 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; req = QR.req;
delete 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]; _ref1 = h1.nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = _ref1[0], threadID = _ref1[1], postID = _ref1[2];
postID = +postID; postID = +postID;
threadID = +threadID || postID; threadID = +threadID || postID;
isReply = threadID !== postID;
((_base = QR.yourPosts.threads)[threadID] || (_base[threadID] = [])).push(postID); ((_base = QR.yourPosts.threads)[threadID] || (_base[threadID] = [])).push(postID);
$.set("yourPosts." + g.BOARD, QR.yourPosts); $.set("yourPosts." + g.BOARD, QR.yourPosts);
ThreadUpdater.postID = postID; ThreadUpdater.postID = postID;
@ -11449,16 +11434,16 @@
threadID: threadID, threadID: threadID,
postID: postID postID: postID
}, QR.nodes.el); }, QR.nodes.el);
QR.cooldown.auto = QR.posts.length > 1; QR.cooldown.auto = QR.posts.length > 1 && isReply;
post.rm(); post.rm();
QR.cooldown.set({ QR.cooldown.set({
req: req, req: req,
post: post, post: post,
isReply: !!threadID isReply: isReply
}); });
if (threadID === postID) { if (threadID === postID) {
URL = "/" + g.BOARD + "/res/" + threadID; 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; URL = "/" + g.BOARD + "/res/" + threadID + "#p" + postID;
} }
if (URL) { if (URL) {
@ -11498,7 +11483,7 @@
field = $.id('recaptcha_response_field'); field = $.id('recaptcha_response_field');
$.on(field, 'keydown', function(e) { $.on(field, 'keydown', function(e) {
if (e.keyCode === 8 && !field.value) { if (e.keyCode === 8 && !field.value) {
return $.unsafeWindow.Recaptcha.reload('t'); return $.globalEval('Recaptcha.reload("t")');
} }
}); });
return $.on(form, 'submit', function(e) { return $.on(form, 'submit', function(e) {
@ -11602,9 +11587,9 @@
this.nodes.uniqueID = uniqueID; this.nodes.uniqueID = uniqueID;
this.info.uniqueID = uniqueID.firstElementChild.textContent; this.info.uniqueID = uniqueID.firstElementChild.textContent;
} }
if (capcode = $('.capcode', info)) { if (capcode = $('.capcode.hand', info)) {
this.nodes.capcode = capcode; this.nodes.capcode = capcode;
this.info.capcode = capcode.textContent; this.info.capcode = capcode.textContent.replace('## ', '');
} }
if (flag = $('.countryFlag', info)) { if (flag = $('.countryFlag', info)) {
this.nodes.flag = flag; this.nodes.flag = flag;
@ -11887,11 +11872,6 @@
init: function() { init: function() {
var flatten, initFeatures, key, pathname, val; var flatten, initFeatures, key, pathname, val;
$.asap((function() {
return d.documentElement;
}), function() {
return doc = d.documentElement;
});
flatten = function(parent, obj) { flatten = function(parent, obj) {
var key, val; var key, val;
@ -12079,7 +12059,8 @@
Main.callbackNodes(Thread, threads); Main.callbackNodes(Thread, threads);
Main.callbackNodes(Post, posts); Main.callbackNodes(Post, posts);
} }
return $.event('4chanXInitFinished'); $.event('4chanXInitFinished');
return Main.checkUpdate();
}, },
callbackNodes: function(klass, nodes) { callbackNodes: function(klass, nodes) {
var callback, err, errors, i, len, node, _i, _j, _len, _ref; var callback, err, errors, i, len, node, _i, _j, _len, _ref;
@ -12112,10 +12093,56 @@
var Klass, obj; var Klass, obj;
obj = e.detail; 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; obj.callback.isAddon = true;
return Klass.prototype.callbacks.push(obj.callback); 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) { handleErrors: function(errors) {
var div, error, logs, _i, _len; var div, error, logs, _i, _len;
@ -12129,7 +12156,7 @@
return; return;
} }
div = $.el('div', { 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() { $.on(div.lastElementChild, 'click', function() {
if (this.textContent === 'show') { if (this.textContent === 'show') {
@ -12164,8 +12191,10 @@
return [message, error]; return [message, error];
}, },
isThisPageLegit: function() { isThisPageLegit: function() {
var _ref;
if (!('thisPageIsLegit' in Main)) { 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; 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))) $.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)))
$.extend $, $.extend $,
engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase() engine: '<% if (type === 'crx') { %>webkit<% } else if (type === 'userjs') { %>presto<% } else { %>gecko<% } %>'
id: (id) -> id: (id) ->
d.getElementById id d.getElementById id
ready: (fc) -> ready: (fc) ->
@ -227,19 +227,8 @@ $.extend $,
globalEval: (code) -> globalEval: (code) ->
script = $.el 'script', script = $.el 'script',
textContent: code textContent: code
$.add d.head, script $.add (d.head or doc), script
$.rm 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) -> bytesToString: (size) ->
unit = 0 # Bytes unit = 0 # Bytes
while size >= 1024 while size >= 1024
@ -256,50 +245,65 @@ $.extend $,
Math.round size Math.round size
"#{size} #{['B', 'KB', 'MB', 'GB'][unit]}" "#{size} #{['B', 'KB', 'MB', 'GB'][unit]}"
if GM_deleteValue? <% if (type === 'crx') { %>
$.delete = (name) -> delete: (keys) ->
GM_deleteValue g.NAMESPACE + name chrome.storage.sync.remove keys
$.get = (name, defaultValue) -> get: (key, defaultVal) ->
if value = GM_getValue g.NAMESPACE + name if val = localStorage.getItem g.NAMESPACE + key
JSON.parse value JSON.parse val
else else
defaultValue defaultVal
$.set = (name, value) -> set: (key, val) ->
name = g.NAMESPACE + name item = {}
value = JSON.stringify value 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 # for `storage` events
localStorage.setItem name, value localStorage.setItem key, val
GM_setValue name, value scriptStorage[key] = val
else if window.opera <% } else { %>
do -> delete: (key) ->
# http://www.opera.com/docs/userjs/specs/#scriptstorage unless keys instanceof Array
# http://www.opera.com/docs/userjs/using/#securepages keys = [keys]
# >The scriptStorage object is available only during for key in keys
# the main User JavaScript thread, being therefore key = g.NAMESPACE + key
# accessible only in the main body of the user script. localStorage.removeItem key
# To access the storage object later, keep a reference GM_deleteValue key
# to the object. return
{scriptStorage} = opera get: (key, defaultVal) ->
$.delete = (name) -> if val = GM_getValue g.NAMESPACE + key
delete scriptStorage[g.NAMESPACE + name] JSON.parse val
$.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
else else
defaultValue defaultVal
$.set = (name, value) -> set: (key, val) ->
localStorage.setItem g.NAMESPACE + name, JSON.stringify value 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", "namespace": "zixaphir",
"repo": "https://github.com/zixaphir/appchan-x/", "repo": "https://github.com/zixaphir/appchan-x/",
"page": "http://zixaphir.github.com/appchan-x/", "page": "http://zixaphir.github.com/appchan-x/",
"buildsPath": "builds/",
"mainBranch": "Av2", "mainBranch": "Av2",
"matches": [ "matches": [
"*://api.4chan.org/*", "*://api.4chan.org/*",
@ -16,12 +17,14 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"grunt": "~0.4.0", "grunt": "~0.4.1",
"grunt-bump": "~0.0.0", "grunt-bump": "~0.0.0",
"grunt-contrib-clean": "~0.4.0", "grunt-contrib-clean": "~0.4.0",
"grunt-contrib-coffee": "~0.6.2", "grunt-contrib-coffee": "~0.6.4",
"grunt-contrib-concat": "~0.1.0", "grunt-contrib-compress": "~0.4.5",
"grunt-contrib-watch": "~0.3.0", "grunt-contrib-concat": "~0.1.3",
"grunt-contrib-copy": "~0.4.0",
"grunt-contrib-watch": "~0.3.1",
"grunt-exec": "~0.4.0" "grunt-exec": "~0.4.0"
}, },
"repository": { "repository": {

View File

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

View File

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

View File

@ -1,6 +1,8 @@
<% if (type === 'userjs') { %>
# Opera doesn't support the @match metadata key, # Opera doesn't support the @match metadata key,
# return 4chan X here if we're not on 4chan. # return 4chan X here if we're not on 4chan.
return unless /^[a-z]+\.4chan\.org$/.test location.hostname return unless /^[a-z]+\.4chan\.org$/.test location.hostname
<% } %>
editTheme = {} # Currently editted theme. editTheme = {} # Currently editted theme.
editMascot = {} # Which mascot we're editting. editMascot = {} # Which mascot we're editting.
@ -8,8 +10,8 @@ userNavigation = {} # ...
Conf = {} Conf = {}
c = console c = console
d = document d = document
doc = null doc = d.documentElement
g = g =
VERSION: '<%= version %>' VERSION: '<%= version %>'
NAMESPACE: '<%= meta.name %>.'.replace ' ', '_' NAMESPACE: '<%= meta.name %>.'.replace ' ', '_'
TYPE: 'sfw' TYPE: 'sfw'
@ -2759,4 +2761,4 @@ Icons =
png: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA' png: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA'
themes: 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=' 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' backlinks: info.getElementsByClassName 'backlink'
@info = {} @info = {}
if subject = $ '.subject', info if subject = $ '.subject', info
@nodes.subject = subject @nodes.subject = subject
@info.subject = subject.textContent @info.subject = subject.textContent
if name = $ '.name', info if name = $ '.name', info
@nodes.name = name @nodes.name = name
@info.name = name.textContent @info.name = name.textContent
if email = $ '.useremail', info if email = $ '.useremail', info
@nodes.email = email @nodes.email = email
@info.email = decodeURIComponent email.href[7..] @info.email = decodeURIComponent email.href[7..]
if tripcode = $ '.postertrip', info if tripcode = $ '.postertrip', info
@nodes.tripcode = tripcode @nodes.tripcode = tripcode
@info.tripcode = tripcode.textContent @info.tripcode = tripcode.textContent
if uniqueID = $ '.posteruid', info if uniqueID = $ '.posteruid', info
@nodes.uniqueID = uniqueID @nodes.uniqueID = uniqueID
@info.uniqueID = uniqueID.firstElementChild.textContent @info.uniqueID = uniqueID.firstElementChild.textContent
if capcode = $ '.capcode', info if capcode = $ '.capcode.hand', info
@nodes.capcode = capcode @nodes.capcode = capcode
@info.capcode = capcode.textContent @info.capcode = capcode.textContent.replace '## ', ''
if flag = $ '.countryFlag', info if flag = $ '.countryFlag', info
@nodes.flag = flag @nodes.flag = flag
@info.flag = flag.title @info.flag = flag.title
if date = $ '.dateTime', info if date = $ '.dateTime', info
@nodes.date = date @nodes.date = date
@info.date = new Date date.dataset.utc * 1000 @info.date = new Date date.dataset.utc * 1000
if Conf['Quick Reply'] if Conf['Quick Reply']
@ -284,9 +284,6 @@ class Clone extends Post
Main = Main =
init: -> init: ->
$.asap (-> d.documentElement), ->
doc = d.documentElement
# flatten Config into Conf # flatten Config into Conf
# and get saved or default values # and get saved or default values
flatten = (parent, obj) -> flatten = (parent, obj) ->
@ -344,7 +341,7 @@ Main =
for name, module of features for name, module of features
# c.time "#{name} initialization" # c.time "#{name} initialization"
try try
module.init() do module.init
catch err catch err
Main.handleErrors Main.handleErrors
message: "\"#{name}\" initialization crashed." message: "\"#{name}\" initialization crashed."
@ -447,6 +444,7 @@ Main =
Main.callbackNodes Post, posts Main.callbackNodes Post, posts
$.event '4chanXInitFinished' $.event '4chanXInitFinished'
Main.checkUpdate()
callbackNodes: (klass, nodes) -> callbackNodes: (klass, nodes) ->
# get the nodes' length only once # get the nodes' length only once
@ -467,14 +465,42 @@ Main =
Main.handleErrors errors if errors Main.handleErrors errors if errors
addCallback: (e) -> addCallback: (e) ->
obj = e.detail obj = e.detail
Klass = if obj.type is 'Post' unless typeof obj.callback.name is 'string'
Post throw new Error "Invalid callback name: #{obj.callback.name}"
else switch obj.type
Thread when 'Post'
Klass = Post
when 'Thread'
Klass = Thread
else
return
obj.callback.isAddon = true obj.callback.isAddon = true
Klass::callbacks.push obj.callback 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) -> handleErrors: (errors) ->
unless 'length' of errors unless 'length' of errors
error = errors error = errors
@ -485,7 +511,7 @@ Main =
return return
div = $.el 'div', 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', -> $.on div.lastElementChild, 'click', ->
if @textContent is 'show' if @textContent is 'show'
@textContent = 'hide' @textContent = 'hide'
@ -514,7 +540,9 @@ Main =
isThisPageLegit: -> isThisPageLegit: ->
# 404 error page or similar. # 404 error page or similar.
unless 'thisPageIsLegit' of Main 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 Main.thisPageIsLegit

View File

@ -3,12 +3,19 @@
"version": "<%= version %>", "version": "<%= version %>",
"manifest_version": 2, "manifest_version": 2,
"description": "<%= description %>", "description": "<%= description %>",
"icons": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
},
"content_scripts": [{ "content_scripts": [{
"js": ["script.js"], "js": ["script.js"],
"matches": <%= JSON.stringify(meta.matches) %>, "matches": <%= JSON.stringify(meta.matches) %>,
"run_at": "document_start" "run_at": "document_start"
}], }],
"homepage_url": "<%= meta.page %>", "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_deleteValue
// @grant GM_openInTab // @grant GM_openInTab
// @run-at document-start // @run-at document-start
// @updateURL <%= meta.repo %>raw/<%= meta.mainBranch %>/<%= name %>.meta.js // @updateURL <%= meta.page %><%= meta.buildsPath %><%= name %>.meta.js
// @downloadURL <%= meta.repo %>raw/<%= meta.mainBranch %>/<%= name %>.user.js // @downloadURL <%= meta.page %><%= meta.buildsPath %><%= name %>.user.js
// @icon data:image/gif;base64,<%= grunt.file.read('img/icon.gif', {encoding: 'base64'}) %> // @icon data:image/png;base64,<%= grunt.file.read('img/icon48.png', {encoding: 'base64'}) %>
// ==/UserScript== // ==/UserScript==

View File

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

View File

@ -6,7 +6,7 @@ Report =
form = $ 'form' form = $ 'form'
field = $.id 'recaptcha_response_field' field = $.id 'recaptcha_response_field'
$.on field, 'keydown', (e) -> $.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) -> $.on form, 'submit', (e) ->
e.preventDefault() e.preventDefault()
response = field.value.trim() response = field.value.trim()