diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 42fa67ad2..66cdd1973 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,10 +3,10 @@ 1. Make sure both your **browser** and **4chan X** are up to date. 2. Disable your other extensions & scripts to identify conflicts. 3. If your issue persists, open a [new issue](https://github.com/MayhemYDG/4chan-x/issues) with the following information: - 1. Report precise steps to reproduce the problem. - 2. Report console errors, if any. - 3. Report browser version. - 4. Include your exported settings. + 1. Precise steps to reproduce the problem. + 2. Console errors, if any. + 3. Browser version. + 4. Your exported settings. Open your console with: - `Ctrl + Shift + J` on Chrome. diff --git a/Gruntfile.coffee b/Gruntfile.coffee index d4cfed689..40e433381 100644 --- a/Gruntfile.coffee +++ b/Gruntfile.coffee @@ -24,31 +24,18 @@ module.exports = (grunt) -> ] dest: 'tmp/script.coffee' - manifest: - options: - process: - data: pkg - src: 'src/manifest.json', - dest: 'builds/crx/manifest.json' - - metadata: - options: - process: - data: pkg - src: 'src/metadata.js', - dest: '<%= pkg.name %>.meta.js' - crx: options: process: data: pkg - src: [ - 'src/banner.js' - 'tmp/script.js' - ] - dest: 'builds/crx/script.js' + files: + 'builds/crx/manifest.json': 'src/manifest.json' + 'builds/crx/script.js': [ + 'src/banner.js' + 'tmp/script.js' + ] - userscript: + userjs: options: process: data: pkg @@ -57,13 +44,27 @@ module.exports = (grunt) -> 'src/banner.js' 'tmp/script.js' ] - dest: '<%= pkg.name %>.user.js' - - userjs: - # Lazily copy the userscript - src: '<%= pkg.name %>.user.js' dest: 'builds/<%= pkg.name %>.js' + userscript: + options: + process: + data: pkg + files: + '<%= pkg.name %>.meta.js': 'src/metadata.js' + '<%= pkg.name %>.user.js': [ + 'src/metadata.js' + 'src/banner.js' + 'tmp/script.js' + ] + + copy: + crx: + src: 'img/*.png' + dest: 'builds/crx/' + expand: true + flatten: true + coffee: script: src: 'tmp/script.coffee' @@ -90,35 +91,73 @@ module.exports = (grunt) -> options: interrupt: true files: [ - 'Gruntfile.js' + 'Gruntfile.coffee' 'package.json' - 'lib/**/*.coffee' - 'src/**/*.coffee' - 'src/**/*.js' - 'css/**/*.css' - 'img/*' + 'lib/**/*' + 'src/**/*' + 'css/**/*' + 'img/**/*' ] - tasks: 'default' + tasks: 'build' + + compress: + crx: + options: + archive: 'builds/4chan-X.zip' + level: 9 + pretty: true + expand: true + cwd: 'builds/crx/' + src: '**' clean: - tmp: 'tmp' + builds: 'builds' + tmp: 'tmp' grunt.loadNpmTasks 'grunt-bump' grunt.loadNpmTasks 'grunt-contrib-clean' grunt.loadNpmTasks 'grunt-contrib-coffee' + grunt.loadNpmTasks 'grunt-contrib-compress' grunt.loadNpmTasks 'grunt-contrib-concat' + grunt.loadNpmTasks 'grunt-contrib-copy' grunt.loadNpmTasks 'grunt-contrib-watch' grunt.loadNpmTasks 'grunt-exec' - grunt.registerTask 'default', [ - 'concat:coffee', - 'coffee:script', - 'concat:manifest', - 'concat:crx', - 'concat:userscript', - 'concat:userjs', - 'concat:metadata', - 'clean' + grunt.registerTask 'default', ['build'] + + grunt.registerTask 'set-build', 'Set the build type variable', (type) -> + pkg.type = type; + grunt.log.ok 'pkg.type = %s', type + + grunt.registerTask 'build', [ + 'build-crx' + 'build-userjs' + 'build-userscript' + ] + + grunt.registerTask 'build-crx', [ + 'set-build:crx' + 'concat:coffee' + 'coffee:script' + 'concat:crx' + 'copy:crx' + 'clean:tmp' + ] + + grunt.registerTask 'build-userjs', [ + 'set-build:userjs' + 'concat:coffee' + 'coffee:script' + 'concat:userjs' + 'clean:tmp' + ] + + grunt.registerTask 'build-userscript', [ + 'set-build:userscript' + 'concat:coffee' + 'coffee:script' + 'concat:userscript' + 'clean:tmp' ] grunt.registerTask 'release', [ @@ -141,6 +180,7 @@ module.exports = (grunt) -> 'bump:major' 'updcl:1' ] + grunt.registerTask 'updcl', 'Update the changelog', (i) -> # Update the `pkg` object with the new version. pkg = grunt.file.readJSON('package.json'); diff --git a/README.md b/README.md index d377b6372..c8c176c3d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # 4chan X -Get it [here](http://mayhemydg.github.com/4chan-x/). +Get it [here](https://4chan-x.just-believe.in/). *** -### [MIT License](/4chan-x/blob/master/LICENSE) -### [Contribute](/4chan-x/blob/master/CONTRIBUTING.md) +### [MIT License](/LICENSE) +### [Contribute](/CONTRIBUTING.md) diff --git a/appchan-x.meta.js b/appchan-x.meta.js index c3169b8cc..7c06671dd 100644 --- a/appchan-x.meta.js +++ b/appchan-x.meta.js @@ -15,7 +15,7 @@ // @grant GM_deleteValue // @grant GM_openInTab // @run-at document-start -// @updateURL https://github.com/zixaphir/appchan-x/raw/Av2/appchan-x.meta.js -// @downloadURL https://github.com/zixaphir/appchan-x/raw/Av2/appchan-x.user.js -// @icon data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAIALAAAAAAQABAAAAIxlI+pq+D9DAgUoFkPDlbs7lGiI2bSVnKglnJMOL6omczxVZK3dH/41AG6Lh7i6qUoAAA7 +// @updateURL http://zixaphir.github.com/appchan-x/builds/appchan-x.meta.js +// @downloadURL http://zixaphir.github.com/appchan-x/builds/appchan-x.user.js +// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwAgMAAAAqbBEUAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAHFJREFUKFOt0LENACEIBdBv4Qju4wgWanEj3D6OcIVMKaitYHEU/jwTCQj8W75kiVCSBvdQ5/AvfVHBin11BgdRq3ysBgfwBDRrj3MCIA+oAQaku/Q1cNctrAmyDl577tOThYt/Y1RBM4DgOHzM0HFTAyLukH/cmRnqAAAAAElFTkSuQmCC // ==/UserScript== diff --git a/appchan-x.user.js b/appchan-x.user.js index dedc3a693..218a297a1 100644 --- a/appchan-x.user.js +++ b/appchan-x.user.js @@ -15,12 +15,12 @@ // @grant GM_deleteValue // @grant GM_openInTab // @run-at document-start -// @updateURL https://github.com/zixaphir/appchan-x/raw/Av2/appchan-x.meta.js -// @downloadURL https://github.com/zixaphir/appchan-x/raw/Av2/appchan-x.user.js -// @icon data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAIALAAAAAAQABAAAAIxlI+pq+D9DAgUoFkPDlbs7lGiI2bSVnKglnJMOL6omczxVZK3dH/41AG6Lh7i6qUoAAA7 +// @updateURL http://zixaphir.github.com/appchan-x/builds/appchan-x.meta.js +// @downloadURL http://zixaphir.github.com/appchan-x/builds/appchan-x.user.js +// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwAgMAAAAqbBEUAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAHFJREFUKFOt0LENACEIBdBv4Qju4wgWanEj3D6OcIVMKaitYHEU/jwTCQj8W75kiVCSBvdQ5/AvfVHBin11BgdRq3ysBgfwBDRrj3MCIA+oAQaku/Q1cNctrAmyDl577tOThYt/Y1RBM4DgOHzM0HFTAyLukH/cmRnqAAAAAElFTkSuQmCC // ==/UserScript== -/* appchan x - Version 2.0.0 - 2013-03-20 +/* appchan x - Version 2.0.0 - 2013-03-28 * http://zixaphir.github.com/appchan-x/ * * Copyright (c) 2009-2011 James Campos @@ -54,16 +54,15 @@ 'Catalog Links': [true, 'Turn Navigation links into links to each board\'s catalog.'], 'External Catalog': [false, 'Link to external catalog instead of the internal one.'], 'Enable 4chan\'s Extension': [false, 'Compatibility between appchan x and 4chan\'s inline extension is NOT guaranteed.'], - 'Custom Board Navigation': [true, 'Disable this to always display the full board list.'], + 'Custom Board Navigation': [true, 'Show custom links instead of the full board list.'], '404 Redirect': [true, 'Redirect dead threads and images.'], 'Keybinds': [true, 'Bind actions to keyboard shortcuts.'], - 'Time Formatting': [true, 'Localize and format timestamps arbitrarily.'], + 'Time Formatting': [true, 'Localize and format timestamps.'], 'Relative Post Dates': [false, 'Display dates like "3 minutes ago". Tooltip shows the timestamp.'], 'File Info Formatting': [true, 'Reformat the file information.'], - 'Comment Expansion': [true, 'Can expand too long comments.'], - 'Thread Expansion': [true, 'Can expand threads to view all replies.'], - 'Index Navigation': [false, 'Navigate to previous / next thread.'], - 'Custom CSS': [false, 'Apply custom CSS to 4chan.'], + 'Comment Expansion': [true, 'Add buttons to expand long comments.'], + 'Thread Expansion': [true, 'Add buttons to expand threads.'], + 'Index Navigation': [false, 'Add buttons to navigate between threads.'], 'Check for Updates': [true, 'Check for updated versions of appchan x.'] }, 'Linkification': { @@ -72,13 +71,13 @@ 'Link Title': [true, 'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud'] }, 'Filtering': { - 'Anonymize': [false, 'Turn everyone Anonymous.'], + 'Anonymize': [false, 'Make everyone Anonymous.'], 'Filter': [true, 'Self-moderation placebo.'], 'Recursive Hiding': [true, 'Hide replies of hidden posts, recursively.'], - 'Thread Hiding': [true, 'Hide entire threads.'], - 'Reply Hiding': [true, 'Hide single replies.'], - 'Hiding Buttons': [true, 'Make buttons to hide threads / replies, in addition to menu links.'], - 'Stubs': [true, 'Make stubs of hidden threads / replies.'] + 'Thread Hiding': [true, 'Add buttons to hide entire threads.'], + 'Reply Hiding': [true, 'Add buttons to hide single replies.'], + 'Hiding Buttons': [true, 'Add buttons to hide threads / replies, in addition to menu links.'], + 'Stubs': [true, 'Show stubs of hidden threads / replies.'] }, 'Images': { 'Image Expansion': [true, 'Expand images.'], @@ -90,7 +89,9 @@ 'Replace JPG': [false, 'Replace jpgs.'] }, 'Menu': { - 'Menu': [true, 'Add a drop-down menu in posts.'], + 'Menu': [true, 'Add a drop-down menu to posts.'], + 'Thread Hiding Link': [true, 'Add a link to hide entire threads.'], + 'Reply Hiding Link': [true, 'Add a link to hide single replies.'], 'Report Link': [true, 'Add a report link to the menu.'], 'Delete Link': [true, 'Add post and image deletion links to the menu.'], 'Download Link': [true, 'Add a download with original filename link to the menu. Chrome-only currently.'], @@ -116,7 +117,7 @@ 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'], 'Hide Original Post Form': [true, 'Hide the normal post form.'] }, - 'Quote links': { + 'Quote Links': { 'Quote Backlinks': [true, 'Add quote backlinks.'], 'OP Backlinks': [false, 'Add backlinks to the OP.'], 'Quote Inlining': [true, 'Inline quoted post on click.'], @@ -238,7 +239,8 @@ filesize: '', MD5: '' }, - sauces: "http://iqdb.org/?url=%turl\nhttp://www.google.com/searchbyimage?image_url=%turl\n#http://tineye.com/search?url=%turl\n#http://saucenao.com/search.php?db=999&url=%turl\n#http://3d.iqdb.org/?url=%turl\n#http://regex.info/exif.cgi?imgurl=%url\n# uploaders:\n#http://imgur.com/upload?url=%url;text:Upload to imgur\n#http://omploader.org/upload?url1=%url;text:Upload to omploader\n# \"View Same\" in archives:\n#//archive.foolz.us/_/search/image/%MD5/;text:View same on foolz\n#//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/\n#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/", + sauces: "http://iqdb.org/?url=%TURL\nhttps://www.google.com/searchbyimage?image_url=%TURL\n#//tineye.com/search?url=%TURL\n#http://saucenao.com/search.php?url=%TURL\n#http://3d.iqdb.org/?url=%TURL\n#http://regex.info/exif.cgi?imgurl=%URL\n# uploaders:\n#http://imgur.com/upload?url=%URL;text:Upload to imgur\n#http://ompldr.org/upload?url1=%URL;text:Upload to ompldr\n# \"View Same\" in archives:\n#//archive.foolz.us/_/search/image/%MD5/;text:View same on foolz\n#//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/\n#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/", + 'Custom CSS': false, 'Header auto-hide': false, 'Header catalog links': false, boardnav: '[ toggle-all ] [current-title]', @@ -292,10 +294,6 @@ mascot: '' }; - if (!/^[a-z]+\.4chan\.org$/.test(location.hostname)) { - return; - } - editTheme = {}; editMascot = {}; @@ -308,7 +306,7 @@ d = document; - doc = null; + doc = d.documentElement; g = { VERSION: '2.0.0', @@ -2913,7 +2911,7 @@ $.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000))); $.extend($, { - engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase(), + engine: 'gecko', id: function(id) { return d.getElementById(id); }, @@ -3208,16 +3206,9 @@ script = $.el('script', { textContent: code }); - $.add(d.head, script); + $.add(d.head || doc, script); return $.rm(script); }, - unsafeWindow: window.opera ? window : typeof unsafeWindow !== "undefined" && unsafeWindow !== null ? unsafeWindow : (function() { - var p; - - p = d.createElement('p'); - p.setAttribute('onclick', 'return window'); - return p.onclick(); - })(), bytesToString: function(size) { var unit; @@ -3228,70 +3219,37 @@ } size = unit > 1 ? Math.round(size * 100) / 100 : Math.round(size); return "" + size + " " + ['B', 'KB', 'MB', 'GB'][unit]; + }, + "delete": function(key) { + var keys, _i, _len; + + if (!(keys instanceof Array)) { + keys = [keys]; + } + for (_i = 0, _len = keys.length; _i < _len; _i++) { + key = keys[_i]; + key = g.NAMESPACE + key; + localStorage.removeItem(key); + GM_deleteValue(key); + } + }, + get: function(key, defaultVal) { + var val; + + if (val = GM_getValue(g.NAMESPACE + key)) { + return JSON.parse(val); + } else { + return defaultVal; + } + }, + set: function(key, val) { + key = g.NAMESPACE + key; + val = JSON.stringify(val); + localStorage.setItem(key, val); + return GM_setValue(key, val); } }); - if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { - $["delete"] = function(name) { - return GM_deleteValue(g.NAMESPACE + name); - }; - $.get = function(name, defaultValue) { - var value; - - if (value = GM_getValue(g.NAMESPACE + name)) { - return JSON.parse(value); - } else { - return defaultValue; - } - }; - $.set = function(name, value) { - name = g.NAMESPACE + name; - value = JSON.stringify(value); - localStorage.setItem(name, value); - return GM_setValue(name, value); - }; - } else if (window.opera) { - (function() { - var scriptStorage; - - scriptStorage = opera.scriptStorage; - $["delete"] = function(name) { - return delete scriptStorage[g.NAMESPACE + name]; - }; - $.get = function(name, defaultValue) { - var value; - - if (value = scriptStorage[g.NAMESPACE + name]) { - return JSON.parse(value); - } else { - return defaultValue; - } - }; - return $.set = function(name, value) { - name = g.NAMESPACE + name; - value = JSON.stringify(value); - localStorage.setItem(name, value); - return scriptStorage[name] = value; - }; - })(); - } else { - $["delete"] = function(name) { - return localStorage.removeItem(g.NAMESPACE + name); - }; - $.get = function(name, defaultValue) { - var value; - - if (value = localStorage.getItem(g.NAMESPACE + name)) { - return JSON.parse(value); - } else { - return defaultValue; - } - }; - $.set = function(name, value) { - return localStorage.setItem(g.NAMESPACE + name, JSON.stringify(value)); - }; - } - Polyfill = { init: function() { return Polyfill.visibility(); @@ -3882,7 +3840,7 @@ }; /* - JSColor + JSColor http://github.com/hotchpotch/jscolor/tree/master JSColor is color library for JavaScript. @@ -4919,7 +4877,7 @@ innerHTML: '[ - ]\u00A0' }); $.on(btn, 'click', Header.toggleBoardList); - return $.prepend(fullBoardList, btn); + return $.add(fullBoardList, btn); } else { $.rm($('#custom-board-list', nav)); return fullBoardList.hidden = false; @@ -4933,8 +4891,8 @@ if (!text) { return; } - as = $$('#full-board-list a', Header.nav); - nodes = text.match(/[\w@]+(-(all|title|full|text:"[^"]+"))?|[^\w@]+/g).map(function(t) { + as = $$('#full-board-list a', Header.nav).slice(0, -2); + nodes = text.match(/[\w@]+(-(all|title|full|index|catalog|text:"[^"]+"))*|[^\w@]+/g).map(function(t) { var a, board, m, _i, _len; if (/^[^\w@]/.test(t)) { @@ -4954,12 +4912,21 @@ a = as[_i]; if (a.textContent === board) { a = a.cloneNode(true); - if (/-title$/.test(t)) { + if (/-title/.test(t)) { a.textContent = a.title; - } else if (/-full$/.test(t)) { + } else if (/-full/.test(t)) { a.textContent = "/" + board + "/ - " + a.title; - } else if (m = t.match(/-text:"(.+)"$/)) { - a.textContent = m[1]; + } else if (/-(index|catalog|text)/.test(t)) { + if (m = t.match(/-(index|catalog)/)) { + a.setAttribute('data-only', m[1]); + a.href = "//boards.4chan.org/" + board + "/"; + if (m[1] === 'catalog') { + a.href += 'catalog'; + } + } + if (m = t.match(/-text:"(.+)"/)) { + a.textContent = m[1]; + } } else if (board === '@') { $.addClass(a, 'navSmall'); } @@ -5107,7 +5074,7 @@ Settings = { init: function() { - var link, settings; + var link, prevVersion, settings; link = $.el('a', { id: 'appchanOptions', @@ -5127,9 +5094,12 @@ return $.prepend($.id('navtopright'), [$.tn(' ['), link, $.tn('] ')]); }); }); - if (!$.get('previousversion')) { + if ((prevVersion = $.get('previousversion', null)) !== g.VERSION) { + $.set('lastupdate', Date.now()); $.set('previousversion', g.VERSION); - $.on(d, '4chanXInitFinished', Settings.open); + if (!prevVersion) { + $.on(d, '4chanXInitFinished', Settings.open); + } } Settings.addSection('Main', Settings.main); Settings.addSection('Filter', Settings.filter); @@ -5167,6 +5137,7 @@ for (_i = 0, _len = _ref.length; _i < _len; _i++) { section = _ref[_i]; link = $.el('a', { + className: "tab-" + section.hyphenatedTitle, textContent: section.title, href: 'javascript:;' }); @@ -5177,12 +5148,8 @@ } } links.pop(); - if (sectionToOpen) { - sectionToOpen.click(); - } else { - links[0].click(); - } $.add($('.sections-list', overlay), links); + (sectionToOpen ? sectionToOpen : links[0]).click(); $.on($('.close', overlay), 'click', Settings.close); $.on(overlay, 'click', Settings.close); $.on(overlay.firstElementChild, 'click', function(e) { @@ -5203,22 +5170,28 @@ }, sections: [], addSection: function(title, open) { - var _ref; + var hyphenatedTitle, _ref; if (typeof title !== 'string') { _ref = title.detail, title = _ref.title, open = _ref.open; } + hyphenatedTitle = title.toLowerCase().replace(/\s+/g, '-'); return Settings.sections.push({ title: title, + hyphenatedTitle: hyphenatedTitle, open: open }); }, openSection: function() { - var section; + var section, selected; + if (selected = $('.tab-selected', Settings.dialog)) { + $.rmClass(selected, 'tab-selected'); + } + $.addClass($(".tab-" + this.hyphenatedTitle, Settings.dialog), 'tab-selected'); section = $('section', Settings.dialog); section.innerHTML = null; - section.className = "section-" + (this.title.toLowerCase().replace(/\s+/g, '-')); + section.className = "section-" + this.hyphenatedTitle; this.open(section, g); return section.scrollTop = 0; }, @@ -5242,7 +5215,7 @@ div = $.el('div', { innerHTML: ": " + description + "" }); - $.on($('input', div), 'click', $.cb.checked); + $.on($('input', div), 'change', $.cb.checked); $.add(fs, div); } $.add(section, fs); @@ -5266,8 +5239,7 @@ }); $.on($('button', div), 'click', function() { this.textContent = 'Hidden: 0'; - $["delete"]("hiddenThreads." + g.BOARD); - return $["delete"]("hiddenPosts." + g.BOARD); + return $["delete"](["hiddenThreads." + g.BOARD, "hiddenPosts." + g.BOARD]); }); return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div); }, @@ -5285,7 +5257,7 @@ className: 'warning', textContent: 'Save me!', download: "appchan x v" + g.VERSION + "-" + now + ".json", - href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data))))), + href: "data:application/json;base64," + (btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))))), target: '_blank' }); if ($.engine !== 'gecko') { @@ -5449,7 +5421,7 @@ $.add(div, ta); return; } - return div.innerHTML = "
Filter is disabled.
\n

\n Use regular expressions, one per line.
\n Lines starting with a # will be ignored.
\n For example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
\n MD5 filtering uses exact string matching, not regular expressions.\n

\n"; + return div.innerHTML = "
Filter is disabled.
\n

\n Use regular expressions, one per line.
\n Lines starting with a # will be ignored.
\n For example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
\n MD5 filtering uses exact string matching, not regular expressions.\n

\n"; }, sauce: function(section) { var sauce; @@ -5462,7 +5434,7 @@ rice: function(section) { var event, input, name, _i, _len, _ref; - section.innerHTML = "
\n Custom Board Navigation is disabled.\n
\n
In the following, board can translate to a board ID (a, b, etc...), the current board (current), or the Status/Twitter link (status, @).
\n
Board link: board
\n
Title link: board-title
\n
Full text link: board-full
\n
Custom text link: board-text:\"VIP Board\"
\n
Full board list toggle: toggle-all
\n
\n\n
\n Time Formatting is disabled.\n
:
\n
Supported format specifiers:
\n
Day: %a, %A, %d, %e
\n
Month: %m, %b, %B
\n
Year: %y
\n
Hour: %k, %H, %l, %I, %p, %P
\n
Minute: %M
\n
Second: %S
\n
\n\n
\n Quote Backlinks formatting is disabled.\n
:
\n
\n\n
\n File Info Formatting is disabled.\n
:
\n
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
\n
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
\n
Spoiler indicator: %p
\n
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
\n
Resolution: %r (Displays 'PDF' for PDF files)
\n
\n\n
\n Unread Tab Icon is disabled.\n \n \n
\n\n
\n Custom CSS is disabled.\n \n \n
"; + section.innerHTML = "
\n Custom Board Navigation is disabled.\n
\n
In the following, board can translate to a board ID (a, b, etc...), the current board (current), or the Status/Twitter link (status, @).
\n
Board link: board
\n
Title link: board-title
\n
Full text link: board-full
\n
Custom text link: board-text:\"VIP Board\"
\n
Index-only link: board-index
\n
Catalog-only link: board-catalog
\n
Combinations are possible: board-index-text:\"VIP Index\"
\n
Full board list toggle: toggle-all
\n
\n\n
\n Time Formatting is disabled.\n
:
\n
Supported format specifiers:
\n
Day: %a, %A, %d, %e
\n
Month: %m, %b, %B
\n
Year: %y
\n
Hour: %k, %H, %l, %I, %p, %P
\n
Minute: %M
\n
Second: %S
\n
\n\n
\n Quote Backlinks formatting is disabled.\n
:
\n
\n\n
\n File Info Formatting is disabled.\n
:
\n
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
\n
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
\n
Spoiler indicator: %p
\n
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
\n
Resolution: %r (Displays 'PDF' for PDF files)
\n
\n\n
\n Unread Tab Icon is disabled.\n \n \n
\n\n
\n Custom CSS\n \n \n
"; _ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss']; for (_i = 0, _len = _ref.length; _i < _len; _i++) { name = _ref[_i]; @@ -5475,6 +5447,7 @@ Settings[name].call(input); } } + $.on($('input[name="Custom CSS"]', section), 'change', Settings.togglecss); return $.on($.id('apply-css'), 'click', Settings.usercss); }, boardnav: function() { @@ -5512,14 +5485,18 @@ if (g.VIEW === 'thread' && Conf['Unread Tab Icon']) { Unread.update(); } - return this.nextElementSibling.innerHTML = " "; + return this.nextElementSibling.innerHTML = "\n\n\n"; + }, + togglecss: function() { + if ($('textarea', this.parentNode.parentNode).disabled = !this.checked) { + CustomCSS.rmStyle(); + } else { + CustomCSS.addStyle(); + } + return $.cb.checked.call(this); }, usercss: function() { - if (Conf['Custom CSS']) { - return CustomCSS.update(); - } else { - return CustomCSS.rmStyle(); - } + return CustomCSS.update(); }, keybinds: function(section) { var arr, input, key, tbody, tr, _ref; @@ -5565,12 +5542,14 @@ } board = g.BOARD.ID; if (board === 'g') { + $.globalEval("window.addEventListener('prettyprint', function(e) {\n var pre = e.detail;\n pre.innerHTML = prettyPrintOne(pre.innerHTML);\n}, false);"); Post.prototype.callbacks.push({ name: 'Parse /g/ code', cb: this.code }); } if (board === 'sci') { + $.globalEval("window.addEventListener('jsmath', function(e) {\n if (jsMath.loaded) {\n // process one post\n jsMath.ProcessBeforeShowing(e.detail);\n } else {\n // load jsMath and process whole document\n jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);\n jsMath.Autoload.LoadJsMath();\n }\n}, false);"); return Post.prototype.callbacks.push({ name: 'Parse /sci/ math', cb: this.math @@ -5586,23 +5565,14 @@ _ref = $$('.prettyprint', this.nodes.comment); for (_i = 0, _len = _ref.length; _i < _len; _i++) { pre = _ref[_i]; - pre.innerHTML = $.unsafeWindow.prettyPrintOne(pre.innerHTML); + $.event('prettyprint', pre, window); } }, math: function() { - var jsMath; - if (this.isClone || !$('.math', this.nodes.comment)) { return; } - jsMath = $.unsafeWindow.jsMath; - if (jsMath) { - if (jsMath.loaded) { - return jsMath.ProcessBeforeShowing(this.nodes.post); - } else { - return $.globalEval("jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);\njsMath.Autoload.LoadJsMath();"); - } - } + return $.event('jsmath', this.nodes.post, window); }, parseThread: function(threadID, offset, limit) { return $.event('4chanParsingDone', { @@ -5921,7 +5891,7 @@ ThreadHiding = { init: function() { - if (g.VIEW !== 'index' || !Conf['Thread Hiding']) { + if (g.VIEW !== 'index' || !Conf['Thread Hiding'] && !Conf['Thread Hiding Link']) { return; } Misc.clearThreads("hiddenThreads." + g.BOARD); @@ -5938,7 +5908,7 @@ if (data = ThreadHiding.hiddenThreads.threads[this]) { ThreadHiding.hide(this, data.makeStub); } - if (!Conf['Hiding Buttons']) { + if (!Conf['Thread Hiding']) { return; } return $.prepend(this.OP.nodes.root, ThreadHiding.makeButton(this, 'hide')); @@ -5975,7 +5945,7 @@ init: function() { var apply, div, makeStub; - if (g.VIEW !== 'index' || !Conf['Menu'] || !Conf['Thread Hiding']) { + if (g.VIEW !== 'index' || !Conf['Menu'] || !Conf['Thread Hiding Link']) { return; } div = $.el('div', { @@ -6111,7 +6081,7 @@ ReplyHiding = { init: function() { - if (g.VIEW === 'catalog' || !Conf['Reply Hiding']) { + if (g.VIEW === 'catalog' || !Conf['Reply Hiding'] && !Conf['Reply Hiding Link']) { return; } Misc.clearThreads("hiddenPosts." + g.BOARD); @@ -6137,7 +6107,7 @@ } } } - if (!Conf['Hiding Buttons']) { + if (!Conf['Reply Hiding']) { return; } return $.add($('.postInfo', this.nodes.post), ReplyHiding.makeButton(this, 'hide')); @@ -6151,7 +6121,7 @@ init: function() { var apply, div, makeStub, replies, thisPost; - if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Reply Hiding']) { + if (g.VIEW === 'catalog' || !Conf['Menu'] || !Conf['Reply Hiding Link']) { return; } div = $.el('div', { @@ -6461,7 +6431,7 @@ QuoteStrikeThrough = { init: function() { - if (g.VIEW === 'catalog' || !Conf['Reply Hiding'] && !Conf['Filter']) { + if (g.VIEW === 'catalog' || !Conf['Reply Hiding'] && !Conf['Reply Hiding Link'] && !Conf['Filter']) { return; } return Post.prototype.callbacks.push({ @@ -6874,7 +6844,7 @@ Settings.open(); break; case Conf['Close']: - if ($.id('settings')) { + if ($.id('fourchanx-settings')) { Settings.close(); } else if ((notifications = $$('.notification')).length) { for (_i = 0, _len = notifications.length; _i < _len; _i++) { @@ -7111,8 +7081,7 @@ } }, focus: function(post) { - $.addClass(post, 'highlight'); - return $('a[title="Highlight this post"]', post).focus(); + return $.addClass(post, 'highlight'); } }; @@ -7741,15 +7710,15 @@ if (data.lastChecked > Date.now() - 12 * $.HOUR) { return; } - return $.ajax("//api.4chan.org/" + g.BOARD + "/catalog.json", { + return $.ajax("//api.4chan.org/" + g.BOARD + "/threads.json", { onload: function() { - var obj, thread, threads, _i, _j, _len, _len1, _ref, _ref1; + var page, thread, threads, _i, _j, _len, _len1, _ref, _ref1; threads = {}; _ref = JSON.parse(this.response); for (_i = 0, _len = _ref.length; _i < _len; _i++) { - obj = _ref[_i]; - _ref1 = obj.threads; + page = _ref[_i]; + _ref1 = page.threads; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { thread = _ref1[_j]; if (thread.no in data.threads) { @@ -8008,7 +7977,7 @@ posts.pop(); for (_i = 0, _len = posts.length; _i < _len; _i++) { post = posts[_i]; - $.addClass(post.nodes.post, 'qphl'); + $.addClass(post.nodes.root, 'qphl'); } } quoterID = $.x('ancestor::*[@id][1]', this).id.match(/\d+$/)[0]; @@ -8043,7 +8012,7 @@ _ref = [post].concat(post.clones); for (_i = 0, _len = _ref.length; _i < _len; _i++) { post = _ref[_i]; - $.rmClass(post.nodes.post, 'qphl'); + $.rmClass(post.nodes.root, 'qphl'); } } }; @@ -8590,16 +8559,13 @@ link = link.replace(/%(T?URL|MD5|board)/ig, function(parameter) { switch (parameter) { case '%TURL': - case '%turl': - return "' + post.file.thumbURL + '"; + return "' + encodeURIComponent(post.file.thumbURL) + '"; case '%URL': - case '%url': - return "' + post.file.URL + '"; + return "' + encodeURIComponent(post.file.URL) + '"; case '%MD5': - case '%md5': return "' + encodeURIComponent(post.file.MD5) + '"; case '%board': - return "' + post.board + '"; + return "' + encodeURIComponent(post.board) + '"; default: return parameter; } @@ -8747,7 +8713,7 @@ rect = thumb.parentNode.getBoundingClientRect(); if (rect.bottom > 0) { postRect = post.nodes.root.getBoundingClientRect(); - headRect = Header.bar.getBoundingClientRect(); + headRect = Header.toggle.getBoundingClientRect(); top = postRect.top - headRect.top - headRect.height - 2; root = $.engine === 'webkit' ? d.body : doc; if (rect.top < 0) { @@ -8814,7 +8780,7 @@ post = Get.postFromNode(this); $.rm(this); delete post.file.fullImage; - if (!$.hasClass(post.file.thumb, 'expanding')) { + if (!($.hasClass(post.file.thumb, 'expanding') || $.hasClass(post.nodes.root, 'expanded-image'))) { return; } ImageExpand.contract(post); @@ -9166,7 +9132,7 @@ if (thread.isSticky) { return 1; } else { - switch (g.BOARD) { + switch (g.BOARD.ID) { case 'b': case 'vg': case 'q': @@ -9232,7 +9198,7 @@ Main.callbackNodes(Post, posts); $.after(a, nodes); if (Conf['Enable 4chan\'s Extension']) { - return $.unsafeWindow.Parser.parseThread(thread.ID, 1, nodes.length); + return $.globalEval("Parser.parseThread(" + thread.ID + ", 1, " + nodes.length + ")"); } else { return Fourchan.parseThread(thread.ID, 1, nodes.length); } @@ -9250,7 +9216,9 @@ }); }, node: function() { - return d.title = Get.threadExcerpt(this); + var excerpt; + + return d.title = (excerpt = Get.threadExcerpt(this)).length > 80 ? "" + excerpt.slice(0, 77) + "..." : excerpt; } }; @@ -9438,35 +9406,35 @@ switch (Conf['favicon']) { case 'ferongr': Favicon.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAOMHAOgLAnMFAL8AAOgLAukMA/+AgP+rq////////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw=='; - Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABEklEQVQ4y6VSu07DQBCcXftQhFNQpEC0UEQpLfEXNFTQ8g0WFY0lyuRPUqRJT8kf5BucCFFhiiAyFNZad+aOFJy0utXN7HMO+OcRc5pCaf55e5AYOcbJDZjvDwFxmCTJaQpl5UDWNblcknXNyoF+taMcI2zHGbfjLCD4wYZHg1/Q2ZA0TG48wyU2HwA8nmi/qGN434lvQwVWI2GpYKngaiRM6ppKUCpoB8CrvecpqXwpm0I5a4MRzqKVK4dfMtn7dDLpO7gSMOjAKi+eauwe7qOjFe9vvd96HapP2i2ek7u5zQWn0tnU+6PBDjYf31g74OYLgAsTrPfEJ7vOL1WQ3IF/+9hdBl4reCF/yGhBMRlT2A8kHPXzaYhj2AAAAABJRU5ErkJggg=='; + Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAS1BMVEUAAAAAAAAAAAAJAAASAAAZAQAaAQAiAQAkAQAoFBQyAgAzAgA1AgA4AABBAgBXAwBzBQCEBgGvCAG/AADoCwLpDAP/gID/q6v///9zILr8AAAAA3RSTlMAx9dmesIgAAAAc0lEQVQY02WPgQ6DIBBDmTqnbE70Cvb/v3TAnW5OSKB9ybXg3HUBOAmEEH4FQtrSn4gxi+xjVC9SVOEiSvbZI8zSV+/Xo7icnryZ15GObMxvtWUkB/VJW57kHU7fUcHStm8FkncGE/mwP6CGzq/eauHwvT7sWQt3gZLW+AAAAABJRU5ErkJggg=='; Favicon.unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAOMHAADX8QBwfgC2zADX8QDY8nnl8qLp8v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw=='; - Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4y6WTv0rDUBTGf7d0aREUNwcHK1UEoQgFwQdwEZw617FrQB2yZHXqA3TNIyh0KN19hJQM2Tq4KEmhHY9Dktub9F47+MEll/N9554/H4F/QulblIq+Xx0qq9qiaWpiMq4K6484NVEqeIH4y0zCr0z8ZSZ4gVSq7dUUAhaZsKgJjOSSN5MburX7R0hiAN5Wu+PrWBLn2skYolSUdT6A0fN2mft4LTJPHeFU6PXzE07FbazrgV5fSgCfZbjptMq0MkqFu46pPLJX9oJdm4r48Xl328FZV6odFJX91xeGP/bJvg+Mopu17rBhtcqGhwGq1Ua12nB5jX0HSQyz99znOuYfyGad/0CdC5w7qHxNbvAk3NwKJ6d/2Fgm2Wx0cL8YR/0BY2szrwAAAABJRU5ErkJggg=='; + Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAASFBMVEUAAAAAAAAAAAAACAkAERMAGBsAGR0AISUALzQALzUAMTcANjwAP0cAVF8AcH4AeokAorYAtswA1/EA2PISIyV55fKi6fL////l+pZqAAAAA3RSTlMAx9dmesIgAAAAcklEQVQY02VPARLCIAxjsjnUWdcg6/9/ukIr00nvIMldEhrC/wHwA0BE3wBUtnICOStQnrNx5oqqzmzKx9vDPH1Nae3F9U4ig3OzjCIX51treYvMxou13EQmBPtHE14xLiawjgoPtfgOaKHP+9VrEXA8O1v7CmSPE3u0AAAAAElFTkSuQmCC'; Favicon.unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAOMHAFT+ACh5AEncAFT+AFX/Acz/su7/5v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw=='; - Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4y6VSPWvCUBQ9L2Qp0iLWLSB1Mkh3oYs/oUscxNGhvyF1rtD/0bGD5Be4dXGra6CLHTvYZKrkdAg3773kpQ698OBy7rlf71zgn6bEiQpQ/FdP46a5OL4E9iubWC/SyokKMIzBHRN+8ZM7Jgxj0Ox2liOEGRVnVBbBTJa4mezJaMMlkKVlwWfcN/YXLEuJ4RLYr8rGyrUfANw+6Y86F6/WMF99gukW7E3KN902441Cdbw3AcUAvAnut0llShkVYNK36nWdncMYDZkEvwmDaoLLkZ7QOqSXdYIHBM7Vjt1D5f986wk9l1QuG8wBv6PgdxSuxhq3CmQp8f7I6h5MO2yAU06cciL/aPl92dt1yoMFeH0HXgR/yChJLhnbYr91gPOcGoNvnQAAAABJRU5ErkJggg=='; + Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAS1BMVEUAAAAAAAAAAAADCgAGEgAIGgAJGwALJAANJwASNwASOAATOgAVQQAWRAAeWwAgKBsoeQAwkQA/wABJ3ABU/gBV/wHM/7Lu/+b////r+K2AAAAAA3RSTlMAx9dmesIgAAAAc0lEQVQY02WPgQ6DIBBDmTonbk70Cvb/v3TAnW5OSKB9ybXg3HUBOAmEEH4FQtrSn4gxi+xjVC9SVOEiSvbZI8zSV+/Xo7icnryZ15GObMxvtWUmB/VJW0byDqfvqGBp20mB5J3Bi3zYH1BD38/eauHwvT7sEAt1Fb320QAAAABJRU5ErkJggg=='; break; case 'xat-': Favicon.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA2ElEQVQ4y61TQQrCMBDMQ8WDIEV6LbT2A4og2Hq0veo7fIAH04dY9N4xmyYlpGmI2MCQTWYy3Wy2DAD7B2wWAzWgcTgVeZKlZRxHNYFi2jM18oBh0IcKtC6ixf22WT4IFLs0owxswXu9egm0Ls6bwfCFfNsJYJKfqoEkd3vgUgFVLWObtzNgVKyruC+ljSzr5OEnBzjvjcQecaQhbZgBb4CmGQw+PoMkTUtdbd8VSEPakcGxPOcsoIgUKy0LecY29BmdBrqRfjIwZ93KLs5loHvBnL3cLH/jF+C/+z5dgUysAAAAAElFTkSuQmCC'; - Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABa0lEQVQ4y8WTv0vDUBDHv6/EoaQ/AsZYHJ06SpUiCP0L/AcsToXSoUPFQdo6iyIIVicpdPJfcIkIrXSwguA/4FpK0yHUhiKkOYfkhTR5Ogne8g7e3efuvvce8N/GAj5x5/K6dToeDTPzT1MGgHhSsbTMxujkqHYWzuUAotBNq3L4rMViCQAYO86sdndfoGhlttRBOGBaLpkAWKrdSYuSAUASJXNLrq2nQ2P6szLvEGoAAHR+AaiqG2IYYI26UD/eAarVSmfPmW/t29JmclVLQ1UZ8nk3bPCKbrFIx7q+mNDiK7ud6+n6E5YA4h0xf4B6t8veDEMCICmKssNDfEA8oViGac1S7Y7ijzAYuJDJBC/Doc+1bXslUuzq5rbpiUnk6kHUbDjUbBAAygLETZblsahhP5kDpuWS6a2SdgOAoOC/avAg2R+emzsAILPIRqMAFsD3Y/F3DngEYHmvhQlA4bdAP9z1vbPwZ7/xG/NNlMkOsFNNAAAAAElFTkSuQmCC'; + Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAdVBMVEUAAAAAAACKkJGNkpN0d3d0eHdra2dGRkORZ1wAAACmaV6naV4PDw8LCwsLCwvyZWLyZWIeExEyFBTAWlr/eHj/enkAAAAKAAAoAAA4AAA4GhpMAACRAAD/AAD/enn/h4j/m5z/nJ3/0dL/0tL/0tP/09P///9VK8WFAAAAFnRSTlMAPnp6kpKdtcHEzc3p6u7v8PT7/v7++jx7+QAAAIFJREFUGNONj90OgjAMhStKmU5k/h1UmAzUvv8jSrYBIeGC9qLtl/a0JVphAJKUOU36xNfWWiitlU9GUphZbXF/hxg10Li2QdQgPhQ3133c9XLOJvD9uZfI0YOdiiMiJw+2CKIPkZzGtcbgKYIJaI26LAfQOzOqoYNA4Z49Nguv/gEEhw2/C5BUZgAAAABJRU5ErkJggg=='; Favicon.unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA30lEQVQ4y2P4//8/AyWYgSoGQMF/GJ7Y11VVUVoyKTM9ey4Ig9ggMWQ1YA1IBvzXm34YjkH8mPyJB+Nqlp8FYRAbmxoMF6ArSNrw6T0Qf8Amh9cFMEWVR/7/A+L/uORxhgEIt5/+/3/2lf//5wAxiI0uj+4CBlBgxVUvOwtydgXQZpDmi2/+/7/0GmIQSAwkB1IDUkuUAZeABlx+g2zAZ9wGlAOjChba+LwAUgNSi2HA5Am9VciBhSsQQWyoWgZiovEDsdGI1QBYQiLJAGQalpSxyWEzAJYWkGm8clTJjQCZ1hkoVG0CygAAAABJRU5ErkJggg=='; - Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABjklEQVQ4y8VTv0tCURT+rtkPU997w8sfFA1NLUWEtlROjQ3O0dQSEvLEIJJmkRoiIXBz6g9oaTECK4ckAwmCIFrzx3Monw9Nnt4G3xN9PpuCznLPPfc7H+d+5xzgv430+FRz4qcnR4VC2fVVrVsBgGUsstvtKArhg6g+VyOgi4n77stzYB3bQvzOZHfYAKAtlWsXccGnxwAgfRXoATuX1U8AJOlnWKNkADAZJWvm5O2sk7ez+riKpUM1AIDYIwVv6QDEOhDxEkP9zNotsLuXlPnVJbN3c87B21neArLiAggFsiVg6zxNU2fhVqta+fYszN9e36TQRzCsRZpK6cQhEd+ezADMHMd5NEyXgGEsslQTa0k/w2lfyBY7JJU68PHy0CVWFGV0gGBm2l08DglBTcyIlyCSoRQAia0RjDtne4tTBgiCof2ovhOlilRVXXZk0taNy7I8NUBgZEru6l11lznPBkxjEwMYkz6gDgkAwCpm8lYxkwcA6TWHdrOBdrPx625Qo5noiWXU0/dn2/gDsiiJvxnPWcEAAAAASUVORK5CYII='; + Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAdVBMVEUAAAAAAACRjoqTkI13dXR4dXRpZ2tFQ0Zcb5EAAABee6ZefKcPDw8LCwsLCwtisPJisPIRFh4UJDJalMB4xP95xP8AAAABBQcHFx4KISoNLToaKzgaVW4ul8N5xP+Hy/+b1P+c1P/R7P/S6//S7P/T7P////9P0rk0AAAAFnRSTlMAPnp6kpKdtcHEzc3p6u7v8PT7/v7++jx7+QAAAIFJREFUGNONj90OgjAMhStKmU5k/h1UmAzUvv8jSrYBIeGC9qLtl/a0JVphAJKUOU36xNfWWiitlU9GUphZbXF/hxg10Li2QdQgPhQ3133c9XLOJvD9uUrk6MFOxRGRkwdbBNGHSE7jWmPwFMEEtEZdlgPonRmvoYNA4Z49Nguv/gEE3A2/sQ7iRgAAAABJRU5ErkJggg=='; Favicon.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA4ElEQVQ4y2P4//8/AyWYgSoGQMF/GJ7YNbGqrKRiUnp21lwQBrFBYshqwBqQDPifdsYYjkH8mInxB+OWx58FYRAbmxoMF6ArKPmU9B6IP2CTw+sCmKKe/5X/gPg/LnmcYQDCs/63/1/9fzYQzwGz0eXRXcAACqy4ZfFnQc7u+V/xD6T55v+LQHwJbBBIDCQHUgNSS5QBt4Cab/2/jDDgMx4DykrKJ8FCG58XQGpAajEMmNw7uQo5sHAFIogNVctATDR+IDYasRoAS0gkGYBMw5IyNjlsBsDSAjKNV44quREAx58Mr9vt5wQAAAAASUVORK5CYII='; - Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABi0lEQVQ4y8WTzysEYRjHP7M/MI3dGVp2RXJwohysOIhyEGdnpRxWkTaRLGeU1mFzcyAl/4Gbg7QHisIdJSy7CsO0B7tel5mJ2eGkPJf3eZ/3+3zf5yf8t0hfdGEpqZXUwm02E3nJ6wqAKgeN+tq6+/hsfNHpaxGI2HHUflnvOGE4NXLgqaUS4CPL23Z8q9eJAaRvETgBM/roMyAlgxuqmzOAx83ZkkggrEYCYdVpN7HixxoArItlqggBEk/kiEkJ1/p5rdvYxHhj61SL6N+MykNLA+URGqQ2OgnTgM4Tob6AuDnPFP3+8nx3V8/e1cXlDoDv9xZJdpC7c/tS5iTnA3yapnVYGJtAlQOGkTPeksENzUrhjCMzhUeuD+9s4kKh4C/5bW11bd4spogdRwUgkiLxkRQJAQitqUJYoihKtiSCyenJRWcn7l8fdCvAskq7XBiGUVNC4CbZ3eKFqbY3D1bjl70lGI/TYA4JAHJaOZXTyinAzaHOe77Ie774624It5n4YkubZ++fbeMnwHeVVSmTml8AAAAASUVORK5CYII='; + Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAdVBMVEUAAAAAAACRipGTjZN2dHd2dHhna2pDRkVckV8AAABepl9ep18PDw8LCwsLCwt08mJ08mIRHhEYMhRpwFqM/3iM/3kAAAAECAIQIAgWLAseOBoePA86dB1mzDOM/3ma/4er/5ur/5zZ/9HZ/9La/9La/9P///85Jx7jAAAAFnRSTlMAPnp6kpKdtcHEzc3p6u7v8PT7/v7++jx7+QAAAIFJREFUGNONj90OgjAMhStKmU5k/h1UmAzUvv8jSrYBIeGC9qLtl/a0JVphAJKUOU36xNfWWiitlU9GUphZbXF/hxg10Li2QdQgPhQ3133c9XLOJvD9uZfI0YOdiiMiJw+2CKIPkZzGtcbgKYIJaI26LAfQOzOqoYNA4Z49Nguv/gEEhw2/C5BUZgAAAABJRU5ErkJggg=='; break; case 'Mayhem': Favicon.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4jZ2ScWuDMBDFgw4pIkU0WsoQkWAYIkXZH4N9/+/V3dmfXSrKYIFHwt17j8vdGWNMIkgFuaDgzgQnwRs4EQs5KdolUQtagRN0givEDBTEOjgtGs0Zq8F7cKqqusVxrMQLaDUWcjBSrXkn8gs51tpJSWLk9b3HUa0aNIL5gPBR1/V4kJvR7lTwl8GmAm1Gf9+c3S+89qBHa8502AsmSrtBaEBPbIbj0ah2madlNAPEccdgJDfAtWifBjqWKShRBT6KoiH8QlEUn/qt0CCjnNdmPUwmFWzj9Oe6LpKuZXcwqq88z78Pch3aZU3dPwwc2sWlfZKCW5tWluV8kGvXClLm6dYN4/aUqfCbnEOzNDGhGZbNargvxCzvMGfRJD8UaDVvgkzo6QAAAABJRU5ErkJggg=='; Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABj0lEQVQ4y42TQUorQRCGv+oekpj43pOhOyIiKoHBxTMkuAnEtWcwx/AY3sUbBIRcwCw8gCfIMkaTOOUiNdgGRRuKoav+v2qq/i4BakBmXweUwDoxLF5ZhVkC64rYBHYMUAIvwKuBMEwdaFiCNbAAngEC0NHkxBi73vsOsG92HGPsphigY1wOzfNhqhpC6AEd730RQuh9hQEOAY6A/jeAs3a7/f+bWB84ckCpqg+I8Osjgqo+AKUDViJS8LkGMcY+sJrNZssYY387LiIFsBLgL9AC/pgaArzZlF+sZgO4BG7sfgvcA3MxUtOStBIpX7cS3Klqd9OBTIEr4DlLOsuAmqpODXQOiHMuy/O8FkLoJth/6Uh2gQPg87Q3k+7leX6hqnpmPvM/GWfXWeWGqj5+oUS9LMs6wF7iHAwGJ9ZW5uxpup+UGwEtEVoijEYjKl66PJujmvIW3vsFwBiYqzJXZTweY5wSU6Bd7UP1KoECODUrJpOJAtPhcKjAtXGaYptWs57qWyv9Zn/it1a5knj5Dm3v4q8APeACAAAAAElFTkSuQmCC'; Favicon.unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCElEQVQ4jZ2S4crCMAxF+0OGDJEPKYrIGKOsiJSx/fJRfSAfTJNyKqXfiuDg0C25N2RJjTGmEVrhTzhw7oStsIEtsVzT4o2Jo9ALThiEM8IdHIgNaHo8mjNWg6/ske8bohPo+63QOLzmooHp8fyAICBSQkVz0QKdsFQEV6WSW/D+7+BbgbIDHcb4Kp61XyjyI16zZ8JemGltQtDBSGxB4/GoN+7TpkkjDCsFArm0IYv3U0BbnYtf8BCy+JytsE0X6VyuKhPPK/GAJ14kvZZDZVV3pZIb8MZr6n4o4PDGKn0S5SdDmyq5PnXQsk+Xbhinp03FFzmHJw6xYRiWm9VxnohZ3vOcxdO8ARmXRvbWdtzQAAAAAElFTkSuQmCC'; - Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABlklEQVQ4y4WTTWobQRCFv2qNRiP5J1mJLAzBJMiCYBubLATy0uQM1jYrb30M38CHyA20EngZPAhhCEaXMIkjWUjzvKkWzWzcUPRM13vVXfWqDMiBzPcAVMA6MdwfLWJWwDoSO0DbARWwBF4dhGNaQOEB1sAC+J/54Z6kuYMxs2/AiwfCMTuSHhPMV6DKgJak+endffQh6dHMjv1FAG1JsxpmbmYHAQhmdj69vtg6T+/ukTQDmkCzTp5eX2Bm50AIQCXpIQW8t/yCB6AyoAt8kPSUgjyFRZpCzd8DnjMvVMPMjlwNAzY1FYKZ/QRu/P8W+AcszUkdYMctSvlaU+GXpBO/vQR+RBlJmiWXVCYpWOyDfr9/kmD3U1LwarcllbGYkmZmdubfZfHpc1qCyAnBgxR1qXy13Gh0dreHg8Hg0NPKgkcL70n38fslIS8IecFoNIqvCOnwbJskWYso5d8/v6lWS6rVkvF4jHMqXIEu8EWSgDPfe8ChW28ymQgoh8OhgCvndMwnLfecWrWR3vhLGrVRjhKv3gDhOKP2kgPZ3gAAAABJRU5ErkJggg=='; + Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAkFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBQcHFx4KISoNLToaVW4oKCgul8M4ODg7OztMTEyRkZHBwcH///9dzWZ0AAAAI3RSTlMEBggKDA4QEhQWFxkbHR8hIyUmKCosLjAxN1hbYc7P0dLc3mzWzBUAAAC+SURBVBjTNY3pcsIwEIM3ePERx/bG5IIe0NIrhVbv/3Y4Ydj9Ic030ogqpY3mDdGGi1EVsYuSvGE2Pkl0TFYAdLGuY1eMWGowzzN6kX41DYVpNbvdKlO4Jx5gSbi2VO+Vcq2jrc/jNLQhtM+n05PfkrKxG/oFHIEXqwqQsVRy7n+AtwLYL3sYR3wA755Jp3Vvv8cn8Js0GXmA7/P5TwzpiLn8MOALuEZNygkm5JTy/+vl4BRVbJvQ1NbWRSxXN64PGOBlhG0qAAAAAElFTkSuQmCC'; Favicon.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCklEQVQ4jZ2S0WrDMAxF/TBCCKWMYhZKCSGYmFJMSNjD/mhf239qJXNcjBdTWODgRLpXKJKNMaYROuFTOHEehFb4gJZYrunwxsSXMApOmIQzwgOciE1oRjyaM1aDj+yR7xuiHvT9VmgcXnPRwO/9+wWCgEgJFc1FCwzCVhFclUpuw/u3g3cFyg50GPOjePZ+ocjPeM2RCXthpbUFwQAzsQ2Nx6PeuE+bJo0w7BQI5NKGLN5XAW11LX7BQ8jia7bCLl2kc7mqTLzuxAOeeJH0Wk6VVf0oldyEN15T948CDm+sMiZRfjK0pZIbUwcd+3TphnF62lR8kXN44hAbhmG5WQNnT8zynucsnuYJhFpBfkMzqD4AAAAASUVORK5CYII='; - Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABlklEQVQ4y4WTv04bQRDGf7N3/nMmCSIFVRoLZCxFQQJTWHKP8gi4paHOY/AIvANv4ModBXCFRRO5zAskSnL8se+jmbVW13il0d7tfN/sznwzBrSB3PcA1MAqMdwfLWJegVUk9oDCATXwDLw4CMd0gK4HWAEV8D/3w4+Slg7GzL4C/zwQjtmR9JRgDoE6BzqSllcPZ9GHpCcz++YvAigkLRqYpZl9CUAws9Ob0f3GefVwhqQF0AJaTfLN6B4zOwVCAGpJjylg2/ILHoHagH1gV9LPFOQpVGkKDf8A+J17oTIzO3I1DFg3VAhmdgn88P9r4C/wbE7qATtuUcqXhgq3ko799hI4jzKSNEtbUpmkYLEPhsPhcYL9lJKCV7uQVMZiSlqY2Yl/l3v9Ii1B5ITgQbpNqXx13Gh/yDaH4/G472nlwaOFbdIdfv9Mq8hoFRnT6TS+IqTDs2mSZFVRyl93f3ir1rxVa2azGc6pcQX2gQNJAk58HwB9t8F8PhdQTiYTARfO6ZlPWttz6jRGeu0vyRqjHCV+fQf4OaM8g/XFLAAAAABJRU5ErkJggg=='; + Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAkFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAECAIQIAgWLAsePA8oKCg4ODg6dB07OztMTExmzDORkZHBwcH///92I3mvAAAAI3RSTlMEBggKDA4QEhQWFxkbHR8hIyUmKCosLjAxN1hbYc7P0dLc3mzWzBUAAAC+SURBVBjTNY3pcsIwEIM3ePERx/bG5IIe0NIT0ur93w4nDLs/pPlGGlGltNG8IdpwMaoidlGSN8zGJ4mOyQqALtZ17IoRSw3meUYv0q+moTCtZrdbZQr3xAMsCdeW6r1SrnW09XmchjaE9vl0evJbUjZ2Q7+AI/BiVQEylkrO/TfwVgD7ZQ/jiA/g3TPptO7t9/gEfpImIw/wez7/iSEdMZcfBnwB16hJOcGEnFL+f70cnKKKbROa2tq6iOXqBuMXGTe4CAUbAAAAAElFTkSuQmCC'; break; case 'Original': Favicon.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; - Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAsUlEQVQ4y6VTPQ6DIQh9NG5degyPQOJJetZOTj1R5/ctYggVbVISEoTHPwJ/kphAklMpIitwihmGyR54wghJiggYyiI5s2wxJ8AoN01wWzmfiBbUMsT+4hxO9gnyFLP23qmqVFX23vOCswCq6oO/TV+is613yBL1gx7ZkZCDfVcAWN271sqt8yqAhre1WX5d3RPAfXHhZfU5ViN+Afi4w8pnEEr0ttYaAeRrNKfNZ/qyXeAkApbmVGieAAAAAElFTkSuQmCC'; + Favicon.unreadDeadY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAALVBMVEUAAAAAAAAAAAAAAAAKAAAoAAAoKCg4AAA4ODg7OztMAACRAADBwcH/AAD///+WCcPSAAAAA3RSTlMAx9dmesIgAAAAZ0lEQVQI1z2LsQmAUAxEb4Isk0rwp3EPR3ECcRQrh7C3/nAasPwzmCgYuPBy5AH/NALSImqAK+H1oJRqyJVHNAnZqDITVhj7/PrAciJ9il0BHs/jjU+fnB9sQ0IxX6OBO6Xr0xKAxANLZzUanCWzZQAAAABJRU5ErkJggg=='; Favicon.unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; - Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA3UlEQVQ4y6VTMQ6DMAw8U9RWVQe2bp0zdepgCbH1M7yI90QMXTox5R9dEAPuQqIoxKVST0KKzueL8QHwJ8gfREQCSUQ5sapZCuGJhVuaUkSEiHDrekTuWHjyzapGEwxtAz+IViMiKnLNW7h1fZgg+37pHrbqQRQjvdVaK8wszCzWWlHH0wyYOTZ/er5Mm328uRQiVCuDdJnxkogIh8s1dBtjjHMOAFBoMabYnc7h7JwL5uWv0VX3B4r9ccUXKTG0Tdbg7V6YpxHzNOrbj/LNfgd1XQsAPUbf9OVnWtU+UtzonSagmcwAAAAASUVORK5CYII='; + Favicon.unreadSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAALVBMVEUAAAAAAAAAAAAAAAABBQcHFx4KISoNLToaVW4oKCgul8M4ODg7OzvBwcH///8uS/CdAAAAA3RSTlMAx9dmesIgAAAAZ0lEQVQI1z2LsQ2AUAhEbwKWoftRGvdwBEewchM7d9BFbE6pbP4Mgj+R5MjjwgP+qQSkRtQAV8K3lVI2Q648oknIRpWZsMI4988HjgvpU+wO8HgeHzR9cjZYhoRiPkcDd0rXpyUAiRd5YjKC7MvNRgAAAABJRU5ErkJggg=='; Favicon.unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; - Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA30lEQVQ4y6VTMQ6CQBCcBTSaWBBfYGFBD8UmfMCX8Ch+c5UW0N8DfIGNBSFrw10ux62YOAnJZXZ2btkB4E+QO4iIeJKIUmJVsxT8Ewq3NIWICBGhG2oE7lh4cs2qRhP0zQg3iFYjIspSzVvohtpPkHy/eA9bdS8KEd9qjBFmFmYWY4yo42kGzBya3x1fxM0u3lQKAcqVQbzMcElEhPJy8N1VVVXWWgBApsUYY3/K/dla682LX6O73s7YHfMVn8VE34xJg+fjhek9Y3rP+vaDfJPfQdu2AkCP0TV9+ZlWtQ9lu+fiaucJAgAAAABJRU5ErkJggg=='; + Favicon.unreadNSFWY = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAALVBMVEUAAAAAAAAAAAAAAAAECAIQIAgWLAsePA8oKCg4ODg6dB07OztmzDPBwcH///+rsf3XAAAAA3RSTlMAx9dmesIgAAAAZ0lEQVQI1z2LsQ2AUAhEbwKWofRL4x6O4AhuopWb2P4F7E5prP4MgiaSHHlceMA/jYC0iBrgSnjdKaUacuURTUI2qsyEFcaxvD6wnkifYleAx/N449Mn5wfbkFDM52jgTun6tAQg8QAEvjQg42KY2AAAAABJRU5ErkJggg=='; } if (Favicon.SFW) { Favicon.unread = Favicon.unreadSFW; @@ -9859,9 +9827,9 @@ var length, threadID; threadID = ThreadUpdater.thread.ID; - length = ThreadUpdater.root.children.length; + length = $$('.thread > .postContainer', ThreadUpdater.root).length; if (Conf['Enable 4chan\'s Extension']) { - return $.unsafeWindow.Parser.parseThread(threadID, -count); + return $.globalEval("Parser.parseThread(" + threadID + ", " + (-count) + ")"); } else { return Fourchan.parseThread(threadID, length - count, length); } @@ -9909,6 +9877,9 @@ } }, ready: function() { + if (!Main.isThisPageLegit()) { + return; + } ThreadWatcher.refresh(); return $.add(d.body, ThreadWatcher.dialog); }, @@ -10899,6 +10870,9 @@ var applyBlob, cv, data, height, i, l, s, ui8a, width, _i; s = 90 * 2; + if (_this.file.type === 'image/gif') { + s *= 3; + } height = img.height, width = img.width; if (height < s || width < s) { if (window.URL) { @@ -11051,8 +11025,16 @@ }), this.ready.bind(this)); }, ready: function() { - var MutationObserver, imgContainer, input, observer; + var MutationObserver, imgContainer, input, observer, setLifetime, + _this = this; + setLifetime = function(e) { + return _this.lifetime = e.detail; + }; + $.on(window, 'captcha:timeout', setLifetime); + $.globalEval('window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))'); + $.off(window, 'captcha:timeout', setLifetime); + c.log(this.lifetime); imgContainer = $.el('div', { className: 'captcha-img', title: 'Reload', @@ -11153,7 +11135,7 @@ if (!this.nodes.challenge.firstChild) { return; } - this.timeout = Date.now() + $.unsafeWindow.RecaptchaState.timeout * $.SECOND - $.MINUTE; + this.timeout = Date.now() + this.lifetime * $.SECOND - $.MINUTE; challenge = this.nodes.challenge.firstChild.value; this.nodes.img.alt = challenge; this.nodes.img.src = "//www.google.com/recaptcha/api/image?c=" + challenge; @@ -11177,7 +11159,7 @@ return this.nodes.input.alt = count; }, reload: function(focus) { - $.unsafeWindow.Recaptcha.reload('t'); + $.globalEval('Recaptcha.reload("t")'); if (focus) { return this.nodes.input.focus(); } @@ -11196,7 +11178,7 @@ dialog: function() { var dialog, mimeTypes, name, node, nodes, thread, _i, _j, _len, _len1, _ref, _ref1; - dialog = UI.dialog('qr', 'top:0;right:0;', "
\n \n \n ×\n
\n
\n
\n \n \n \n \n
\n
\n \n \n
\n
\n
\n +\n
\n
\n \n \n No selected file\n \n \n ×\n \n \n
\n \n
\n".replace(/>\s+<')); + dialog = UI.dialog('qr', 'top:0;right:0;', "
\n \n \n ×\n
\n
\n
\n \n \n \n \n
\n
\n \n \n
\n
\n
\n +\n
\n
\n \n \n \n No selected file\n \n \n ×\n \n
\n \n
\n".replace(/>\s+<')); QR.nodes = nodes = { el: dialog, move: $('.move', dialog), @@ -11366,7 +11348,9 @@ post.unlock(); QR.cooldown.auto = false; QR.status(); - return QR.error('Network error.'); + return QR.error($.el('span', { + innerHTML: 'Connection error. You may have been banned.' + })); } }; opts = { @@ -11390,7 +11374,7 @@ return QR.status(); }, response: function() { - var URL, ban, board, err, h1, persona, post, postID, req, threadID, tmpDoc, _, _base, _ref, _ref1; + var URL, ban, board, err, h1, isReply, persona, post, postID, req, threadID, tmpDoc, _, _base, _ref, _ref1; req = QR.req; delete QR.req; @@ -11441,6 +11425,7 @@ _ref1 = h1.nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = _ref1[0], threadID = _ref1[1], postID = _ref1[2]; postID = +postID; threadID = +threadID || postID; + isReply = threadID !== postID; ((_base = QR.yourPosts.threads)[threadID] || (_base[threadID] = [])).push(postID); $.set("yourPosts." + g.BOARD, QR.yourPosts); ThreadUpdater.postID = postID; @@ -11449,16 +11434,16 @@ threadID: threadID, postID: postID }, QR.nodes.el); - QR.cooldown.auto = QR.posts.length > 1; + QR.cooldown.auto = QR.posts.length > 1 && isReply; post.rm(); QR.cooldown.set({ req: req, post: post, - isReply: !!threadID + isReply: isReply }); if (threadID === postID) { URL = "/" + g.BOARD + "/res/" + threadID; - } else if (g.VIEW === 'index' && !QR.cooldown.auto) { + } else if (g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab']) { URL = "/" + g.BOARD + "/res/" + threadID + "#p" + postID; } if (URL) { @@ -11498,7 +11483,7 @@ field = $.id('recaptcha_response_field'); $.on(field, 'keydown', function(e) { if (e.keyCode === 8 && !field.value) { - return $.unsafeWindow.Recaptcha.reload('t'); + return $.globalEval('Recaptcha.reload("t")'); } }); return $.on(form, 'submit', function(e) { @@ -11602,9 +11587,9 @@ this.nodes.uniqueID = uniqueID; this.info.uniqueID = uniqueID.firstElementChild.textContent; } - if (capcode = $('.capcode', info)) { + if (capcode = $('.capcode.hand', info)) { this.nodes.capcode = capcode; - this.info.capcode = capcode.textContent; + this.info.capcode = capcode.textContent.replace('## ', ''); } if (flag = $('.countryFlag', info)) { this.nodes.flag = flag; @@ -11887,11 +11872,6 @@ init: function() { var flatten, initFeatures, key, pathname, val; - $.asap((function() { - return d.documentElement; - }), function() { - return doc = d.documentElement; - }); flatten = function(parent, obj) { var key, val; @@ -12079,7 +12059,8 @@ Main.callbackNodes(Thread, threads); Main.callbackNodes(Post, posts); } - return $.event('4chanXInitFinished'); + $.event('4chanXInitFinished'); + return Main.checkUpdate(); }, callbackNodes: function(klass, nodes) { var callback, err, errors, i, len, node, _i, _j, _len, _ref; @@ -12112,10 +12093,56 @@ var Klass, obj; obj = e.detail; - Klass = obj.type === 'Post' ? Post : Thread; + if (typeof obj.callback.name !== 'string') { + throw new Error("Invalid callback name: " + obj.callback.name); + } + switch (obj.type) { + case 'Post': + Klass = Post; + break; + case 'Thread': + Klass = Thread; + break; + default: + return; + } obj.callback.isAddon = true; return Klass.prototype.callbacks.push(obj.callback); }, + checkUpdate: function() { + var freq, now; + + if (!Main.isThisPageLegit()) { + return; + } + now = Date.now(); + freq = 7 * $.DAY; + if ($.get('lastupdate', 0) > now - freq || $.get('lastchecked', 0) > now - $.DAY) { + return; + } + return $.ajax('http://zixaphir.github.com/appchan-x/builds/version', { + onload: function() { + var el, version; + + if (this.status !== 200) { + return; + } + version = this.response; + if (!/^\d\.\d+\.\d+$/.test(version)) { + return; + } + if (g.VERSION === version) { + $.set('lastupdate', now); + return; + } + $.set('lastchecked', now); + el = $.el('span', { + innerHTML: "Update: appchan x v" + version + " is out, get it here." + }); + return new Notification('info', el, 2 * $.MINUTE); + } + }); + }, handleErrors: function(errors) { var div, error, logs, _i, _len; @@ -12129,7 +12156,7 @@ return; } div = $.el('div', { - innerHTML: "" + errors.length + " errors occured. [show]" + innerHTML: "" + errors.length + " errors occurred. [show]" }); $.on(div.lastElementChild, 'click', function() { if (this.textContent === 'show') { @@ -12164,8 +12191,10 @@ return [message, error]; }, isThisPageLegit: function() { + var _ref; + if (!('thisPageIsLegit' in Main)) { - Main.thisPageIsLegit = !$('link[href*="favicon-status.ico"]', d.head) && d.title !== '4chan - Temporarily Offline'; + Main.thisPageIsLegit = location.hostname === 'boards.4chan.org' && !$('link[href*="favicon-status.ico"]', d.head) && ((_ref = d.title) !== '4chan - Temporarily Offline' && _ref !== '4chan - Error'); } return Main.thisPageIsLegit; } diff --git a/changelog b/changelog-old similarity index 100% rename from changelog rename to changelog-old diff --git a/css/style.css b/css/style.css new file mode 100644 index 000000000..ae66b6610 --- /dev/null +++ b/css/style.css @@ -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; +} diff --git a/img/favicons/Mayhem/unreadNSFWY.png b/img/favicons/Mayhem/unreadNSFWY.png index a4b95550d..16180b91c 100644 Binary files a/img/favicons/Mayhem/unreadNSFWY.png and b/img/favicons/Mayhem/unreadNSFWY.png differ diff --git a/img/favicons/Mayhem/unreadSFWY.png b/img/favicons/Mayhem/unreadSFWY.png index 7b5b0ee60..3f48cf5c2 100644 Binary files a/img/favicons/Mayhem/unreadSFWY.png and b/img/favicons/Mayhem/unreadSFWY.png differ diff --git a/img/favicons/Original/unreadDeadY.png b/img/favicons/Original/unreadDeadY.png index f0a1bdab4..f5f41b903 100644 Binary files a/img/favicons/Original/unreadDeadY.png and b/img/favicons/Original/unreadDeadY.png differ diff --git a/img/favicons/Original/unreadNSFWY.png b/img/favicons/Original/unreadNSFWY.png index f005e187c..fb562d564 100644 Binary files a/img/favicons/Original/unreadNSFWY.png and b/img/favicons/Original/unreadNSFWY.png differ diff --git a/img/favicons/Original/unreadSFWY.png b/img/favicons/Original/unreadSFWY.png index 881821f9f..eaf49a8e3 100644 Binary files a/img/favicons/Original/unreadSFWY.png and b/img/favicons/Original/unreadSFWY.png differ diff --git a/img/favicons/ferongr/unreadDeadY.png b/img/favicons/ferongr/unreadDeadY.png index 39c27dcec..e8008c9c6 100644 Binary files a/img/favicons/ferongr/unreadDeadY.png and b/img/favicons/ferongr/unreadDeadY.png differ diff --git a/img/favicons/ferongr/unreadNSFWY.png b/img/favicons/ferongr/unreadNSFWY.png index 4f4fa0979..35c59ddc2 100644 Binary files a/img/favicons/ferongr/unreadNSFWY.png and b/img/favicons/ferongr/unreadNSFWY.png differ diff --git a/img/favicons/ferongr/unreadSFWY.png b/img/favicons/ferongr/unreadSFWY.png index f4397364b..4ab87b0c4 100644 Binary files a/img/favicons/ferongr/unreadSFWY.png and b/img/favicons/ferongr/unreadSFWY.png differ diff --git a/img/favicons/xat-/unreadDeadY.png b/img/favicons/xat-/unreadDeadY.png index dab4041c2..b18f8140c 100644 Binary files a/img/favicons/xat-/unreadDeadY.png and b/img/favicons/xat-/unreadDeadY.png differ diff --git a/img/favicons/xat-/unreadNSFWY.png b/img/favicons/xat-/unreadNSFWY.png index 6e5802597..4ee06355c 100644 Binary files a/img/favicons/xat-/unreadNSFWY.png and b/img/favicons/xat-/unreadNSFWY.png differ diff --git a/img/favicons/xat-/unreadSFWY.png b/img/favicons/xat-/unreadSFWY.png index aa0b53c66..4e6c1b4be 100644 Binary files a/img/favicons/xat-/unreadSFWY.png and b/img/favicons/xat-/unreadSFWY.png differ diff --git a/img/icon128.png b/img/icon128.png new file mode 100644 index 000000000..e75a8f86b Binary files /dev/null and b/img/icon128.png differ diff --git a/img/icon16.png b/img/icon16.png new file mode 100644 index 000000000..a1d2a71e8 Binary files /dev/null and b/img/icon16.png differ diff --git a/img/icon48.png b/img/icon48.png new file mode 100644 index 000000000..227f681ed Binary files /dev/null and b/img/icon48.png differ diff --git a/lib/$.coffee b/lib/$.coffee index b05e86f99..50b612144 100644 --- a/lib/$.coffee +++ b/lib/$.coffee @@ -51,7 +51,7 @@ $.extend String::, $.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000))) $.extend $, - engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase() + engine: '<% if (type === 'crx') { %>webkit<% } else if (type === 'userjs') { %>presto<% } else { %>gecko<% } %>' id: (id) -> d.getElementById id ready: (fc) -> @@ -227,19 +227,8 @@ $.extend $, globalEval: (code) -> script = $.el 'script', textContent: code - $.add d.head, script + $.add (d.head or doc), script $.rm script - # http://mths.be/unsafewindow - unsafeWindow: - if window.opera # Opera - window - else if unsafeWindow? # Firefox - unsafeWindow - else # Chrome - do -> - p = d.createElement 'p' - p.setAttribute 'onclick', 'return window' - p.onclick() bytesToString: (size) -> unit = 0 # Bytes while size >= 1024 @@ -256,50 +245,65 @@ $.extend $, Math.round size "#{size} #{['B', 'KB', 'MB', 'GB'][unit]}" -if GM_deleteValue? - $.delete = (name) -> - GM_deleteValue g.NAMESPACE + name - $.get = (name, defaultValue) -> - if value = GM_getValue g.NAMESPACE + name - JSON.parse value +<% if (type === 'crx') { %> + delete: (keys) -> + chrome.storage.sync.remove keys + get: (key, defaultVal) -> + if val = localStorage.getItem g.NAMESPACE + key + JSON.parse val else - defaultValue - $.set = (name, value) -> - name = g.NAMESPACE + name - value = JSON.stringify value + defaultVal + set: (key, val) -> + item = {} + item[key] = val + chrome.storage.sync.set item +<% } else if (type === 'userjs') { %> +do -> + # http://www.opera.com/docs/userjs/specs/#scriptstorage + # http://www.opera.com/docs/userjs/using/#securepages + # The scriptStorage object is available only during + # the main User JavaScript thread, being therefore + # accessible only in the main body of the user script. + # To access the storage object later, keep a reference + # to the object. + {scriptStorage} = opera + $.delete = (keys) -> + unless keys instanceof Array + keys = [keys] + for key in keys + key = g.NAMESPACE + key + localStorage.removeItem key + delete scriptStorage[key] + return + $.get = (key, defaultVal) -> + if val = scriptStorage[g.NAMESPACE + key] + JSON.parse val + else + defaultVal + $.set = (key, val) -> + key = g.NAMESPACE + key + val = JSON.stringify val # for `storage` events - localStorage.setItem name, value - GM_setValue name, value -else if window.opera - do -> - # http://www.opera.com/docs/userjs/specs/#scriptstorage - # http://www.opera.com/docs/userjs/using/#securepages - # >The scriptStorage object is available only during - # the main User JavaScript thread, being therefore - # accessible only in the main body of the user script. - # To access the storage object later, keep a reference - # to the object. - {scriptStorage} = opera - $.delete = (name) -> - delete scriptStorage[g.NAMESPACE + name] - $.get = (name, defaultValue) -> - if value = scriptStorage[g.NAMESPACE + name] - JSON.parse value - else - defaultValue - $.set = (name, value) -> - name = g.NAMESPACE + name - value = JSON.stringify value - # for `storage` events - localStorage.setItem name, value - scriptStorage[name] = value -else - $.delete = (name) -> - localStorage.removeItem g.NAMESPACE + name - $.get = (name, defaultValue) -> - if value = localStorage.getItem g.NAMESPACE + name - JSON.parse value + localStorage.setItem key, val + scriptStorage[key] = val +<% } else { %> + delete: (key) -> + unless keys instanceof Array + keys = [keys] + for key in keys + key = g.NAMESPACE + key + localStorage.removeItem key + GM_deleteValue key + return + get: (key, defaultVal) -> + if val = GM_getValue g.NAMESPACE + key + JSON.parse val else - defaultValue - $.set = (name, value) -> - localStorage.setItem g.NAMESPACE + name, JSON.stringify value + defaultVal + set: (key, val) -> + key = g.NAMESPACE + key + val = JSON.stringify val + # for `storage` events + localStorage.setItem key, val + GM_setValue key, val +<% } %> diff --git a/package.json b/package.json index 167a9a21f..648d85b2d 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "namespace": "zixaphir", "repo": "https://github.com/zixaphir/appchan-x/", "page": "http://zixaphir.github.com/appchan-x/", + "buildsPath": "builds/", "mainBranch": "Av2", "matches": [ "*://api.4chan.org/*", @@ -16,12 +17,14 @@ ] }, "devDependencies": { - "grunt": "~0.4.0", + "grunt": "~0.4.1", "grunt-bump": "~0.0.0", "grunt-contrib-clean": "~0.4.0", - "grunt-contrib-coffee": "~0.6.2", - "grunt-contrib-concat": "~0.1.0", - "grunt-contrib-watch": "~0.3.0", + "grunt-contrib-coffee": "~0.6.4", + "grunt-contrib-compress": "~0.4.5", + "grunt-contrib-concat": "~0.1.3", + "grunt-contrib-copy": "~0.4.0", + "grunt-contrib-watch": "~0.3.1", "grunt-exec": "~0.4.0" }, "repository": { diff --git a/src/config.coffee b/src/config.coffee index 180369be6..d00549e01 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -15,7 +15,7 @@ Config = ] 'Custom Board Navigation': [ true - 'Disable this to always display the full board list.' + 'Show custom links instead of the full board list.' ] '404 Redirect': [ true @@ -27,7 +27,7 @@ Config = ] 'Time Formatting': [ true - 'Localize and format timestamps arbitrarily.' + 'Localize and format timestamps.' ] 'Relative Post Dates': [ false @@ -39,19 +39,15 @@ Config = ] 'Comment Expansion': [ true - 'Can expand too long comments.' + 'Add buttons to expand long comments.' ] 'Thread Expansion': [ true - 'Can expand threads to view all replies.' + 'Add buttons to expand threads.' ] 'Index Navigation': [ false - 'Navigate to previous / next thread.' - ] - 'Custom CSS': [ - false - 'Apply custom CSS to 4chan.' + 'Add buttons to navigate between threads.' ] 'Check for Updates': [ true @@ -75,7 +71,7 @@ Config = 'Filtering': 'Anonymize': [ false - 'Turn everyone Anonymous.' + 'Make everyone Anonymous.' ] 'Filter': [ true @@ -87,19 +83,19 @@ Config = ] 'Thread Hiding': [ true - 'Hide entire threads.' + 'Add buttons to hide entire threads.' ] 'Reply Hiding': [ true - 'Hide single replies.' + 'Add buttons to hide single replies.' ] 'Hiding Buttons': [ true - 'Make buttons to hide threads / replies, in addition to menu links.' + 'Add buttons to hide threads / replies, in addition to menu links.' ] 'Stubs': [ true - 'Make stubs of hidden threads / replies.' + 'Show stubs of hidden threads / replies.' ] 'Images': @@ -135,8 +131,16 @@ Config = 'Menu': 'Menu': [ true - 'Add a drop-down menu in posts.' + 'Add a drop-down menu to posts.' ] + 'Thread Hiding Link': [ + true + 'Add a link to hide entire threads.' + ] + 'Reply Hiding Link': [ + true + 'Add a link to hide single replies.' + ] 'Report Link': [ true 'Add a report link to the menu.' @@ -222,7 +226,7 @@ Config = 'Hide the normal post form.' ] - 'Quote links': + 'Quote Links': 'Quote Backlinks': [ true 'Add quote backlinks.' @@ -681,21 +685,23 @@ Config = MD5: '' sauces: """ -http://iqdb.org/?url=%turl -http://www.google.com/searchbyimage?image_url=%turl -#http://tineye.com/search?url=%turl -#http://saucenao.com/search.php?db=999&url=%turl -#http://3d.iqdb.org/?url=%turl -#http://regex.info/exif.cgi?imgurl=%url +http://iqdb.org/?url=%TURL +https://www.google.com/searchbyimage?image_url=%TURL +#//tineye.com/search?url=%TURL +#http://saucenao.com/search.php?url=%TURL +#http://3d.iqdb.org/?url=%TURL +#http://regex.info/exif.cgi?imgurl=%URL # uploaders: -#http://imgur.com/upload?url=%url;text:Upload to imgur -#http://omploader.org/upload?url1=%url;text:Upload to omploader +#http://imgur.com/upload?url=%URL;text:Upload to imgur +#http://ompldr.org/upload?url1=%URL;text:Upload to ompldr # "View Same" in archives: #//archive.foolz.us/_/search/image/%MD5/;text:View same on foolz #//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/ #//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/ """ + 'Custom CSS': false + 'Header auto-hide': false 'Header catalog links': false diff --git a/src/features.coffee b/src/features.coffee index c68922763..8ae49caf2 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -31,7 +31,7 @@ Header = className: 'hide-board-list-button' innerHTML: '[ - ]\u00A0' $.on btn, 'click', Header.toggleBoardList - $.prepend fullBoardList, btn + $.add fullBoardList, btn else $.rm $ '#custom-board-list', nav fullBoardList.hidden = false @@ -40,8 +40,8 @@ Header = list = $ '#custom-board-list', Header.nav list.innerHTML = null return unless text - as = $$('#full-board-list a', Header.nav) - nodes = text.match(/[\w@]+(-(all|title|full|text:"[^"]+"))?|[^\w@]+/g).map (t) -> + as = $$('#full-board-list a', Header.nav)[0...-2] # ignore the Settings and Home links + nodes = text.match(/[\w@]+(-(all|title|full|index|catalog|text:"[^"]+"))*|[^\w@]+/g).map (t) -> if /^[^\w@]/.test t return $.tn t if t is 'toggle-all' @@ -58,12 +58,17 @@ Header = for a in as if a.textContent is board a = a.cloneNode true - if /-title$/.test t + if /-title/.test t a.textContent = a.title - else if /-full$/.test t + else if /-full/.test t a.textContent = "/#{board}/ - #{a.title}" - else if m = t.match /-text:"(.+)"$/ - a.textContent = m[1] + else if /-(index|catalog|text)/.test t + if m = t.match /-(index|catalog)/ + a.setAttribute 'data-only', m[1] + a.href = "//boards.4chan.org/#{board}/" + a.href += 'catalog' if m[1] is 'catalog' + if m = t.match /-text:"(.+)"/ + a.textContent = m[1] else if board is '@' $.addClass a, 'navSmall' return a @@ -186,9 +191,10 @@ Settings = $.asap (-> $.id 'boardNavMobile'), -> $.prepend $.id('navtopright'), [$.tn(' ['), link, $.tn('] ')] - unless $.get 'previousversion' + if (prevVersion = $.get 'previousversion', null) isnt g.VERSION + $.set 'lastupdate', Date.now() $.set 'previousversion', g.VERSION - $.on d, '4chanXInitFinished', Settings.open + $.on d, '4chanXInitFinished', Settings.open unless prevVersion Settings.addSection 'Main', Settings.main Settings.addSection 'Filter', Settings.filter @@ -231,17 +237,15 @@ Settings = links = [] for section in Settings.sections link = $.el 'a', + className: "tab-#{section.hyphenatedTitle}" textContent: section.title href: 'javascript:;' $.on link, 'click', Settings.openSection.bind section links.push link, $.tn ' | ' sectionToOpen = link if section.title is openSection links.pop() - if sectionToOpen - sectionToOpen.click() - else - links[0].click() $.add $('.sections-list', overlay), links + (if sectionToOpen then sectionToOpen else links[0]).click() $.on $('.close', overlay), 'click', Settings.close $.on overlay, 'click', Settings.close @@ -261,11 +265,15 @@ Settings = addSection: (title, open) -> if typeof title isnt 'string' {title, open} = title.detail - Settings.sections.push {title, open} + hyphenatedTitle = title.toLowerCase().replace /\s+/g, '-' + Settings.sections.push {title, hyphenatedTitle, open} openSection: -> + if selected = $ '.tab-selected', Settings.dialog + $.rmClass selected, 'tab-selected' + $.addClass $(".tab-#{@hyphenatedTitle}", Settings.dialog), 'tab-selected' section = $ 'section', Settings.dialog section.innerHTML = null - section.className = "section-#{@title.toLowerCase().replace /\s+/g, '-'}" + section.className = "section-#{@hyphenatedTitle}" @open section, g section.scrollTop = 0 @@ -290,7 +298,7 @@ Settings = description = arr[1] div = $.el 'div', innerHTML: ": #{description}" - $.on $('input', div), 'click', $.cb.checked + $.on $('input', div), 'change', $.cb.checked $.add fs, div $.add section, fs @@ -304,8 +312,7 @@ Settings = innerHTML: ": Clear manually hidden threads and posts on /#{g.BOARD}/." $.on $('button', div), 'click', -> @textContent = 'Hidden: 0' - $.delete "hiddenThreads.#{g.BOARD}" - $.delete "hiddenPosts.#{g.BOARD}" + $.delete ["hiddenThreads.#{g.BOARD}", "hiddenPosts.#{g.BOARD}"] $.after $('input[name="Stubs"]', section).parentNode.parentNode, div export: -> now = Date.now() @@ -318,7 +325,7 @@ Settings = className: 'warning' textContent: 'Save me!' download: "<%= meta.name %> v#{g.VERSION}-#{now}.json" - href: "data:application/json;base64,#{btoa unescape encodeURIComponent JSON.stringify data}" + href: "data:application/json;base64,#{btoa unescape encodeURIComponent JSON.stringify data, null, 2}" target: '_blank' if $.engine isnt 'gecko' a.click() @@ -458,7 +465,7 @@ Settings = $.add div, ta return div.innerHTML = """ -
Filter is disabled.
+
Filter is disabled.

Use regular expressions, one per line.
Lines starting with a # will be ignored.
@@ -516,6 +523,9 @@ Settings =

Title link: board-title
Full text link: board-full
Custom text link: board-text:"VIP Board"
+
Index-only link: board-index
+
Catalog-only link: board-catalog
+
Combinations are possible: board-index-text:"VIP Index"
Full board list toggle: toggle-all
@@ -558,9 +568,9 @@ Settings =
- Custom CSS is disabled. + Custom CSS - +
""" for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss'] @@ -574,6 +584,7 @@ Settings = unless 'usercss' is name $.on input, event, Settings[name] Settings[name].call input + $.on $('input[name="Custom CSS"]', section), 'change', Settings.togglecss $.on $.id('apply-css'), 'click', Settings.usercss boardnav: -> Header.generateBoardList @value @@ -598,12 +609,20 @@ Settings = favicon: -> Favicon.switch() Unread.update() if g.VIEW is 'thread' and Conf['Unread Tab Icon'] - @nextElementSibling.innerHTML = " " - usercss: -> - if Conf['Custom CSS'] - CustomCSS.update() - else + @nextElementSibling.innerHTML = """ + + + + + """ + togglecss: -> + if $('textarea', @parentNode.parentNode).disabled = !@checked CustomCSS.rmStyle() + else + CustomCSS.addStyle() + $.cb.checked.call @ + usercss: -> + CustomCSS.update() keybinds: (section) -> section.innerHTML = """ @@ -639,33 +658,40 @@ Fourchan = board = g.BOARD.ID if board is 'g' + $.globalEval """ + window.addEventListener('prettyprint', function(e) { + var pre = e.detail; + pre.innerHTML = prettyPrintOne(pre.innerHTML); + }, false); + """ Post::callbacks.push name: 'Parse /g/ code' cb: @code if board is 'sci' + # https://github.com/MayhemYDG/4chan-x/issues/645#issuecomment-13704562 + $.globalEval """ + window.addEventListener('jsmath', function(e) { + if (jsMath.loaded) { + // process one post + jsMath.ProcessBeforeShowing(e.detail); + } else { + // load jsMath and process whole document + jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]); + jsMath.Autoload.LoadJsMath(); + } + }, false); + """ Post::callbacks.push name: 'Parse /sci/ math' cb: @math code: -> return if @isClone for pre in $$ '.prettyprint', @nodes.comment - pre.innerHTML = $.unsafeWindow.prettyPrintOne pre.innerHTML + $.event 'prettyprint', pre, window return math: -> return if @isClone or !$ '.math', @nodes.comment - # https://github.com/MayhemYDG/4chan-x/issues/645#issuecomment-13704562 - {jsMath} = $.unsafeWindow - if jsMath - if jsMath.loaded - # process one post - jsMath.ProcessBeforeShowing @nodes.post - else - # load jsMath and process whole document - # Yes this requires to be globalEval'd, don't ask me why. - $.globalEval """ - jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]); - jsMath.Autoload.LoadJsMath(); - """ + $.event 'jsmath', @nodes.post, window parseThread: (threadID, offset, limit) -> # Fix /sci/ # Fix /g/ @@ -966,7 +992,7 @@ Filter = ThreadHiding = init: -> - return if g.VIEW isnt 'index' or !Conf['Thread Hiding'] + return if g.VIEW isnt 'index' or !Conf['Thread Hiding'] and !Conf['Thread Hiding Link'] Misc.clearThreads "hiddenThreads.#{g.BOARD}" @getHiddenThreads() @@ -978,7 +1004,7 @@ ThreadHiding = node: -> if data = ThreadHiding.hiddenThreads.threads[@] ThreadHiding.hide @, data.makeStub - return unless Conf['Hiding Buttons'] + return unless Conf['Thread Hiding'] $.prepend @OP.nodes.root, ThreadHiding.makeButton @, 'hide' getHiddenThreads: -> @@ -1006,7 +1032,7 @@ ThreadHiding = menu: init: -> - return if g.VIEW isnt 'index' or !Conf['Menu'] or !Conf['Thread Hiding'] + return if g.VIEW isnt 'index' or !Conf['Menu'] or !Conf['Thread Hiding Link'] div = $.el 'div', className: 'hide-thread-link' @@ -1108,7 +1134,7 @@ ThreadHiding = ReplyHiding = init: -> - return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] + return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] and !Conf['Reply Hiding Link'] Misc.clearThreads "hiddenPosts.#{g.BOARD}" @getHiddenPosts() @@ -1125,7 +1151,7 @@ ReplyHiding = else Recursive.apply ReplyHiding.hide, @, data.makeStub, true Recursive.add ReplyHiding.hide, @, data.makeStub, true - return unless Conf['Hiding Buttons'] + return unless Conf['Reply Hiding'] $.add $('.postInfo', @nodes.post), ReplyHiding.makeButton @, 'hide' getHiddenPosts: -> @@ -1133,7 +1159,7 @@ ReplyHiding = menu: init: -> - return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Reply Hiding'] + return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Reply Hiding Link'] # Hide div = $.el 'div', @@ -1343,7 +1369,7 @@ Recursive = QuoteStrikeThrough = init: -> - return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] and !Conf['Filter'] + return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] and !Conf['Reply Hiding Link'] and !Conf['Filter'] Post::callbacks.push name: 'Strike-through Quotes' @@ -1631,7 +1657,7 @@ Keybinds = when Conf['Open settings'] Settings.open() when Conf['Close'] - if $.id 'settings' + if $.id 'fourchanx-settings' Settings.close() else if (notifications = $$ '.notification').length for notification in notifications @@ -1798,7 +1824,6 @@ Keybinds = focus: (post) -> $.addClass post, 'highlight' - $('a[title="Highlight this post"]', post).focus() Nav = init: -> @@ -2426,10 +2451,10 @@ Misc = # super semantic return if data.lastChecked > Date.now() - 12 * $.HOUR - $.ajax "//api.4chan.org/#{g.BOARD}/catalog.json", onload: -> + $.ajax "//api.4chan.org/#{g.BOARD}/threads.json", onload: -> threads = {} - for obj in JSON.parse @response - for thread in obj.threads + for page in JSON.parse @response + for thread in page.threads if thread.no of data.threads threads[thread.no] = data.threads[thread.no] unless Object.keys(threads).length @@ -2637,7 +2662,7 @@ QuotePreview = # Remove the clone that's in the qp from the array. posts.pop() for post in posts - $.addClass post.nodes.post, 'qphl' + $.addClass post.nodes.root, 'qphl' quoterID = $.x('ancestor::*[@id][1]', @).id.match(/\d+$/)[0] clone = Get.postFromRoot qp.firstChild @@ -2658,7 +2683,7 @@ QuotePreview = return unless Conf['Quote Highlighting'] for post in [post].concat post.clones - $.rmClass post.nodes.post, 'qphl' + $.rmClass post.nodes.root, 'qphl' return QuoteBacklink = @@ -3062,14 +3087,15 @@ Sauce = createSauceLink: (link) -> link = link.replace /%(T?URL|MD5|board)/ig, (parameter) -> switch parameter - when '%TURL', '%turl' - "' + post.file.thumbURL + '" - when '%URL', '%url' - "' + post.file.URL + '" - when '%MD5', '%md5' + + when '%TURL' + "' + encodeURIComponent(post.file.thumbURL) + '" + when '%URL' + "' + encodeURIComponent(post.file.URL) + '" + when '%MD5' "' + encodeURIComponent(post.file.MD5) + '" when '%board' - "' + post.board + '" + "' + encodeURIComponent(post.board) + '" else parameter text = if m = link.match(/;text:(.+)$/) then m[1] else link.match(/(\w+)\.\w+\//)[1] @@ -3178,7 +3204,7 @@ ImageExpand = # Scroll back to the thumbnail when contracting the image # to avoid being left miles away from the relevant post. postRect = post.nodes.root.getBoundingClientRect() - headRect = Header.bar.getBoundingClientRect() + headRect = Header.toggle.getBoundingClientRect() top = postRect.top - headRect.top - headRect.height - 2 root = if $.engine is 'webkit' d.body @@ -3229,7 +3255,10 @@ ImageExpand = post = Get.postFromNode @ $.rm @ delete post.file.fullImage - unless $.hasClass post.file.thumb, 'expanding' + # Images can error: + # - before the image started loading. + # - after the image started loading. + unless $.hasClass(post.file.thumb, 'expanding') or $.hasClass post.nodes.root, 'expanded-image' # Don't try to re-expend if it was already contracted. return ImageExpand.contract post @@ -3452,7 +3481,7 @@ ExpandThread = #goddamit moot num = if thread.isSticky 1 - else switch g.BOARD + else switch g.BOARD.ID # XXX boards config when 'b', 'vg', 'q' then 3 when 't' then 1 @@ -3500,7 +3529,7 @@ ExpandThread = # Enable 4chan features. if Conf['Enable 4chan\'s Extension'] - $.unsafeWindow.Parser.parseThread thread.ID, 1, nodes.length + $.globalEval "Parser.parseThread(#{thread.ID}, 1, #{nodes.length})" else Fourchan.parseThread thread.ID, 1, nodes.length @@ -3512,7 +3541,10 @@ ThreadExcerpt = name: 'Thread Excerpt' cb: @node node: -> - d.title = Get.threadExcerpt @ + d.title = if (excerpt = Get.threadExcerpt @).length > 80 + "#{excerpt[...77]}..." + else + excerpt Unread = init: -> @@ -3634,10 +3666,12 @@ Unread = else Favicon.default + <% if (type !== 'crx') { %> # `favicon.href = href` doesn't work on Firefox. # `favicon.href = href` isn't enough on Opera. # Opera won't always update the favicon if the href didn't change. $.add d.head, Favicon.el + <% } %> Favicon = init: -> @@ -4004,9 +4038,9 @@ ThreadUpdater = $.queueTask -> # Enable 4chan features. threadID = ThreadUpdater.thread.ID - {length} = ThreadUpdater.root.children + {length} = $$ '.thread > .postContainer', ThreadUpdater.root if Conf['Enable 4chan\'s Extension'] - $.unsafeWindow.Parser.parseThread threadID, -count + $.globalEval "Parser.parseThread(#{threadID}, #{-count})" else Fourchan.parseThread threadID, length - count, length @@ -4045,6 +4079,7 @@ ThreadWatcher = $.delete 'AutoWatch' ready: -> + return unless Main.isThisPageLegit() ThreadWatcher.refresh() $.add d.body, ThreadWatcher.dialog diff --git a/src/globals.coffee b/src/globals.coffee index 13bdd4b61..05593377a 100644 --- a/src/globals.coffee +++ b/src/globals.coffee @@ -1,6 +1,8 @@ +<% if (type === 'userjs') { %> # Opera doesn't support the @match metadata key, # return 4chan X here if we're not on 4chan. return unless /^[a-z]+\.4chan\.org$/.test location.hostname +<% } %> editTheme = {} # Currently editted theme. editMascot = {} # Which mascot we're editting. @@ -8,8 +10,8 @@ userNavigation = {} # ... Conf = {} c = console d = document -doc = null -g = +doc = d.documentElement +g = VERSION: '<%= version %>' NAMESPACE: '<%= meta.name %>.'.replace ' ', '_' TYPE: 'sfw' @@ -2759,4 +2761,4 @@ Icons = png: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA' themes: oneechan: 'A8AAACWBAMAAADzkc/yAAAAMFBMVEVoaGhsbGxsbGxsbGxsbGxsbGxsbGxsbGxqampsbGxsbGxsbGxlZWVsbGxsbGxsbGzdpWE1AAAAD3RSTlMAfEDYHcNYkhP0pz8MJYTrg0d+AAACwElEQVR4Xp3RT2gTaRjH8W9r/rVJNTl42UvHvYinVGLR7SoZ6+JBD0kpsafVgJale4m67r/DkgEp7EEMsqy2KEwEUdjt0kBZqovY3oQedEBE8GA9iV42TQaTVm0f33lfCAgLZfuDgc/85n2f5zDAonnYIQ5LIwCykrj5hyqc5dWlz35QaMr7c3eKEBGReVVASaRZBOCxiKfRJ7KuMV4x1eGvE7pKjHo8bvwDXXsh0Xqu4AGP/mKT7DRgwTc4IgaXRSSTeQ2i0zbNv2UBjonP9gDMGQC7DQAiGbacbwpjRYB7rsh0DSLN+pnSqxbk7reIfEjb7Iyvwvtw0jSrC7Y6Uy6WzreAPVl1y+rM2STjmaGa3uX/+PNbB+JrHqSnoDJAfy3mQ92hbJG2GFS26KnSIHjtHaClUCM6wBA8RX0KDtf14coUp73YdDDQgvRb4ImMnRJxgPnRsXFZwaTbAl4UTnoASzKYbTsQy14gVD4AvS0g0oblsxCvztrUbUKztVySUSi12ZanQEg+EM6rpleK9OTptxd+AnUmV/0TULeies5GMPmWun8AeCS/uxsOwOHCiM0nOV4zWBp5Y2ncJnYIIJSHeYB4EsIaL2ESgH3wSmPOutzQCH1/ov8gJmF/0SCWXTfgYtuAvl/4jyyLzzYRj5xcJ+6KrfBucbsEcKVaF9dm8oH48u5vB6iLzJhN4qMzIWtF4OFR95p79dkilS+kOLGWdajIVSKuBDhEKKuQkzZRUUj8ekk/MPy5io2qg7Qhd8GGr8o2aRsgZ5EeloZB9zqwQ6PrxkkN8QanTEMpaRD7TTfDsnFwv7rVmdOZ3NnV2a6KZL/Iyv9DKpWydqVSSTrpNFsbCN9mpk8ATIrKWWA2QAsiEqQBYY0Z6FG4cheIytBEC4AE4TUP0GXeoEvaBnG1xSgrTYOS+Hz6M2PffclH1wyYp51t8R8AAAAASUVORK5CYII=' - "4chan SS": 'A8AAACWBAMAAADzkc/yAAAAMFBMVEVmZmZnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2djY2NkZGRnZ2dnZ2dnZ2eHsc5jAAAAD3RSTlMAgEDLkve+G+dSqwcRdCZzKH/3AAACz0lEQVR4Xp2PQWhTdxzHP0vTliQvMaE4NjdoutMOA6NrpeghQaazp4R1XkL15VDR4UbeKiJ4UXCuzG7kgd2lONLbGNmWgKwqGyMQN0QRqruIuKYXi9iDtkmeaaD97Z9/2nfy0n0u+fDl//3+XnA5E+XIoA30bPDnVceC0EfgkynAl6AyulICHhjDi/kYEHx7Ab8NhPpW0bxfbRQAfEcfSasEVGv3RTIQlMlAVlagv7lIRcSi+An05hy4awN3f+a1TKNhqbFvfAG4LW1+BVNLI0pRNDE8cS0p8F364bEpF4FbkJdZAIxs0wIIvSU/AuAdPci2GRj6ooz+jpp8A4RyzvKSFOCZrB46KLUyWUmZKVMybuK+cVvAwAm1s12MXUP73wXYKSKNMHhF0iJ1qIpMqWyebEcymCJjSi4SFzmuk6p+LPP8JXKs0zI3dx7eePOyWn4900ODNsD5xmC8ZUHv5Se/B81J8Lf6xkdOpSE/kmvtWq7aJK0Xze/KNxN8iNeZpyfGpwT+vYc3xT4I/GP7YxwqQTL9W4J8GO6sqJb/OHSPpNvL7/C8OKlvjZ9Qt9zrEDx1Rf+G4iKtMtAlIzOSAKprUF0FsgmV1oGcDW84W9ICirNwfQ3IrxNIvgL8sqdPVgAjKYow4Dn805c6AujXEaq/JURg/BpgTGdINhbhqSTI10pQEZs78dMY8eEyvXNOqeLcAALZkyoEmGs4JS295hiaik5Qlc+zpwFPxSnNqRZncwVVVDt6+brYFJubt36Jbl5n4j2FBV5pU4cLH1twLmmx2wK4EGX3hKx3xLMB7NASHBvUIstff9tJyM93xLg0tkO36lNfqZa74y67t9zrKgj3i7zcnkQikehAJBLGxU3+3yDMDB/9DOBvUSSAYlvWwSeb0qWlAN1Kjn0AeOXK9+sA/EFXYxHQ4QE0QamjCakraOKyiqYqNTQ9W2Ic2ct/3c/SAwz13v8AAAAASUVORK5CYII=' \ No newline at end of file + "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=' diff --git a/src/main.coffee b/src/main.coffee index ab40d951b..4abfefcb6 100644 --- a/src/main.coffee +++ b/src/main.coffee @@ -41,28 +41,28 @@ class Post backlinks: info.getElementsByClassName 'backlink' @info = {} - if subject = $ '.subject', info + if subject = $ '.subject', info @nodes.subject = subject @info.subject = subject.textContent - if name = $ '.name', info + if name = $ '.name', info @nodes.name = name @info.name = name.textContent - if email = $ '.useremail', info + if email = $ '.useremail', info @nodes.email = email @info.email = decodeURIComponent email.href[7..] - if tripcode = $ '.postertrip', info + if tripcode = $ '.postertrip', info @nodes.tripcode = tripcode @info.tripcode = tripcode.textContent - if uniqueID = $ '.posteruid', info + if uniqueID = $ '.posteruid', info @nodes.uniqueID = uniqueID @info.uniqueID = uniqueID.firstElementChild.textContent - if capcode = $ '.capcode', info + if capcode = $ '.capcode.hand', info @nodes.capcode = capcode - @info.capcode = capcode.textContent - if flag = $ '.countryFlag', info + @info.capcode = capcode.textContent.replace '## ', '' + if flag = $ '.countryFlag', info @nodes.flag = flag @info.flag = flag.title - if date = $ '.dateTime', info + if date = $ '.dateTime', info @nodes.date = date @info.date = new Date date.dataset.utc * 1000 if Conf['Quick Reply'] @@ -284,9 +284,6 @@ class Clone extends Post Main = init: -> - $.asap (-> d.documentElement), -> - doc = d.documentElement - # flatten Config into Conf # and get saved or default values flatten = (parent, obj) -> @@ -344,7 +341,7 @@ Main = for name, module of features # c.time "#{name} initialization" try - module.init() + do module.init catch err Main.handleErrors message: "\"#{name}\" initialization crashed." @@ -447,6 +444,7 @@ Main = Main.callbackNodes Post, posts $.event '4chanXInitFinished' + Main.checkUpdate() callbackNodes: (klass, nodes) -> # get the nodes' length only once @@ -467,14 +465,42 @@ Main = Main.handleErrors errors if errors addCallback: (e) -> - obj = e.detail - Klass = if obj.type is 'Post' - Post - else - Thread + obj = e.detail + unless typeof obj.callback.name is 'string' + throw new Error "Invalid callback name: #{obj.callback.name}" + switch obj.type + when 'Post' + Klass = Post + when 'Thread' + Klass = Thread + else + return obj.callback.isAddon = true Klass::callbacks.push obj.callback + checkUpdate: -> + return unless Main.isThisPageLegit() + # Check for updates after: + # - 6 hours since the last update on Opera because it lacks auto-updating. + # - 7 days since the last update on Chrome/Firefox. + # After that, check for updates every day if we still haven't updated. + now = Date.now() + freq = <% if (type === 'userjs') { %>6 * $.HOUR<% } else { %>7 * $.DAY<% } %> + if $.get('lastupdate', 0) > now - freq or $.get('lastchecked', 0) > now - $.DAY + return + $.ajax '<%= meta.page %><%= meta.buildsPath %>version', onload: -> + return unless @status is 200 + version = @response + return unless /^\d\.\d+\.\d+$/.test version + if g.VERSION is version + # Don't check for updates too frequently if there wasn't one in a 'long' time. + $.set 'lastupdate', now + return + $.set 'lastchecked', now + el = $.el 'span', + innerHTML: "Update: <%= meta.name %> v#{version} is out, get it target=_blank>here." + new Notification 'info', el, 2 * $.MINUTE + handleErrors: (errors) -> unless 'length' of errors error = errors @@ -485,7 +511,7 @@ Main = return div = $.el 'div', - innerHTML: "#{errors.length} errors occured. [show]" + innerHTML: "#{errors.length} errors occurred. [show]" $.on div.lastElementChild, 'click', -> if @textContent is 'show' @textContent = 'hide' @@ -514,7 +540,9 @@ Main = isThisPageLegit: -> # 404 error page or similar. unless 'thisPageIsLegit' of Main - Main.thisPageIsLegit = !$('link[href*="favicon-status.ico"]', d.head) and d.title isnt '4chan - Temporarily Offline' + Main.thisPageIsLegit = location.hostname is 'boards.4chan.org' and + !$('link[href*="favicon-status.ico"]', d.head) and + d.title not in ['4chan - Temporarily Offline', '4chan - Error'] Main.thisPageIsLegit diff --git a/src/manifest.json b/src/manifest.json index f125f6cb5..b17375947 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -3,12 +3,19 @@ "version": "<%= version %>", "manifest_version": 2, "description": "<%= description %>", + "icons": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + }, "content_scripts": [{ "js": ["script.js"], "matches": <%= JSON.stringify(meta.matches) %>, "run_at": "document_start" }], "homepage_url": "<%= meta.page %>", - "incognito": "spanning", - "minimum_chrome_version": "25" + "minimum_chrome_version": "25", + "permissions": [ + "storage" + ] } diff --git a/src/metadata.js b/src/metadata.js index 3337a2fc9..d2d22916f 100644 --- a/src/metadata.js +++ b/src/metadata.js @@ -16,7 +16,7 @@ // @grant GM_deleteValue // @grant GM_openInTab // @run-at document-start -// @updateURL <%= meta.repo %>raw/<%= meta.mainBranch %>/<%= name %>.meta.js -// @downloadURL <%= meta.repo %>raw/<%= meta.mainBranch %>/<%= name %>.user.js -// @icon data:image/gif;base64,<%= grunt.file.read('img/icon.gif', {encoding: 'base64'}) %> +// @updateURL <%= meta.page %><%= meta.buildsPath %><%= name %>.meta.js +// @downloadURL <%= meta.page %><%= meta.buildsPath %><%= name %>.user.js +// @icon data:image/png;base64,<%= grunt.file.read('img/icon48.png', {encoding: 'base64'}) %> // ==/UserScript== diff --git a/src/qr.coffee b/src/qr.coffee index d4ea0b4f9..56448a468 100644 --- a/src/qr.coffee +++ b/src/qr.coffee @@ -486,6 +486,7 @@ QR = # so we generate thumbnails `s` times bigger then expected # to avoid crappy resized quality. s = 90*2 + s *= 3 if @file.type is 'image/gif' # let them animate {height, width} = img if height < s or width < s @URL = fileURL if window.URL @@ -581,6 +582,12 @@ QR = return unless @isEnabled = !!$.id 'captchaFormPart' $.asap (-> $.id 'recaptcha_challenge_field_holder'), @ready.bind @ ready: -> + setLifetime = (e) => @lifetime = e.detail + $.on window, 'captcha:timeout', setLifetime + $.globalEval 'window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))' + $.off window, 'captcha:timeout', setLifetime + c.log @lifetime + imgContainer = $.el 'div', className: 'captcha-img' title: 'Reload' @@ -648,7 +655,7 @@ QR = load: -> return unless @nodes.challenge.firstChild # -1 minute to give upload some time. - @timeout = Date.now() + $.unsafeWindow.RecaptchaState.timeout * $.SECOND - $.MINUTE + @timeout = Date.now() + @lifetime * $.SECOND - $.MINUTE challenge = @nodes.challenge.firstChild.value @nodes.img.alt = challenge @nodes.img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}" @@ -666,7 +673,7 @@ QR = @nodes.input.alt = count # For XTRM RICE. reload: (focus) -> # the 't' argument prevents the input from being focused - $.unsafeWindow.Recaptcha.reload 't' + $.globalEval 'Recaptcha.reload("t")' # Focus if we meant to. @nodes.input.focus() if focus keydown: (e) -> @@ -701,14 +708,14 @@ QR = +
+ No selected file - × - - + × +
@@ -877,12 +884,14 @@ QR = callbacks = onload: QR.response onerror: -> + # Connection error, or + # www.4chan.org/banned delete QR.req post.unlock() QR.cooldown.auto = false QR.status() - # Connection error. - QR.error 'Network error.' + QR.error $.el 'span', + innerHTML: 'Connection error. You may have been banned.' opts = form: $.formData postData upCallbacks: @@ -966,6 +975,7 @@ QR = [_, threadID, postID] = h1.nextSibling.textContent.match /thread:(\d+),no:(\d+)/ postID = +postID threadID = +threadID or postID + isReply = threadID isnt postID (QR.yourPosts.threads[threadID] or= []).push postID $.set "yourPosts.#{g.BOARD}", QR.yourPosts @@ -980,18 +990,15 @@ QR = }, QR.nodes.el # Enable auto-posting if we have stuff to post, disable it otherwise. - QR.cooldown.auto = QR.posts.length > 1 + QR.cooldown.auto = QR.posts.length > 1 and isReply post.rm() - QR.cooldown.set - req: req - post: post - isReply: !!threadID + QR.cooldown.set {req, post, isReply} if threadID is postID # new thread URL = "/#{g.BOARD}/res/#{threadID}" - else if g.VIEW is 'index' and !QR.cooldown.auto # posting from the index + else if g.VIEW is 'index' and !QR.cooldown.auto and Conf['Open Post in New Tab'] # replying from the index URL = "/#{g.BOARD}/res/#{threadID}#p#{postID}" if URL if Conf['Open Post in New Tab'] diff --git a/src/report.coffee b/src/report.coffee index 42ae19bd4..eea172ddc 100644 --- a/src/report.coffee +++ b/src/report.coffee @@ -6,7 +6,7 @@ Report = form = $ 'form' field = $.id 'recaptcha_response_field' $.on field, 'keydown', (e) -> - $.unsafeWindow.Recaptcha.reload 't' if e.keyCode is 8 and not field.value + $.globalEval 'Recaptcha.reload("t")' if e.keyCode is 8 and not field.value $.on form, 'submit', (e) -> e.preventDefault() response = field.value.trim()