From f02688bd0aa5018565292b3c892af73704fee35c Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 21 Feb 2012 15:10:35 +0100 Subject: [PATCH 1/6] Let the filter add more than one highlight class. --- 4chan_x.user.js | 1 + script.coffee | 2 ++ 2 files changed, 3 insertions(+) diff --git a/4chan_x.user.js b/4chan_x.user.js index cb9926cc9..47a47776f 100644 --- a/4chan_x.user.js +++ b/4chan_x.user.js @@ -576,6 +576,7 @@ $.before(firstThread.parentNode, [thisThread, thisThread.nextElementSibling]); } } + return false; } else if (isOP) { if (!g.REPLY) threadHiding.hideHide(root.parentNode); } else { diff --git a/script.coffee b/script.coffee index d9b59f856..ef0b5c4c6 100644 --- a/script.coffee +++ b/script.coffee @@ -491,6 +491,8 @@ filter = # ...before the first non highlighted thread. if firstThread = $ 'div[class=op]' $.before firstThread.parentNode, [thisThread, thisThread.nextElementSibling] + # Continue the filter lookup to add more classes or hide it. + return false else if isOP unless g.REPLY threadHiding.hideHide root.parentNode From ddee297a36f68ab4e72d6e7f2890da2fe847334f Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 21 Feb 2012 15:36:09 +0100 Subject: [PATCH 2/6] Add top option for highlighting filters. --- 4chan_x.user.js | 20 ++++++++++++-------- script.coffee | 21 +++++++++++++-------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/4chan_x.user.js b/4chan_x.user.js index 47a47776f..b2b765be3 100644 --- a/4chan_x.user.js +++ b/4chan_x.user.js @@ -141,7 +141,7 @@ subject: ['# Filter Generals on /v/:', '#/general/i;boards:v;op:only'].join('\n'), comment: ['# Filter Stallman copypasta on /g/:', '#/what you\'re refer+ing to as linux/i;boards:g'].join('\n'), filename: [''].join('\n'), - dimensions: ['# Highlight potential wallpapers:', '#/1920x1080/;op:yes;highlight;boards:w,wg'].join('\n'), + dimensions: ['# Highlight potential wallpapers:', '#/1920x1080/;op:yes;highlight;top:no;boards:w,wg'].join('\n'), filesize: [''].join('\n'), md5: [''].join('\n') }, @@ -534,7 +534,7 @@ filter = { filters: {}, init: function() { - var boards, filter, hl, key, op, regexp, _i, _len, _ref, _ref2, _ref3, _ref4; + var boards, filter, hl, key, op, regexp, top, _i, _len, _ref, _ref2, _ref3, _ref4, _ref5; for (key in config.filter) { this.filters[key] = []; _ref = conf[key].split('\n'); @@ -553,31 +553,34 @@ alert(e.message); continue; } - op = ((_ref3 = filter.match(/op:(yes|no|only)/)) != null ? _ref3[1].toLowerCase() : void 0) || 'no'; + op = ((_ref3 = filter.match(/[^t]op:(yes|no|only)/)) != null ? _ref3[1].toLowerCase() : void 0) || 'no'; if (hl = /highlight/.test(filter)) { hl = ((_ref4 = filter.match(/highlight:(\w+)/)) != null ? _ref4[1].toLowerCase() : void 0) || 'filter_highlight'; + top = ((_ref5 = filter.match(/top:(yes|no)/)) != null ? _ref5[1].toLowerCase() : void 0) || 'yes'; + top = top === 'yes'; } - this.filters[key].push(this.createFilter(regexp, op, hl)); + this.filters[key].push(this.createFilter(regexp, op, hl, top)); } if (!this.filters[key].length) delete this.filters[key]; } if (Object.keys(this.filters).length) return g.callbacks.push(this.node); }, - createFilter: function(regexp, op, hl) { + createFilter: function(regexp, op, hl, top) { return function(root, value, isOP) { var firstThread, thisThread; if (isOP && op === 'no' || !isOP && op === 'only') return false; if (!regexp.test(value)) return false; if (hl) { $.addClass(root, hl); - if (isOP && !g.REPLY) { + if (isOP && top && !g.REPLY) { thisThread = root.parentNode; if (firstThread = $('div[class=op]')) { $.before(firstThread.parentNode, [thisThread, thisThread.nextElementSibling]); } } return false; - } else if (isOP) { + } + if (isOP) { if (!g.REPLY) threadHiding.hideHide(root.parentNode); } else { replyHiding.hideHide(root.previousSibling); @@ -2125,7 +2128,8 @@ \

Name:

\

Tripcode:

\ diff --git a/script.coffee b/script.coffee index ef0b5c4c6..a16d96d43 100644 --- a/script.coffee +++ b/script.coffee @@ -75,7 +75,7 @@ config = ].join '\n' dimensions: [ '# Highlight potential wallpapers:' - '#/1920x1080/;op:yes;highlight;boards:w,wg' + '#/1920x1080/;op:yes;highlight;top:no;boards:w,wg' ].join '\n' filesize: [ '' @@ -460,15 +460,19 @@ filter = # Filter OPs along with their threads, replies only, or both. # Defaults to replies only. - op = filter.match(/op:(yes|no|only)/)?[1].toLowerCase() or 'no' + op = filter.match(/[^t]op:(yes|no|only)/)?[1].toLowerCase() or 'no' # Highlight the post, or hide it. # If not specified, the highlight class will be filter_highlight. # Defaults to post hiding. if hl = /highlight/.test filter - hl = filter.match(/highlight:(\w+)/)?[1].toLowerCase() or 'filter_highlight' + hl = filter.match(/highlight:(\w+)/)?[1].toLowerCase() or 'filter_highlight' + # Put highlighted OP's thread on top of the board page or not. + # Defaults to on top. + top = filter.match(/top:(yes|no)/)?[1].toLowerCase() or 'yes' + top = top is 'yes' # Turn it into a boolean - @filters[key].push @createFilter regexp, op, hl + @filters[key].push @createFilter regexp, op, hl, top # Only execute filter types that contain valid filters. unless @filters[key].length @@ -477,7 +481,7 @@ filter = if Object.keys(@filters).length g.callbacks.push @node - createFilter: (regexp, op, hl) -> + createFilter: (regexp, op, hl, top) -> (root, value, isOP) -> if isOP and op is 'no' or !isOP and op is 'only' return false @@ -485,7 +489,7 @@ filter = return false if hl $.addClass root, hl - if isOP and not g.REPLY + if isOP and top and not g.REPLY # Put the highlighted OPs' threads on top of the board pages... thisThread = root.parentNode # ...before the first non highlighted thread. @@ -493,7 +497,7 @@ filter = $.before firstThread.parentNode, [thisThread, thisThread.nextElementSibling] # Continue the filter lookup to add more classes or hide it. return false - else if isOP + if isOP unless g.REPLY threadHiding.hideHide root.parentNode else @@ -1719,7 +1723,8 @@ options =

Name:

Tripcode:

From 6e0cd10b707936ef0bce5707ca7f21b874fc0eaa Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 21 Feb 2012 15:45:08 +0100 Subject: [PATCH 3/6] Add two controversial filter examples. --- 4chan_x.user.js | 4 ++-- script.coffee | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/4chan_x.user.js b/4chan_x.user.js index 91ca8a838..370e5793e 100644 --- a/4chan_x.user.js +++ b/4chan_x.user.js @@ -136,8 +136,8 @@ } }, filter: { - name: [''].join('\n'), - tripcode: [''].join('\n'), + name: ['# Filter any namefags:', '#/^(?!Anonymous$)/'].join('\n'), + tripcode: ['# Filter any tripfags', '#/^!/'].join('\n'), email: ['# Filter any e-mails that are not `sage` on /a/ and /jp/:', '#/^(?!sage$)/;boards:a,jp'].join('\n'), subject: ['# Filter Generals on /v/:', '#/general/i;boards:v;op:only'].join('\n'), comment: ['# Filter Stallman copypasta on /g/:', '#/what you\'re refer+ing to as linux/i;boards:g'].join('\n'), diff --git a/script.coffee b/script.coffee index 720a5aeba..1200b539e 100644 --- a/script.coffee +++ b/script.coffee @@ -54,10 +54,12 @@ config = 'Forward Hiding': [true, 'Hide original posts of inlined backlinks'] filter: name: [ - '' + '# Filter any namefags:' + '#/^(?!Anonymous$)/' ].join '\n' tripcode: [ - '' + '# Filter any tripfags' + '#/^!/' ].join '\n' email: [ '# Filter any e-mails that are not `sage` on /a/ and /jp/:' From 718b204e7a476c8b3b939b3ac9b29601c48b4a86 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 21 Feb 2012 15:46:43 +0100 Subject: [PATCH 4/6] Spaces. --- script.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script.coffee b/script.coffee index 1200b539e..344b03cfb 100644 --- a/script.coffee +++ b/script.coffee @@ -13,8 +13,8 @@ config = 'Check for Updates': [true, 'Check for updated versions of 4chan X'] Filtering: 'Anonymize': [false, 'Make everybody anonymous'] - 'Filter': [true, 'Self-moderation placebo'] - 'Recursive Filtering': [true, 'Filter replies of filtered posts, recursively'] + 'Filter': [true, 'Self-moderation placebo'] + 'Recursive Filtering': [true, 'Filter replies of filtered posts, recursively'] 'Reply Hiding': [true, 'Hide single replies'] 'Thread Hiding': [true, 'Hide entire threads'] 'Show Stubs': [true, 'Of hidden threads / replies'] From 15ee8ff6c5b70a767cd2b9aa736c4df11a90526e Mon Sep 17 00:00:00 2001 From: ahodesuka Date: Tue, 21 Feb 2012 10:25:15 -0600 Subject: [PATCH 5/6] Scroll back up to anchor. --- 4chan_x.user.js | 205 +++++++++++++++++++++++++--------------- changelog | 13 ++- script.coffee | 243 +++++++++++++++++++++++++++++++++++------------- 3 files changed, 323 insertions(+), 138 deletions(-) diff --git a/4chan_x.user.js b/4chan_x.user.js index e365c8737..568af7011 100644 --- a/4chan_x.user.js +++ b/4chan_x.user.js @@ -90,9 +90,8 @@ }, Filtering: { 'Anonymize': [false, 'Make everybody anonymous'], - 'Filter': [false, 'Self-moderation placebo'], - 'Filter OPs': [false, 'Filter OPs along with their threads'], - 'Recursive Filtering': [false, 'Filter replies of filtered posts, recursively'], + 'Filter': [true, 'Self-moderation placebo'], + 'Recursive Filtering': [true, 'Filter replies of filtered posts, recursively'], 'Reply Hiding': [true, 'Hide single replies'], 'Thread Hiding': [true, 'Hide entire threads'], 'Show Stubs': [true, 'Of hidden threads / replies'] @@ -119,6 +118,7 @@ 'Cooldown': [true, 'Prevent "flood detected" errors.'], 'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.'], 'Auto Hide QR': [true, 'Automatically hide the quick reply when posting.'], + 'Open Reply in New Tab': [false, 'Open replies in a new tab that are made from the main board.'], 'Remember QR size': [false, 'Remember the size of the Quick reply (Firefox only).'], 'Remember Subject': [false, 'Remember the subject field, instead of resetting after posting.'], 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'], @@ -136,14 +136,15 @@ } }, filter: { - name: '', - tripcode: '', - email: '', - subject: '', - comment: '', - filename: '', - filesize: '', - md5: '' + name: ['# Filter any namefags:', '#/^(?!Anonymous$)/'].join('\n'), + tripcode: ['# Filter any tripfags', '#/^!/'].join('\n'), + email: ['# Filter any e-mails that are not `sage` on /a/ and /jp/:', '#/^(?!sage$)/;boards:a,jp'].join('\n'), + subject: ['# Filter Generals on /v/:', '#/general/i;boards:v;op:only'].join('\n'), + comment: ['# Filter Stallman copypasta on /g/:', '#/what you\'re refer+ing to as linux/i;boards:g'].join('\n'), + filename: [''].join('\n'), + dimensions: ['# Highlight potential wallpapers:', '#/1920x1080/;op:yes;highlight;top:no;boards:w,wg'].join('\n'), + filesize: [''].join('\n'), + md5: [''].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://omploader.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', @@ -532,86 +533,126 @@ }; filter = { - regexps: {}, - callbacks: [], + filters: {}, init: function() { - var f, filter, key, m, _i, _len; + var boards, filter, hl, key, op, regexp, top, _i, _len, _ref, _ref2, _ref3, _ref4, _ref5; for (key in config.filter) { - if (!(m = conf[key].match(/^\/.+\/\w*$/gm))) continue; - this.regexps[key] = []; - for (_i = 0, _len = m.length; _i < _len; _i++) { - filter = m[_i]; - f = filter.match(/^\/(.+)\/(\w*)$/); + this.filters[key] = []; + _ref = conf[key].split('\n'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + filter = _ref[_i]; + if (filter[0] === '#') continue; + if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) continue; + filter = filter.replace(regexp[0], ''); + boards = ((_ref2 = filter.match(/boards:([^;]+)/)) != null ? _ref2[1].toLowerCase() : void 0) || 'global'; + if (boards !== 'global' && boards.split(',').indexOf(g.BOARD) === -1) { + continue; + } try { - this.regexps[key].push(RegExp(f[1], f[2])); + regexp = RegExp(regexp[1], regexp[2]); } catch (e) { alert(e.message); + continue; } + op = ((_ref3 = filter.match(/[^t]op:(yes|no|only)/)) != null ? _ref3[1].toLowerCase() : void 0) || 'no'; + if (hl = /highlight/.test(filter)) { + hl = ((_ref4 = filter.match(/highlight:(\w+)/)) != null ? _ref4[1].toLowerCase() : void 0) || 'filter_highlight'; + top = ((_ref5 = filter.match(/top:(yes|no)/)) != null ? _ref5[1].toLowerCase() : void 0) || 'yes'; + top = top === 'yes'; + } + this.filters[key].push(this.createFilter(regexp, op, hl, top)); } - this.callbacks.push(this[key]); + if (!this.filters[key].length) delete this.filters[key]; } - return g.callbacks.push(this.node); + if (Object.keys(this.filters).length) return g.callbacks.push(this.node); + }, + createFilter: function(regexp, op, hl, top) { + return function(root, value, isOP) { + var firstThread, thisThread; + if (isOP && op === 'no' || !isOP && op === 'only') return false; + if (!regexp.test(value)) return false; + if (hl) { + $.addClass(root, hl); + if (isOP && top && !g.REPLY) { + thisThread = root.parentNode; + if (firstThread = $('div[class=op]')) { + $.before(firstThread.parentNode, [thisThread, thisThread.nextElementSibling]); + } + } + return false; + } + if (isOP) { + if (!g.REPLY) threadHiding.hideHide(root.parentNode); + } else { + replyHiding.hideHide(root.previousSibling); + } + return true; + }; }, node: function(root) { - if (!root.className) { - if (filter.callbacks.some(function(callback) { - return callback(root); - })) { - return replyHiding.hideHide($('td:not([nowrap])', root)); - } - } else if (root.className === 'op' && !g.REPLY && conf['Filter OPs']) { - if (filter.callbacks.some(function(callback) { - return callback(root); - })) { - return threadHiding.hideHide(root.parentNode); - } + var isOP, key, klass; + klass = root.className; + if (/\binlined\b/.test(klass)) return; + if (!(isOP = klass === 'op')) root = $('td[id]', root); + for (key in filter.filters) { + if (filter.test(root, key, isOP)) return; } }, - test: function(key, value) { - return filter.regexps[key].some(function(regexp) { - return regexp.test(value); - }); + test: function(root, key, isOP) { + var filter, value, _i, _len, _ref; + value = this[key](root, isOP); + if (value === false) return false; + _ref = this.filters[key]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + filter = _ref[_i]; + if (filter(root, value, isOP)) return true; + } + return false; }, - name: function(root) { + name: function(root, isOP) { var name; - name = root.className === 'op' ? $('.postername', root) : $('.commentpostername', root); - return filter.test('name', name.textContent); + name = isOP ? $('.postername', root) : $('.commentpostername', root); + return name.textContent; }, tripcode: function(root) { var trip; - if (trip = $('.postertrip', root)) { - return filter.test('tripcode', trip.textContent); - } + if (trip = $('.postertrip', root)) return trip.textContent; + return false; }, email: function(root) { var mail; - if (mail = $('.linkmail', root)) return filter.test('email', mail.href); + if (!(mail = $('.linkmail', root))) return mail.href; + return false; }, - subject: function(root) { + subject: function(root, isOP) { var sub; - sub = root.className === 'op' ? $('.filetitle', root) : $('.replytitle', root); - return filter.test('subject', sub.textContent); + sub = isOP ? $('.filetitle', root) : $('.replytitle', root); + return sub.textContent; }, comment: function(root) { - return filter.test('comment', ($.el('a', { + return ($.el('a', { innerHTML: $('blockquote', root).innerHTML.replace(/
/g, '\n') - })).textContent); + })).textContent; }, filename: function(root) { var file; - if (file = $('.filesize span', root)) { - return filter.test('filename', file.title); - } + if (file = $('.filesize > span', root)) return file.title; + return false; + }, + dimensions: function(root) { + var span; + if (span = $('.filesize', root)) return span.textContent.match(/\d+x\d+/)[0]; + return false; }, filesize: function(root) { var img; - if (img = $('img[md5]', root)) return filter.test('filesize', img.alt); + if (img = $('img[md5]', root)) return img.alt; + return false; }, md5: function(root) { var img; - if (img = $('img[md5]', root)) { - return filter.test('md5', img.getAttribute('md5')); - } + if (img = $('img[md5]', root)) return img.getAttribute('md5'); + return false; } }; @@ -1223,7 +1264,7 @@ $.before(form, link); } g.callbacks.push(this.node); - if (engine === 'webkit') { + if (/chrome/i.test(navigator.userAgent)) { qr.status({ ready: true }); @@ -1379,7 +1420,7 @@ if (e != null) e.preventDefault(); qr.open(); if (!g.REPLY) { - $('select', qr.el).value = $.x('ancestor::div', this).firstChild.id; + $('select', qr.el).value = $.x('ancestor::div[@class="thread"]', this).firstChild.id; } id = this.previousElementSibling.hash.slice(1); text = ">>" + id + "\n"; @@ -1678,7 +1719,7 @@ }); ta.style.cssText = $.get('qr.size', ''); } - mimeTypes = $('.rules').textContent.match(/: (.+) /)[1].toLowerCase().replace(/\w+/g, function(type) { + mimeTypes = $('.rules').firstChild.textContent.match(/: (.+) /)[1].toLowerCase().replace(/\w+/g, function(type) { switch (type) { case 'jpg': return 'image/jpeg'; @@ -1832,14 +1873,14 @@ reader.readAsBinaryString(reply.file); return; } - if (engine === 'webkit') { + if (/chrome/i.test(navigator.userAgent)) { qr.message.post(post); return; } return qr.message.send(post); }, response: function(html) { - var b, doc, err, node, persona, postNumber, reply, thread, _, _ref; + var b, doc, err, node, open, persona, postNumber, reply, thread, _, _ref; doc = $.el('a', { innerHTML: html }); @@ -1887,6 +1928,10 @@ } else { qr.cooldown.auto = qr.replies.length > 1; qr.cooldown.set(/sage/i.test(reply.email) ? 60 : 30); + if (conf['Open Reply in New Tab'] && !g.REPLY && !qr.cooldown.auto) { + open = GM_openInTab || window.open; + open("http://boards.4chan.org/" + g.BOARD + "/res/" + thread + "#" + postNumber, "_blank"); + } } if (conf['Persistent QR'] || qr.cooldown.auto) { reply.rm(); @@ -1899,7 +1944,7 @@ message: { send: function(data) { var host, window; - if (engine === 'webkit') { + if (/chrome/i.test(navigator.userAgent)) { qr.message.receive(data); return; } @@ -2071,7 +2116,7 @@ \
\
Sauce is disabled.
\ -
Lines starting with a # will be ignored.
\ + Lines starting with a # will be ignored.\
    These variables will be replaced by the corresponding url:\
  • $1: Thumbnail.
  • \
  • $2: Full image.
  • \ @@ -2083,13 +2128,21 @@
    \
    Filter is disabled.
    \ Use regular expressions, one per line.
    \ + Lines starting with a # will be ignored.
    \ For example, /weeaboo/i will filter posts containing `weeaboo` case-insensitive.\ +
      You can use these settings with each regular expression, separate them with semicolons:\ +
    • Per boards, separate them with commas. It is global if not specified.
      For example: boards:a,jp;.
    • \ +
    • Filter OPs only along with their threads (`only`), replies only (`no`, this is default), or both (`yes`).
      For example: op:only;, op:no; or op:yes;.
    • \ +
    • Highlight instead of hiding. You can specify a class name to use with a userstyle.
      For example: highlight; or hightlight:wallpaper;.
    • \ +
    • Highlighted OPs will have their threads put on top of board pages by default.
      For example: top:yes or top:no.
    • \ +
    \

    Name:

    \

    Tripcode:

    \

    E-mail:

    \

    Subject:

    \

    Comment:

    \

    Filename:

    \ +

    Image dimensions:

    \

    Filesize:

    \

    Image MD5:

    \
    \ @@ -3099,18 +3152,19 @@ return g.callbacks.push(this.node); }, node: function(root) { - var path, quote, tid, _i, _len, _ref; + var hash, path, quote, tid, _i, _len, _ref; if (root.className === 'inline') return; tid = g.THREAD_ID || $.x('ancestor::div[contains(@class,"thread")]', root).firstChild.id; _ref = $$('.quotelink', root); for (_i = 0, _len = _ref.length; _i < _len; _i++) { quote = _ref[_i]; - if (conf['Indicate OP quote'] && quote.hash.slice(1) === tid) { + hash = quote.hash.slice(1); + if (conf['Indicate OP quote'] && hash === tid) { $.add(quote, $.tn('\u00A0(OP)')); return; } path = quote.pathname; - if (conf['Indicate Cross-thread Quotes'] && path.lastIndexOf("/" + tid) === -1 && path.indexOf("/" + g.BOARD + "/") === 0) { + if (conf['Indicate Cross-thread Quotes'] && hash && path.lastIndexOf("/" + tid) === -1 && path.indexOf("/" + g.BOARD + "/") === 0) { $.add(quote, $.tn('\u00A0(Cross-thread)')); } } @@ -3431,8 +3485,8 @@ var rect, thumb; thumb = a.firstChild; if (thumb.hidden) { - rect = a.parentNode.getBoundingClientRect(); - if (rect.top < 0) d.body.scrollTop += rect.top; + rect = a.getBoundingClientRect(); + if (rect.top < 0) d.body.scrollTop += rect.top - 42; if (rect.left < 0) d.body.scrollLeft += rect.left; return imgExpand.contract(thumb); } else { @@ -3547,6 +3601,10 @@ return; } $.ready(options.init); + if (conf['Quick Reply'] && conf['Hide Original Post Form']) { + Main.css += 'form[name=post] { display: none; }'; + } + Main.addStyle(); now = Date.now(); if (conf['Check for Updates'] && $.get('lastUpdate', 0) < now - 6 * HOUR) { $.ready(function() { @@ -3590,10 +3648,6 @@ quoteIndicators.init(); } if (conf['Fix XXX\'d Post Numbers']) unxify.init(); - if (conf['Quick Reply'] && conf['Hide Original Post Form']) { - Main.css += 'form[name=post] { display: none; }'; - } - Main.addStyle(); return $.ready(Main.ready); }, ready: function() { @@ -4031,13 +4085,16 @@ img[md5], img[md5] + img {\ .inlined {\ opacity: .5;\ }\ -.inline td.reply {\ +.inline .reply {\ background-color: rgba(255, 255, 255, 0.15);\ border: 1px solid rgba(128, 128, 128, 0.5);\ }\ .filetitle, .replytitle, .postername, .commentpostername, .postertrip {\ background: none;\ }\ +.filter_highlight {\ + box-shadow: -5px 0 rgba(255,0,0,0.5);\ +}\ .filtered {\ text-decoration: line-through;\ }\ diff --git a/changelog b/changelog index 6560e094b..e4c3bee03 100644 --- a/changelog +++ b/changelog @@ -1,4 +1,15 @@ master +- ahodesuka + Add Open Reply in New Tab option for replies made from the main board (not dumping). + Scroll back up (top of anchor - 42px) when unexpanding images. +- Mayhem + The Filter now has per filter settings: + - Filter the OP only along its thread, replies only, or both. + - Per boards, or global. + - Highlight, or hide. Highlighted OPs will have their threads put on top of the board page. + New filter group: Image dimensions. + Fix posting on Safari. + Fix rare case where the QR would not accept certain image types. 2.26.4 - Mayhem @@ -93,7 +104,7 @@ master see https://github.com/MayhemYDG/4chan-x/issues/136 2.24.4 -- ahokadesuka +- ahodesuka Scroll back up when unexpanding images. - e000 Prevent absurd cooldown durations. diff --git a/script.coffee b/script.coffee index 0b8a6c988..17b357a0b 100644 --- a/script.coffee +++ b/script.coffee @@ -13,9 +13,8 @@ config = 'Check for Updates': [true, 'Check for updated versions of 4chan X'] Filtering: 'Anonymize': [false, 'Make everybody anonymous'] - 'Filter': [false, 'Self-moderation placebo'] - 'Filter OPs': [false, 'Filter OPs along with their threads'] - 'Recursive Filtering': [false, 'Filter replies of filtered posts, recursively'] + 'Filter': [true, 'Self-moderation placebo'] + 'Recursive Filtering': [true, 'Filter replies of filtered posts, recursively'] 'Reply Hiding': [true, 'Hide single replies'] 'Thread Hiding': [true, 'Hide entire threads'] 'Show Stubs': [true, 'Of hidden threads / replies'] @@ -39,6 +38,7 @@ config = 'Cooldown': [true, 'Prevent "flood detected" errors.'] 'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.'] 'Auto Hide QR': [true, 'Automatically hide the quick reply when posting.'] + 'Open Reply in New Tab': [false, 'Open replies in a new tab that are made from the main board.'] 'Remember QR size': [false, 'Remember the size of the Quick reply (Firefox only).'] 'Remember Subject': [false, 'Remember the subject field, instead of resetting after posting.'] 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'] @@ -53,14 +53,39 @@ config = 'Indicate Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes'] 'Forward Hiding': [true, 'Hide original posts of inlined backlinks'] filter: - name: '' - tripcode: '' - email: '' - subject: '' - comment: '' - filename: '' - filesize: '' - md5: '' + name: [ + '# Filter any namefags:' + '#/^(?!Anonymous$)/' + ].join '\n' + tripcode: [ + '# Filter any tripfags' + '#/^!/' + ].join '\n' + email: [ + '# Filter any e-mails that are not `sage` on /a/ and /jp/:' + '#/^(?!sage$)/;boards:a,jp' + ].join '\n' + subject: [ + '# Filter Generals on /v/:' + '#/general/i;boards:v;op:only' + ].join '\n' + comment: [ + '# Filter Stallman copypasta on /g/:' + '#/what you\'re refer+ing to as linux/i;boards:g' + ].join '\n' + filename: [ + '' + ].join '\n' + dimensions: [ + '# Highlight potential wallpapers:' + '#/1920x1080/;op:yes;highlight;top:no;boards:w,wg' + ].join '\n' + filesize: [ + '' + ].join '\n' + md5: [ + '' + ].join '\n' sauces: [ 'http://iqdb.org/?url=$1' 'http://www.google.com/searchbyimage?image_url=$1' @@ -408,58 +433,132 @@ $$ = (selector, root=d.body) -> Array::slice.call root.querySelectorAll selector filter = - regexps: {} - callbacks: [] + filters: {} init: -> for key of config.filter - unless m = conf[key].match /^\/.+\/\w*$/gm - continue - @regexps[key] = [] - for filter in m - f = filter.match /^\/(.+)\/(\w*)$/ - try - @regexps[key].push RegExp f[1], f[2] - catch e - alert e.message - #only execute what's filterable - @callbacks.push @[key] + @filters[key] = [] + for filter in conf[key].split('\n') + continue if filter[0] is '#' - g.callbacks.push @node + unless regexp = filter.match /\/(.+)\/(\w*)/ + continue + + # Don't mix up filter flags with the regular expression. + filter = filter.replace regexp[0], '' + + # Do not add this filter to the list if it's not a global one + # and it's not specifically applicable to the current board. + # Defaults to global. + boards = filter.match(/boards:([^;]+)/)?[1].toLowerCase() or 'global' + if boards isnt 'global' and boards.split(',').indexOf(g.BOARD) is -1 + continue + + try + # Please, don't write silly regular expressions. + regexp = RegExp regexp[1], regexp[2] + catch e + # I warned you, bro. + alert e.message + continue + + # Filter OPs along with their threads, replies only, or both. + # Defaults to replies only. + op = filter.match(/[^t]op:(yes|no|only)/)?[1].toLowerCase() or 'no' + + # Highlight the post, or hide it. + # If not specified, the highlight class will be filter_highlight. + # Defaults to post hiding. + if hl = /highlight/.test filter + hl = filter.match(/highlight:(\w+)/)?[1].toLowerCase() or 'filter_highlight' + # Put highlighted OP's thread on top of the board page or not. + # Defaults to on top. + top = filter.match(/top:(yes|no)/)?[1].toLowerCase() or 'yes' + top = top is 'yes' # Turn it into a boolean + + @filters[key].push @createFilter regexp, op, hl, top + + # Only execute filter types that contain valid filters. + unless @filters[key].length + delete @filters[key] + + if Object.keys(@filters).length + g.callbacks.push @node + + createFilter: (regexp, op, hl, top) -> + (root, value, isOP) -> + if isOP and op is 'no' or !isOP and op is 'only' + return false + unless regexp.test value + return false + if hl + $.addClass root, hl + if isOP and top and not g.REPLY + # Put the highlighted OPs' threads on top of the board pages... + thisThread = root.parentNode + # ...before the first non highlighted thread. + if firstThread = $ 'div[class=op]' + $.before firstThread.parentNode, [thisThread, thisThread.nextElementSibling] + # Continue the filter lookup to add more classes or hide it. + return false + if isOP + unless g.REPLY + threadHiding.hideHide root.parentNode + else + replyHiding.hideHide root.previousSibling + true node: (root) -> - unless root.className - if filter.callbacks.some((callback) -> callback root) - replyHiding.hideHide $ 'td:not([nowrap])', root - else if root.className is 'op' and not g.REPLY and conf['Filter OPs'] - if filter.callbacks.some((callback) -> callback root) - threadHiding.hideHide root.parentNode + klass = root.className + return if /\binlined\b/.test klass + unless isOP = klass is 'op' + root = $ 'td[id]', root + for key of filter.filters + if filter.test root, key, isOP + return - test: (key, value) -> - filter.regexps[key].some (regexp) -> regexp.test value + test: (root, key, isOP) -> + value = @[key] root, isOP + if value is false + # Return if there's nothing to filter (no tripcode for example). + return false - name: (root) -> - name = if root.className is 'op' then $ '.postername', root else $ '.commentpostername', root - filter.test 'name', name.textContent + for filter in @filters[key] + if filter root, value, isOP + return true + false + + name: (root, isOP) -> + name = if isOP then $ '.postername', root else $ '.commentpostername', root + name.textContent tripcode: (root) -> if trip = $ '.postertrip', root - filter.test 'tripcode', trip.textContent + return trip.textContent + false email: (root) -> - if mail = $ '.linkmail', root - filter.test 'email', mail.href - subject: (root) -> - sub = if root.className is 'op' then $ '.filetitle', root else $ '.replytitle', root - filter.test 'subject', sub.textContent + unless mail = $ '.linkmail', root + return mail.href + false + subject: (root, isOP) -> + sub = if isOP then $ '.filetitle', root else $ '.replytitle', root + sub.textContent comment: (root) -> - filter.test 'comment', ($.el 'a', innerHTML: $('blockquote', root).innerHTML.replace /
    /g, '\n').textContent + ($.el 'a', innerHTML: $('blockquote', root).innerHTML.replace /
    /g, '\n').textContent filename: (root) -> - if file = $ '.filesize span', root - filter.test 'filename', file.title + if file = $ '.filesize > span', root + return file.title + false + dimensions: (root) -> + if span = $ '.filesize', root + return span.textContent.match(/\d+x\d+/)[0] + return false filesize: (root) -> if img = $ 'img[md5]', root - filter.test 'filesize', img.alt + return img.alt + false md5: (root) -> if img = $ 'img[md5]', root - filter.test 'md5', img.getAttribute('md5') + return img.getAttribute 'md5' + false strikethroughQuotes = init: -> @@ -900,7 +999,8 @@ qr = $.before form, link g.callbacks.push @node - if engine is 'webkit' + # CORS is ignored for content script on Chrome, but not Safari/Oprah/Firefox. + if /chrome/i.test navigator.userAgent qr.status ready: true else iframe = $.el 'iframe', @@ -1023,7 +1123,7 @@ qr = e?.preventDefault() qr.open() unless g.REPLY - $('select', qr.el).value = $.x('ancestor::div', @).firstChild.id + $('select', qr.el).value = $.x('ancestor::div[@class="thread"]', @).firstChild.id # Make sure we get the correct number, even with XXX censors id = @previousElementSibling.hash[1..] @@ -1265,7 +1365,7 @@ qr = ta.style.cssText = $.get 'qr.size', '' # Allow only this board's supported files. - mimeTypes = $('.rules').textContent.match(/: (.+) /)[1].toLowerCase().replace /\w+/g, (type) -> + mimeTypes = $('.rules').firstChild.textContent.match(/: (.+) /)[1].toLowerCase().replace /\w+/g, (type) -> switch type when 'jpg' 'image/jpeg' @@ -1405,7 +1505,8 @@ qr = reader.readAsBinaryString reply.file return - if engine is 'webkit' + # CORS is ignored for content script on Chrome, but not Safari/Oprah/Firefox. + if /chrome/i.test navigator.userAgent qr.message.post post return qr.message.send post @@ -1456,6 +1557,9 @@ qr = # Enable auto-posting if we have stuff to post, disable it otherwise. qr.cooldown.auto = qr.replies.length > 1 qr.cooldown.set if /sage/i.test reply.email then 60 else 30 + if conf['Open Reply in New Tab'] && !g.REPLY && !qr.cooldown.auto + open = GM_openInTab or window.open + open "http://boards.4chan.org/#{g.BOARD}/res/#{thread}##{postNumber}", "_blank" if conf['Persistent QR'] or qr.cooldown.auto reply.rm() @@ -1467,7 +1571,8 @@ qr = message: send: (data) -> - if engine is 'webkit' + # CORS is ignored for content script on Chrome, but not Safari/Oprah/Firefox. + if /chrome/i.test navigator.userAgent qr.message.receive data return data.qr = true @@ -1607,7 +1712,7 @@ options =
    Sauce is disabled.
    -
    Lines starting with a # will be ignored.
    + Lines starting with a # will be ignored.
      These variables will be replaced by the corresponding url:
    • $1: Thumbnail.
    • $2: Full image.
    • @@ -1619,13 +1724,21 @@ options =
      Filter is disabled.
      Use regular expressions, one per line.
      + Lines starting with a # will be ignored.
      For example, /weeaboo/i will filter posts containing `weeaboo` case-insensitive. +
        You can use these settings with each regular expression, separate them with semicolons: +
      • Per boards, separate them with commas. It is global if not specified.
        For example: boards:a,jp;.
      • +
      • Filter OPs only along with their threads (`only`), replies only (`no`, this is default), or both (`yes`).
        For example: op:only;, op:no; or op:yes;.
      • +
      • Highlight instead of hiding. You can specify a class name to use with a userstyle.
        For example: highlight; or hightlight:wallpaper;.
      • +
      • Highlighted OPs will have their threads put on top of board pages by default.
        For example: top:yes or top:no.
      • +

      Name:

      Tripcode:

      E-mail:

      Subject:

      Comment:

      Filename:

      +

      Image dimensions:

      Filesize:

      Image MD5:

      @@ -2441,13 +2554,14 @@ quoteIndicators = # We use contains() so that it works with hidden threads tid = g.THREAD_ID or $.x('ancestor::div[contains(@class,"thread")]', root).firstChild.id for quote in $$ '.quotelink', root - if conf['Indicate OP quote'] and quote.hash[1..] is tid + hash = quote.hash[1..] + if conf['Indicate OP quote'] and hash is tid # \u00A0 is nbsp $.add quote, $.tn '\u00A0(OP)' return path = quote.pathname #if quote leads to a different thread id and is located on the same board (index 0) - if conf['Indicate Cross-thread Quotes'] and path.lastIndexOf("/#{tid}") is -1 and path.indexOf("/#{g.BOARD}/") is 0 + if conf['Indicate Cross-thread Quotes'] and hash and path.lastIndexOf("/#{tid}") is -1 and path.indexOf("/#{g.BOARD}/") is 0 # \u00A0 is nbsp $.add quote, $.tn '\u00A0(Cross-thread)' return @@ -2690,8 +2804,8 @@ imgExpand = toggle: (a) -> thumb = a.firstChild if thumb.hidden - rect = a.parentNode.getBoundingClientRect() - d.body.scrollTop += rect.top if rect.top < 0 + rect = a.getBoundingClientRect() + d.body.scrollTop += rect.top - 42 if rect.top < 0 d.body.scrollLeft += rect.left if rect.left < 0 imgExpand.contract thumb else @@ -2784,6 +2898,11 @@ Main = $.ready options.init + if conf['Quick Reply'] and conf['Hide Original Post Form'] + Main.css += 'form[name=post] { display: none; }' + + Main.addStyle() + now = Date.now() if conf['Check for Updates'] and $.get('lastUpdate', 0) < now - 6*HOUR $.ready -> $.add d.head, $.el 'script', src: 'https://raw.github.com/mayhemydg/4chan-x/master/latest.js' @@ -2854,11 +2973,6 @@ Main = if conf['Fix XXX\'d Post Numbers'] unxify.init() - if conf['Quick Reply'] and conf['Hide Original Post Form'] - Main.css += 'form[name=post] { display: none; }' - - Main.addStyle() - $.ready Main.ready ready: -> @@ -3302,13 +3416,16 @@ img[md5], img[md5] + img { .inlined { opacity: .5; } -.inline td.reply { +.inline .reply { background-color: rgba(255, 255, 255, 0.15); border: 1px solid rgba(128, 128, 128, 0.5); } .filetitle, .replytitle, .postername, .commentpostername, .postertrip { background: none; } +.filter_highlight { + box-shadow: -5px 0 rgba(255,0,0,0.5); +} .filtered { text-decoration: line-through; } From 55a42d9cf87f485ccea373c1baeaaf3c6a349afc Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 21 Feb 2012 18:31:19 +0100 Subject: [PATCH 6/6] Update changelog about the "top" filter setting. --- changelog | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog b/changelog index 7b991ad46..720fb7fe2 100644 --- a/changelog +++ b/changelog @@ -5,7 +5,8 @@ master The Filter now has per filter settings: - Filter the OP only along its thread, replies only, or both. - Per boards, or global. - - Highlight, or hide. Highlighted OPs will have their threads put on top of the board page. + - Highlight, or hide. + - Highlighted OPs will have their threads put on top of the board page by default. New filter group: Image dimensions. Fix posting on Safari. Fix rare case where the QR would not accept certain image types.