diff --git a/4chan_x.user.js b/4chan_x.user.js index 72f457cf7..8b9bd0056 100644 --- a/4chan_x.user.js +++ b/4chan_x.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan x -// @version 2.25.3 +// @version 2.25.5 // @namespace aeosynth // @description Adds various features. // @copyright 2009-2011 James Campos @@ -19,7 +19,7 @@ * Copyright (c) 2009-2011 James Campos * Copyright (c) 2012 Nicolas Stepien * http://mayhemydg.github.com/4chan-x/ - * 4chan X 2.25.3 + * 4chan X 2.25.5 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -117,7 +117,8 @@ 'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.'], 'Auto Hide QR': [true, 'Automatically hide the quick reply when posting.'], 'Remember Subject': [false, 'Remember the subject field, instead of resetting after posting.'], - 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'] + 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'], + 'Hide Original Post Form': [true, 'Replace the normal post form with a shortcut to open the QR.'] }, Quoting: { 'Quote Backlinks': [true, 'Add quote backlinks'], @@ -140,33 +141,33 @@ filesize: '', md5: '' }, - flavors: ['http://iqdb.org/?url=', 'http://google.com/searchbyimage?image_url=', '#http://tineye.com/search?url=', '#http://saucenao.com/search.php?db=999&url=', '#http://3d.iqdb.org/?url=', '#http://regex.info/exif.cgi?imgurl=', '#http://imgur.com/upload?url=', '#http://ompldr.org/upload?url1='].join('\n'), + sauces: ['http://iqdb.org/?url=$1', 'http://www.google.com/searchbyimage?image_url=$1', '#http://tineye.com/search?url=$1', '#http://saucenao.com/search.php?db=999&url=$1', '#http://3d.iqdb.org/?url=$1', '#http://regex.info/exif.cgi?imgurl=$2', '# uploaders:', '#http://imgur.com/upload?url=$2', '#http://ompldr.org/upload?url1=$2', '# "View Same" in archives:', '#http://archive.foolz.us/a/image/$3/', '#http://archive.installgentoo.net/g/image/$3'].join('\n'), time: '%m/%d/%y(%a)%H:%M', backlink: '>>%id', favicon: 'ferongr', hotkeys: { - openOptions: 'ctrl+o', - close: 'Esc', - spoiler: 'ctrl+s', - openQR: 'i', - openEmptyQR: 'I', - submit: 'alt+s', - nextReply: 'J', - previousReply: 'K', - nextThread: 'n', - previousThread: 'p', - nextPage: 'L', - previousPage: 'H', - zero: '0', - openThreadTab: 'o', - openThread: 'O', - expandThread: 'e', - watch: 'w', - hide: 'x', - expandImages: 'm', - expandAllImages: 'M', - update: 'u', - unreadCountTo0: 'z' + openOptions: ['ctrl+o', 'Open Options'], + close: ['Esc', 'Close Options or QR'], + spoiler: ['ctrl+s', 'Quick spoiler'], + openQR: ['i', 'Open QR with post number inserted'], + openEmptyQR: ['I', 'Open QR without post number inserted'], + submit: ['alt+s', 'Submit post'], + nextReply: ['J', 'Select next reply'], + previousReply: ['K', 'Select previous reply'], + nextThread: ['n', 'See next thread'], + previousThread: ['p', 'See previous thread'], + nextPage: ['L', 'Jump to the next page'], + previousPage: ['H', 'Jump to the previous page'], + zero: ['0', 'Jump to page 0'], + openThreadTab: ['o', 'Open thread in current tab'], + openThread: ['O', 'Open thread in new tab'], + expandThread: ['e', 'Expand thread'], + watch: ['w', 'Watch thread'], + hide: ['x', 'Hide thread'], + expandImages: ['m', 'Expand selected image'], + expandAllImages: ['M', 'Expand all images'], + update: ['u', 'Update now'], + unreadCountTo0: ['z', 'Reset unread count to 0'] }, updater: { checkbox: { @@ -185,19 +186,17 @@ (flatten = function(parent, obj) { var key, val, _results; - if (obj.length) { - if (typeof obj[0] === 'boolean') { + if (typeof obj === 'object') { + if (obj.length) { return conf[parent] = obj[0]; } else { - return conf[parent] = obj; + _results = []; + for (key in obj) { + val = obj[key]; + _results.push(flatten(key, val)); + } + return _results; } - } else if (typeof obj === 'object') { - _results = []; - for (key in obj) { - val = obj[key]; - _results.push(flatten(key, val)); - } - return _results; } else { return conf[parent] = obj; } @@ -205,7 +204,7 @@ NAMESPACE = '4chan_x.'; - VERSION = '2.25.3'; + VERSION = '2.25.5'; SECOND = 1000; @@ -306,7 +305,6 @@ val = properties[key]; object[key] = val; } - return object; }; $.extend($, { @@ -409,14 +407,12 @@ return el.parentNode.removeChild(el); }, add: function() { - var child, children, parent, _i, _len, _results; + var child, children, parent, _i, _len; parent = arguments[0], children = 2 <= arguments.length ? __slice.call(arguments, 1) : []; - _results = []; for (_i = 0, _len = children.length; _i < _len; _i++) { child = children[_i]; - _results.push(parent.appendChild(child)); + parent.appendChild(child); } - return _results; }, prepend: function(parent, child) { return parent.insertBefore(child, parent.firstChild); @@ -1200,13 +1196,17 @@ qr = { init: function() { - var h1, iframe; + var form, iframe, link, loadChecking; if (!$.id('recaptcha_challenge_field_holder')) return; - h1 = $.el('h1', { - innerHTML: 'Open the Quick Reply' - }); - $.on($('a', h1), 'click', qr.open); - $.add($('.postarea'), h1); + if (conf['Hide Original Post Form']) { + link = $.el('h1', { + innerHTML: "" + (g.REPLY ? 'Open the Quick Reply' : 'Create a New Thread') + "" + }); + $.on($('a', link), 'click', qr.open); + form = d.forms[0]; + form.hidden = true; + $.before(form, link); + } g.callbacks.push(function(root) { return $.on($('.quotejs + .quotejs', root), 'click', qr.quote); }); @@ -1218,14 +1218,16 @@ $.on(iframe, 'error', function() { return this.src = this.src; }); - $.on(iframe, 'load', function() { - var _this = this; - if (!(qr.status.ready || this.src === 'about:blank')) { - this.src = 'about:blank'; + loadChecking = function(iframe) { + if (!qr.status.ready) { + iframe.src = 'about:blank'; return setTimeout((function() { - return _this.src = 'http://sys.4chan.org/post'; + return iframe.src = 'http://sys.4chan.org/post'; }), 250); } + }; + $.on(iframe, 'load', function() { + if (this.src !== 'about:blank') return setTimeout(loadChecking, 250, this); }); $.add(d.body, iframe); if (conf['Persistent QR']) { @@ -1352,11 +1354,9 @@ } ta = $('textarea', qr.el); caretPos = ta.selectionStart; - ta.value = ta.value.slice(0, caretPos) + text + ta.value.slice(ta.selectionEnd, ta.value.length); + qr.selected.el.lastChild.textContent = qr.selected.com = ta.value = ta.value.slice(0, caretPos) + text + ta.value.slice(ta.selectionEnd, ta.value.length); ta.focus(); - ta.selectionEnd = ta.selectionStart = caretPos + text.length; - qr.selected.com = ta.value; - return qr.selected.el.lastChild.textContent = ta.value; + return ta.selectionEnd = ta.selectionStart = caretPos + text.length; }, fileDrop: function(e) { if (/TEXTAREA|INPUT/.test(e.target.nodeName)) return; @@ -1538,9 +1538,16 @@ return this.input.value = null; }, count: function(count) { - var s; - s = count === 1 ? '' : 's'; - this.input.placeholder = "Verification (" + count + " cached captcha" + s + ")"; + this.input.placeholder = (function() { + switch (count) { + case 0: + return 'Verification (Shift + Enter to cache)'; + case 1: + return 'Vertification (1 cached captcha)'; + default: + return "Verification (" + count + " cached captchas)"; + } + })(); return this.input.alt = count; }, reload: function(focus) { @@ -1561,15 +1568,15 @@ } }, dialog: function() { - var e, fileInput, input, mimeTypes, spoiler, thread, threads, _i, _j, _len, _len2, _ref, _ref2; + var e, fileInput, input, mimeTypes, name, spoiler, thread, threads, _i, _j, _len, _len2, _ref, _ref2; qr.el = ui.dialog('qr', 'top:0;right:0;', '\
\ Quick Reply \ - x\ + x\
\
\
\ - \ + \
\
\
\ @@ -1602,7 +1609,8 @@ threads += ""; } $.prepend($('.move > span', qr.el), $.el('select', { - innerHTML: threads + innerHTML: threads, + title: 'Create a new thread / Reply to a thread' })); $.on($('select', qr.el), 'mousedown', function(e) { return e.stopPropagation(); @@ -1628,8 +1636,12 @@ new qr.reply().select(); _ref2 = ['name', 'email', 'sub', 'com']; for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { - input = _ref2[_j]; - $.on($("[name=" + input + "]", qr.el), 'keyup', function() { + name = _ref2[_j]; + input = $("[name=" + name + "]", qr.el); + $.on(input, 'keyup', function() { + return qr.selected[this.name] = this.value; + }); + $.on(input, 'change', function() { return qr.selected[this.name] = this.value; }); } @@ -1952,7 +1964,7 @@ } }, dialog: function() { - var arr, back, checked, description, dialog, favicon, hiddenNum, hiddenThreads, indicator, indicators, input, key, li, obj, overlay, ta, time, ul, _i, _j, _k, _len, _len2, _len3, _ref, _ref2, _ref3, _ref4; + var arr, back, checked, description, dialog, favicon, hiddenNum, hiddenThreads, indicator, indicators, input, key, li, obj, overlay, ta, time, tr, ul, _i, _j, _len, _len2, _ref, _ref2, _ref3, _ref4; dialog = $.el('div', { id: 'options', className: 'reply dialog', @@ -1964,7 +1976,7 @@
\ \ | \ - | \ + | \ | \ | \
\ @@ -1973,10 +1985,16 @@
\ \
\ - \ + \
\
Sauce is disabled.
\ - \ +
Lines starting with a # will be ignored.
\ +
    These variables will be replaced by the corresponding url:\ +
  • $1: Thumbnail.
  • \ +
  • $2: Full image.
  • \ +
  • $3: MD5 hash.
  • \ +
\ + \
\ \
\ @@ -2024,30 +2042,9 @@ \
\
Keybinds are disabled.
\ +
Allowed keys: Ctrl, Alt, a-z, A-Z, 0-1, Up, Down, Right, Left.
\ \ \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \
ActionsKeybinds
Open Options
Close Options or QR
Quick spoiler
Open QR with post number inserted
Open QR without post number inserted
Submit post
Select next reply
Select previous reply
See next thread
See previous thread
Jump to the next page
Jump to the previous page
Jump to page 0
Open thread in current tab
Open thread in new tab
Expand thread
Watch thread
Hide thread
Expand selected image
Expand all images
Update now
Reset the unread count to 0
\
\
' @@ -2093,17 +2090,21 @@ favicon.value = conf['favicon']; $.on(favicon, 'change', $.cb.value); $.on(favicon, 'change', options.favicon); - _ref3 = $$('#keybinds_tab + div input', dialog); - for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) { - input = _ref3[_j]; - input.type = 'text'; - input.value = conf[input.name]; + _ref3 = config.hotkeys; + for (key in _ref3) { + arr = _ref3[key]; + tr = $.el('tr', { + innerHTML: "" + arr[1] + "" + }); + input = $('input', tr); + input.value = conf[key]; $.on(input, 'keydown', options.keybind); + $.add($('#keybinds_tab + div tbody', dialog), tr); } indicators = {}; _ref4 = $$('.warning', dialog); - for (_k = 0, _len3 = _ref4.length; _k < _len3; _k++) { - indicator = _ref4[_k]; + for (_j = 0, _len2 = _ref4.length; _j < _len2; _j++) { + indicator = _ref4[_j]; key = indicator.firstChild.textContent; indicator.hidden = conf[key]; indicators[key] = indicator; @@ -2573,26 +2574,40 @@ sauce = { init: function() { - if (!(sauce.prefixes = conf['flavors'].match(/^[^#].+$/gm))) return; - sauce.names = sauce.prefixes.map(function(prefix) { - return prefix.match(/(\w+)\./)[1]; - }); - return g.callbacks.push(function(root) { - var i, link, prefix, span, suffix, _len, _ref, _results; - if (root.className === 'inline' || !(span = $('.filesize', root))) return; - suffix = $('a', span).href; - _ref = sauce.prefixes; - _results = []; - for (i = 0, _len = _ref.length; i < _len; i++) { - prefix = _ref[i]; - link = $.el('a', { - textContent: sauce.names[i], - href: prefix + suffix, - target: '_blank' - }); - _results.push($.add(span, $.tn(' '), link)); + var link, links, _i, _len; + links = conf['sauces'].match(/^[^#].+$/gm); + this.links = []; + for (_i = 0, _len = links.length; _i < _len; _i++) { + link = links[_i]; + this.links.push([link, link.match(/(\w+)\.\w+\//)[1]]); + } + return g.callbacks.push(this.node); + }, + node: function(root) { + var a, img, link, span, _i, _len, _ref; + if (root.className === 'inline' || !(span = $('.filesize', root))) return; + img = $('img', root); + _ref = sauce.links; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + link = _ref[_i]; + a = $.el('a', { + textContent: link[1], + href: sauce.href(link[0], img), + target: '_blank' + }); + $.add(span, $.tn(' '), a); + } + }, + href: function(link, img) { + return link.replace(/\$\d/, function(fragment) { + switch (fragment) { + case '$1': + return img.src; + case '$2': + return img.parentNode.href; + case '$3': + return img.getAttribute('md5').replace(/\=+$/, ''); } - return _results; }); } }; @@ -2739,8 +2754,8 @@ quoteBacklink = { init: function() { var format; - format = conf['backlink'].replace(/%id/, "' + id + '"); - quoteBacklink.funk = Function('id', "return'" + format + "'"); + format = conf['backlink'].replace(/%id/g, "' + id + '"); + quoteBacklink.funk = Function('id', "return '" + format + "'"); return g.callbacks.push(function(root) { var a, container, el, id, link, qid, quote, quotes, _i, _len, _ref, _results; if (/\binline\b/.test(root.className)) return; @@ -2748,7 +2763,7 @@ _ref = $$('.quotelink', root); for (_i = 0, _len = _ref.length; _i < _len; _i++) { quote = _ref[_i]; - if (qid = quote.hash.slice(1)) quotes[qid] = quote; + if (qid = quote.hash.slice(1)) quotes[qid] = true; } id = $('input', root).name; a = $.el('a', { @@ -2758,8 +2773,9 @@ }); _results = []; for (qid in quotes) { - if (!(el = $.id(qid))) continue; - if (el.className === 'op' && !conf['OP Backlinks']) continue; + if (!(el = $.id(qid)) || el.className === 'op' && !conf['OP Backlinks']) { + continue; + } link = a.cloneNode(true); if (conf['Quote Preview']) { $.on(link, 'mouseover', quotePreview.mouseover); @@ -3161,15 +3177,11 @@ empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==', dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==', update: function() { - var clone, favicon, l; + var favicon, l; l = unread.replies.length; favicon = $('link[rel="shortcut icon"]', d.head); favicon.href = g.dead ? l ? this.unreadDead : this.dead : l ? this.unread : this["default"]; - if (engine !== 'webkit') { - clone = favicon.cloneNode(true); - favicon.href = null; - return $.replace(favicon, clone); - } + if (engine !== 'webkit') return $.add(d.head, $.rm(favicon)); } }; @@ -3291,7 +3303,7 @@ var thumb, _i, _j, _len, _len2, _ref, _ref2, _results, _results2; imgExpand.on = this.checked; if (imgExpand.on) { - _ref = $$('.op > a > img[md5]:last-child, table:not([hidden]) img[md5]:last-child'); + _ref = $$('img[md5]'); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { thumb = _ref[_i]; @@ -3347,22 +3359,21 @@ }, contract: function(thumb) { thumb.hidden = false; - return $.rm(thumb.nextSibling); + return thumb.nextSibling.hidden = true; }, expand: function(thumb, url) { - var a, filesize, img, max; - if (thumb.hidden) return; + var a, img; + if ($.x('ancestor-or-self::*[@hidden]', thumb)) return; + thumb.hidden = true; + if (img = thumb.nextSibling) { + img.hidden = false; + return; + } a = thumb.parentNode; img = $.el('img', { src: url || a.href }); - if (engine === 'gecko' && a.parentNode.className !== 'op') { - filesize = $.x('preceding-sibling::span[@class="filesize"]', a).textContent; - max = filesize.match(/(\d+)x/); - img.style.maxWidth = "" + max[1] + "px"; - } if (conf['404 Redirect']) $.on(img, 'error', imgExpand.error); - thumb.hidden = true; return $.add(a, img); }, error: function() { @@ -3371,6 +3382,7 @@ thumb = this.previousSibling; src = href.split('/'); imgExpand.contract(thumb); + $.rm(this); if (!(this.src.split('/')[2] === 'images.4chan.org' && (url = redirect.image(src[3], src[5])))) { if (g.dead) return; url = href + '?' + Date.now(); @@ -3833,8 +3845,8 @@ img[md5], img[md5] + img {\ resize: vertical;\ width: 100%;\ }\ -#flavors {\ - height: 100%;\ +#sauces {\ + height: 320px;\ }\ \ #updater {\ @@ -3853,22 +3865,24 @@ img[md5], img[md5] + img {\ }\ \ #watcher {\ + padding-bottom: 5px;\ position: absolute;\ -}\ -#watcher > div {\ overflow: hidden;\ - padding-right: 5px;\ - padding-left: 5px;\ - text-overflow: ellipsis;\ - max-width: 200px;\ white-space: nowrap;\ }\ -#watcher > div.move {\ - text-decoration: underline;\ - padding-top: 5px;\ +#watcher:not(:hover) {\ + max-height: 220px;\ }\ -#watcher > div:last-child {\ - padding-bottom: 5px;\ +#watcher > div {\ + max-width: 200px;\ + overflow: hidden;\ + padding-left: 5px;\ + padding-right: 5px;\ + text-overflow: ellipsis;\ +}\ +#watcher > .move {\ + padding-top: 5px;\ + text-decoration: underline;\ }\ \ #qp {\ diff --git a/Cakefile b/Cakefile index 611a21218..937d63640 100644 --- a/Cakefile +++ b/Cakefile @@ -2,7 +2,7 @@ {exec} = require 'child_process' fs = require 'fs' -VERSION = '2.25.3' +VERSION = '2.25.5' HEADER = """ // ==UserScript== diff --git a/changelog b/changelog index a9fdab8ec..73932bdff 100644 --- a/changelog +++ b/changelog @@ -1,6 +1,18 @@ master - aeosynth prevent post form flicker +- Mayhem + Increase Sauce linking possibilites: + Thumbnails, full images, MD5 hashes. + +2.25.5 +- Mayhem + Hide the normal post form by default, optional. + +2.25.4 +- Mayhem + Fix text inputs not sent/saved correctly in the QR when pasted for example. + Revert hidding normal post form. 2.25.3 - Mayhem diff --git a/latest.js b/latest.js index 4e3a36f0a..ea8868a08 100644 --- a/latest.js +++ b/latest.js @@ -1 +1 @@ -postMessage({version:'2.25.3'},'*'); +postMessage({version:'2.25.5'},'*'); diff --git a/script.coffee b/script.coffee index 4b06573ef..45b5d1d82 100644 --- a/script.coffee +++ b/script.coffee @@ -38,6 +38,7 @@ config = 'Auto Hide QR': [true, 'Automatically hide the quick reply when posting.'] 'Remember Subject': [false, 'Remember the subject field, instead of resetting after posting.'] 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'] + 'Hide Original Post Form': [true, 'Replace the normal post form with a shortcut to open the QR.'] Quoting: 'Quote Backlinks': [true, 'Add quote backlinks'] 'OP Backlinks': [false, 'Add backlinks to the OP'] @@ -56,48 +57,52 @@ config = filename: '' filesize: '' md5: '' - flavors: [ - 'http://iqdb.org/?url=' - 'http://google.com/searchbyimage?image_url=' - '#http://tineye.com/search?url=' - '#http://saucenao.com/search.php?db=999&url=' - '#http://3d.iqdb.org/?url=' - '#http://regex.info/exif.cgi?imgurl=' - '#http://imgur.com/upload?url=' - '#http://ompldr.org/upload?url1=' + sauces: [ + 'http://iqdb.org/?url=$1' + 'http://www.google.com/searchbyimage?image_url=$1' + '#http://tineye.com/search?url=$1' + '#http://saucenao.com/search.php?db=999&url=$1' + '#http://3d.iqdb.org/?url=$1' + '#http://regex.info/exif.cgi?imgurl=$2' + '# uploaders:' + '#http://imgur.com/upload?url=$2' + '#http://ompldr.org/upload?url1=$2' + '# "View Same" in archives:' + '#http://archive.foolz.us/a/image/$3/' + '#http://archive.installgentoo.net/g/image/$3' ].join '\n' time: '%m/%d/%y(%a)%H:%M' backlink: '>>%id' favicon: 'ferongr' hotkeys: - openOptions: 'ctrl+o' - close: 'Esc' - spoiler: 'ctrl+s' - openQR: 'i' - openEmptyQR: 'I' - submit: 'alt+s' - nextReply: 'J' - previousReply: 'K' - nextThread: 'n' - previousThread: 'p' - nextPage: 'L' - previousPage: 'H' - zero: '0' - openThreadTab: 'o' - openThread: 'O' - expandThread: 'e' - watch: 'w' - hide: 'x' - expandImages: 'm' - expandAllImages: 'M' - update: 'u' - unreadCountTo0: 'z' + openOptions: ['ctrl+o', 'Open Options'] + close: ['Esc', 'Close Options or QR'] + spoiler: ['ctrl+s', 'Quick spoiler'] + openQR: ['i', 'Open QR with post number inserted'] + openEmptyQR: ['I', 'Open QR without post number inserted'] + submit: ['alt+s', 'Submit post'] + nextReply: ['J', 'Select next reply'] + previousReply: ['K', 'Select previous reply'] + nextThread: ['n', 'See next thread'] + previousThread: ['p', 'See previous thread'] + nextPage: ['L', 'Jump to the next page'] + previousPage: ['H', 'Jump to the previous page'] + zero: ['0', 'Jump to page 0'] + openThreadTab: ['o', 'Open thread in current tab'] + openThread: ['O', 'Open thread in new tab'] + expandThread: ['e', 'Expand thread'] + watch: ['w', 'Watch thread'] + hide: ['x', 'Hide thread'] + expandImages: ['m', 'Expand selected image'] + expandAllImages: ['M', 'Expand all images'] + update: ['u', 'Update now'] + unreadCountTo0: ['z', 'Reset unread count to 0'] updater: checkbox: - 'Scrolling': [false, 'Scroll updated posts into view. Only enabled at bottom of page.'] - 'Scroll BG': [false, 'Scroll background tabs'] - 'Verbose': [true, 'Show countdown timer, new post count'] - 'Auto Update': [true, 'Automatically fetch new posts'] + 'Scrolling': [false, 'Scroll updated posts into view. Only enabled at bottom of page.'] + 'Scroll BG': [false, 'Scroll background tabs'] + 'Verbose': [true, 'Show countdown timer, new post count'] + 'Auto Update': [true, 'Automatically fetch new posts'] 'Interval': 30 # XXX Chrome can't into {log} = console @@ -107,20 +112,19 @@ log = console.log.bind? console # flatten the config conf = {} (flatten = (parent, obj) -> - if obj.length #array - if typeof obj[0] is 'boolean' + if typeof obj is 'object' + # array + if obj.length conf[parent] = obj[0] - else - conf[parent] = obj - else if typeof obj is 'object' - for key, val of obj + # object + else for key, val of obj flatten key, val - else #constant + else # string or number conf[parent] = obj ) null, config NAMESPACE = '4chan_x.' -VERSION = '2.25.3' +VERSION = '2.25.5' SECOND = 1000 MINUTE = 60*SECOND HOUR = 60*MINUTE @@ -217,7 +221,7 @@ $ = (selector, root=d.body) -> $.extend = (object, properties) -> for key, val of properties object[key] = val - object + return $.extend $, ready: (fc) -> @@ -282,6 +286,7 @@ $.extend $, add: (parent, children...) -> for child in children parent.appendChild child + return prepend: (parent, child) -> parent.insertBefore child, parent.firstChild after: (root, el) -> @@ -862,23 +867,28 @@ nav = qr = init: -> return unless $.id 'recaptcha_challenge_field_holder' - h1 = $.el 'h1' - innerHTML: 'Open the Quick Reply' - $.on $('a', h1), 'click', qr.open - $.add $('.postarea'), h1 + if conf['Hide Original Post Form'] + link = $.el 'h1', innerHTML: "#{if g.REPLY then 'Open the Quick Reply' else 'Create a New Thread'}" + $.on $('a', link), 'click', qr.open + form = d.forms[0] + form.hidden = true + $.before form, link g.callbacks.push (root) -> $.on $('.quotejs + .quotejs', root), 'click', qr.quote + iframe = $.el 'iframe', id: 'iframe' hidden: true src: 'http://sys.4chan.org/post' $.on iframe, 'error', -> @src = @src # Greasemonkey ghetto fix - $.on iframe, 'load', -> - unless qr.status.ready or @src is 'about:blank' - @src = 'about:blank' - setTimeout (=> @src = 'http://sys.4chan.org/post'), 250 + loadChecking = (iframe) -> + unless qr.status.ready + iframe.src = 'about:blank' + setTimeout (-> iframe.src = 'http://sys.4chan.org/post'), 250 + $.on iframe, 'load', -> unless @src is 'about:blank' then setTimeout loadChecking, 250, @ $.add d.body, iframe + if conf['Persistent QR'] qr.dialog() qr.hide() if conf['Auto Hide QR'] @@ -984,15 +994,15 @@ qr = ta = $ 'textarea', qr.el caretPos = ta.selectionStart # Replace selection for text. - ta.value = ta.value[0...caretPos] + text + ta.value[ta.selectionEnd...ta.value.length] + # onchange event isn't triggered, save value. + qr.selected.el.lastChild.textContent = + qr.selected.com = + ta.value = + ta.value[0...caretPos] + text + ta.value[ta.selectionEnd...ta.value.length] ta.focus() # Move the caret to the end of the new quote. ta.selectionEnd = ta.selectionStart = caretPos + text.length - # onchange event isn't triggered, save value. - qr.selected.com = ta.value - qr.selected.el.lastChild.textContent = ta.value - fileDrop: (e) -> return if /TEXTAREA|INPUT/.test e.target.nodeName e.preventDefault() @@ -1129,9 +1139,14 @@ qr = @img.src = "http://www.google.com/recaptcha/api/image?c=#{challenge}" @input.value = null count: (count) -> - s = if count is 1 then '' else 's' - @input.placeholder = "Verification (#{count} cached captcha#{s})" - @input.alt = count # For XTRM RICE. + @input.placeholder = switch count + when 0 + 'Verification (Shift + Enter to cache)' + when 1 + 'Vertification (1 cached captcha)' + else + "Verification (#{count} cached captchas)" + @input.alt = count # For XTRM RICE. reload: (focus) -> window.location = 'javascript:Recaptcha.reload()' # Focus if we meant to. @@ -1150,11 +1165,11 @@ qr = qr.el = ui.dialog 'qr', 'top:0;right:0;', '
Quick Reply - x + x
- +
@@ -1186,7 +1201,9 @@ qr = threads = '' for thread in $$ '.op' threads += "" - $.prepend $('.move > span', qr.el), $.el 'select', innerHTML: threads + $.prepend $('.move > span', qr.el), $.el 'select' + innerHTML: threads + title: 'Create a new thread / Reply to a thread' $.on $('select', qr.el), 'mousedown', (e) -> e.stopPropagation() $.on $('#autohide', qr.el), 'change', qr.toggleHide $.on $('.close', qr.el), 'click', qr.close @@ -1200,8 +1217,10 @@ qr = new qr.reply().select() # save selected reply's data - for input in ['name', 'email', 'sub', 'com'] - $.on $("[name=#{input}]", qr.el), 'keyup', -> qr.selected[@name] = @value + for name in ['name', 'email', 'sub', 'com'] + input = $ "[name=#{name}]", qr.el + $.on input, 'keyup', -> qr.selected[@name] = @value + $.on input, 'change', -> qr.selected[@name] = @value # sync between tabs $.sync 'qr.persona', (persona) -> return if qr.replies.length isnt 1 @@ -1502,7 +1521,7 @@ options =
| - | + | | |
@@ -1511,10 +1530,16 @@ options =
- +
Sauce is disabled.
- +
Lines starting with a # will be ignored.
+
    These variables will be replaced by the corresponding url: +
  • $1: Thumbnail.
  • +
  • $2: Full image.
  • +
  • $3: MD5 hash.
  • +
+
@@ -1562,30 +1587,9 @@ options =
Keybinds are disabled.
+
Allowed keys: Ctrl, Alt, a-z, A-Z, 0-1, Up, Down, Right, Left.
- - - - - - - - - - - - - - - - - - - - - -
ActionsKeybinds
Open Options
Close Options or QR
Quick spoiler
Open QR with post number inserted
Open QR without post number inserted
Submit post
Select next reply
Select previous reply
See next thread
See previous thread
Jump to the next page
Jump to the previous page
Jump to page 0
Open thread in current tab
Open thread in new tab
Expand thread
Watch thread
Hide thread
Expand selected image
Expand all images
Update now
Reset the unread count to 0
' @@ -1628,10 +1632,13 @@ options = $.on favicon, 'change', options.favicon #keybinds - for input in $$ '#keybinds_tab + div input', dialog - input.type = 'text' - input.value = conf[input.name] + for key, arr of config.hotkeys + tr = $.el 'tr', + innerHTML: "#{arr[1]}" + input = $ 'input', tr + input.value = conf[key] $.on input, 'keydown', options.keybind + $.add $('#keybinds_tab + div tbody', dialog), tr #indicate if the settings require a feature to be enabled indicators = {} @@ -2029,17 +2036,31 @@ anonymize = sauce = init: -> - return unless sauce.prefixes = conf['flavors'].match /^[^#].+$/gm - sauce.names = sauce.prefixes.map (prefix) -> prefix.match(/(\w+)\./)[1] - g.callbacks.push (root) -> - return if root.className is 'inline' or not span = $ '.filesize', root - suffix = $('a', span).href - for prefix, i in sauce.prefixes - link = $.el 'a', - textContent: sauce.names[i] - href: prefix + suffix - target: '_blank' - $.add span, $.tn(' '), link + # return unless + links = conf['sauces'].match /^[^#].+$/gm + @links = [] + for link in links + @links.push [link, link.match(/(\w+)\.\w+\//)[1]] + g.callbacks.push @node + node: (root) -> + return if root.className is 'inline' or not span = $ '.filesize', root + img = $ 'img', root + for link in sauce.links + a = $.el 'a', + textContent: link[1] + href: sauce.href link[0], img + target: '_blank' + $.add span, $.tn(' '), a + return + href: (link, img) -> + link.replace /\$\d/, (fragment) -> + switch fragment + when '$1' + img.src + when '$2' + img.parentNode.href + when '$3' + img.getAttribute('md5').replace /\=+$/, '' revealSpoilers = init: -> @@ -2142,26 +2163,25 @@ titlePost = quoteBacklink = init: -> - format = conf['backlink'].replace /%id/, "' + id + '" - quoteBacklink.funk = Function 'id', "return'#{format}'" + format = conf['backlink'].replace /%id/g, "' + id + '" + quoteBacklink.funk = Function 'id', "return '#{format}'" g.callbacks.push (root) -> return if /\binline\b/.test root.className quotes = {} for quote in $$ '.quotelink', root - #don't process >>>/b/ + # Don't process >>>/b/. if qid = quote.hash[1..] - #duplicate quotes get overwritten - quotes[qid] = quote - # op or reply + # Duplicate quotes get overwritten. + quotes[qid] = true + # OP or reply id. id = $('input', root).name a = $.el 'a', href: "##{id}" className: if root.hidden then 'filtered backlink' else 'backlink' textContent: quoteBacklink.funk id for qid of quotes - continue unless el = $.id qid - #don't backlink the op - continue if el.className is 'op' and !conf['OP Backlinks'] + # Don't backlink the OP. + continue if !(el = $.id qid) or el.className is 'op' and !conf['OP Backlinks'] link = a.cloneNode true if conf['Quote Preview'] $.on link, 'mouseover', quotePreview.mouseover @@ -2457,9 +2477,7 @@ Favicon = #`favicon.href = href` isn't enough on Opera #Opera won't always update the favicon if the href do not change if engine isnt 'webkit' - clone = favicon.cloneNode true - favicon.href = null - $.replace favicon, clone + $.add d.head, $.rm favicon redirect = init: -> @@ -2526,7 +2544,7 @@ imgExpand = all: -> imgExpand.on = @checked if imgExpand.on #expand - for thumb in $$ '.op > a > img[md5]:last-child, table:not([hidden]) img[md5]:last-child' + for thumb in $$ 'img[md5]' imgExpand.expand thumb else #contract for thumb in $$ 'img[md5][hidden]' @@ -2562,19 +2580,20 @@ imgExpand = contract: (thumb) -> thumb.hidden = false - $.rm thumb.nextSibling + thumb.nextSibling.hidden = true expand: (thumb, url) -> - return if thumb.hidden + # Do not expand images of hidden/filtered replies, or already expanded pictures. + return if $.x 'ancestor-or-self::*[@hidden]', thumb + thumb.hidden = true + if img = thumb.nextSibling + # Expand already loaded picture + img.hidden = false + return a = thumb.parentNode img = $.el 'img', src: url or a.href - if engine is 'gecko' and a.parentNode.className isnt 'op' - filesize = $.x('preceding-sibling::span[@class="filesize"]', a).textContent - max = filesize.match /(\d+)x/ - img.style.maxWidth = "#{max[1]}px" $.on img, 'error', imgExpand.error if conf['404 Redirect'] - thumb.hidden = true $.add a, img error: -> @@ -2582,6 +2601,7 @@ imgExpand = thumb = @previousSibling src = href.split '/' imgExpand.contract thumb + $.rm @ unless @src.split('/')[2] is 'images.4chan.org' and url = redirect.image src[3], src[5] return if g.dead # CloudFlare may cache banned pages instead of images. @@ -3075,8 +3095,8 @@ img[md5], img[md5] + img { resize: vertical; width: 100%; } -#flavors { - height: 100%; +#sauces { + height: 320px; } #updater { @@ -3095,22 +3115,24 @@ img[md5], img[md5] + img { } #watcher { + padding-bottom: 5px; position: absolute; -} -#watcher > div { overflow: hidden; - padding-right: 5px; - padding-left: 5px; - text-overflow: ellipsis; - max-width: 200px; white-space: nowrap; } -#watcher > div.move { - text-decoration: underline; - padding-top: 5px; +#watcher:not(:hover) { + max-height: 220px; } -#watcher > div:last-child { - padding-bottom: 5px; +#watcher > div { + max-width: 200px; + overflow: hidden; + padding-left: 5px; + padding-right: 5px; + text-overflow: ellipsis; +} +#watcher > .move { + padding-top: 5px; + text-decoration: underline; } #qp {