Merge branch 'filter'

This commit is contained in:
Nicolas Stepien 2012-02-21 02:14:29 +01:00
commit 1b8da8fd5f
2 changed files with 171 additions and 90 deletions

View File

@ -90,9 +90,8 @@
}, },
Filtering: { Filtering: {
'Anonymize': [false, 'Make everybody anonymous'], 'Anonymize': [false, 'Make everybody anonymous'],
'Filter': [false, 'Self-moderation placebo'], 'Filter': [true, 'Self-moderation placebo'],
'Filter OPs': [false, 'Filter OPs along with their threads'], 'Recursive Filtering': [true, 'Filter replies of filtered posts, recursively'],
'Recursive Filtering': [false, 'Filter replies of filtered posts, recursively'],
'Reply Hiding': [true, 'Hide single replies'], 'Reply Hiding': [true, 'Hide single replies'],
'Thread Hiding': [true, 'Hide entire threads'], 'Thread Hiding': [true, 'Hide entire threads'],
'Show Stubs': [true, 'Of hidden threads / replies'] 'Show Stubs': [true, 'Of hidden threads / replies']
@ -532,86 +531,108 @@
}; };
filter = { filter = {
regexps: {}, filters: {},
callbacks: [],
init: function() { init: function() {
var f, filter, key, m, _i, _len; var boards, filter, hl, key, op, regexp, _i, _len, _ref, _ref2, _ref3;
for (key in config.filter) { for (key in config.filter) {
if (!(m = conf[key].match(/^\/.+\/\w*$/gm))) continue; this.filters[key] = [];
this.regexps[key] = []; _ref = conf[key].split('\n');
for (_i = 0, _len = m.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
filter = m[_i]; filter = _ref[_i];
f = filter.match(/^\/(.+)\/(\w*)$/); 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 { try {
this.regexps[key].push(RegExp(f[1], f[2])); regexp = RegExp(regexp[1], regexp[2]);
} catch (e) { } catch (e) {
alert(e.message); alert(e.message);
continue;
} }
op = ((_ref3 = filter.match(/op:(yes|no|only)/)) != null ? _ref3[1].toLowerCase() : void 0) || 'no';
hl = /highlight/.test(filter);
this.filters[key].push(this.createFilter(regexp, op, hl));
} }
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) {
return function(root, value, isOP) {
if (isOP && op === 'no' || op === 'only') return false;
if (!regexp.test(value)) return false;
if (hl) {
$.addClass(root, 'filter_highlight');
} else if (isOP) {
threadHiding.hideHide(root.parentNode);
} else {
replyHiding.hideHide(root.previousSibling);
}
return true;
};
}, },
node: function(root) { node: function(root) {
if (!root.className) { var isOP, key, klass;
if (filter.callbacks.some(function(callback) { klass = root.className;
return callback(root); if (/\binlined\b/.test(klass)) return;
})) { if (!(isOP = klass === 'op')) root = $('td[id]', root);
return replyHiding.hideHide($('td:not([nowrap])', root)); for (key in filter.filters) {
} if (filter.test(root, key, isOP)) return;
} else if (root.className === 'op' && !g.REPLY && conf['Filter OPs']) {
if (filter.callbacks.some(function(callback) {
return callback(root);
})) {
return threadHiding.hideHide(root.parentNode);
}
} }
}, },
test: function(key, value) { test: function(root, key, isOP) {
return filter.regexps[key].some(function(regexp) { var filter, value, _i, _len, _ref;
return regexp.test(value); 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; var name;
name = root.className === 'op' ? $('.postername', root) : $('.commentpostername', root); name = isOP ? $('.postername', root) : $('.commentpostername', root);
return filter.test('name', name.textContent); return name.textContent;
}, },
tripcode: function(root) { tripcode: function(root) {
var trip; var trip;
if (trip = $('.postertrip', root)) { if (trip = $('.postertrip', root)) return trip.textContent;
return filter.test('tripcode', trip.textContent); return false;
}
}, },
email: function(root) { email: function(root) {
var mail; 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; var sub;
sub = root.className === 'op' ? $('.filetitle', root) : $('.replytitle', root); sub = isOP ? $('.filetitle', root) : $('.replytitle', root);
return filter.test('subject', sub.textContent); return sub.textContent;
}, },
comment: function(root) { comment: function(root) {
return filter.test('comment', ($.el('a', { return ($.el('a', {
innerHTML: $('blockquote', root).innerHTML.replace(/<br>/g, '\n') innerHTML: $('blockquote', root).innerHTML.replace(/<br>/g, '\n')
})).textContent); })).textContent;
}, },
filename: function(root) { filename: function(root) {
var file; var file;
if (file = $('.filesize span', root)) { if (file = $('.filesize > span', root)) return file.title;
return filter.test('filename', file.title); return false;
}
}, },
filesize: function(root) { filesize: function(root) {
var img; 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) { md5: function(root) {
var img; var img;
if (img = $('img[md5]', root)) { if (img = $('img[md5]', root)) return img.getAttribute('md5');
return filter.test('md5', img.getAttribute('md5')); return false;
}
} }
}; };
@ -4032,13 +4053,16 @@ img[md5], img[md5] + img {\
.inlined {\ .inlined {\
opacity: .5;\ opacity: .5;\
}\ }\
.inline td.reply {\ .inline .reply {\
background-color: rgba(255, 255, 255, 0.15);\ background-color: rgba(255, 255, 255, 0.15);\
border: 1px solid rgba(128, 128, 128, 0.5);\ border: 1px solid rgba(128, 128, 128, 0.5);\
}\ }\
.filetitle, .replytitle, .postername, .commentpostername, .postertrip {\ .filetitle, .replytitle, .postername, .commentpostername, .postertrip {\
background: none;\ background: none;\
}\ }\
.filter_highlight {\
box-shadow: -5px 0 rgba(255,0,0,0.5);\
}\
.filtered {\ .filtered {\
text-decoration: line-through;\ text-decoration: line-through;\
}\ }\

View File

@ -13,9 +13,8 @@ config =
'Check for Updates': [true, 'Check for updated versions of 4chan X'] 'Check for Updates': [true, 'Check for updated versions of 4chan X']
Filtering: Filtering:
'Anonymize': [false, 'Make everybody anonymous'] 'Anonymize': [false, 'Make everybody anonymous']
'Filter': [false, 'Self-moderation placebo'] 'Filter': [true, 'Self-moderation placebo']
'Filter OPs': [false, 'Filter OPs along with their threads'] 'Recursive Filtering': [true, 'Filter replies of filtered posts, recursively']
'Recursive Filtering': [false, 'Filter replies of filtered posts, recursively']
'Reply Hiding': [true, 'Hide single replies'] 'Reply Hiding': [true, 'Hide single replies']
'Thread Hiding': [true, 'Hide entire threads'] 'Thread Hiding': [true, 'Hide entire threads']
'Show Stubs': [true, 'Of hidden threads / replies'] 'Show Stubs': [true, 'Of hidden threads / replies']
@ -408,58 +407,113 @@ $$ = (selector, root=d.body) ->
Array::slice.call root.querySelectorAll selector Array::slice.call root.querySelectorAll selector
filter = filter =
regexps: {} filters: {}
callbacks: []
init: -> init: ->
for key of config.filter for key of config.filter
unless m = conf[key].match /^\/.+\/\w*$/gm @filters[key] = []
continue for filter in conf[key].split('\n')
@regexps[key] = [] continue if filter[0] is '#'
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]
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(/op:(yes|no|only)/)?[1].toLowerCase() or 'no'
# Highlight the post, or hide it.
# Defaults to post hiding.
hl = /highlight/.test filter
@filters[key].push @createFilter regexp, op, hl
# 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) ->
(root, value, isOP) ->
if isOP and op is 'no' or op is 'only'
return false
unless regexp.test value
return false
if hl
$.addClass root, 'filter_highlight'
else if isOP
threadHiding.hideHide root.parentNode
else
replyHiding.hideHide root.previousSibling
true
node: (root) -> node: (root) ->
unless root.className klass = root.className
if filter.callbacks.some((callback) -> callback root) return if /\binlined\b/.test klass
replyHiding.hideHide $ 'td:not([nowrap])', root unless isOP = klass is 'op'
else if root.className is 'op' and not g.REPLY and conf['Filter OPs'] root = $ 'td[id]', root
if filter.callbacks.some((callback) -> callback root) for key of filter.filters
threadHiding.hideHide root.parentNode if filter.test root, key, isOP
return
test: (key, value) -> test: (root, key, isOP) ->
filter.regexps[key].some (regexp) -> regexp.test value value = @[key] root, isOP
if value is false
# Return if there's nothing to filter (no tripcode for example).
return false
name: (root) -> for filter in @filters[key]
name = if root.className is 'op' then $ '.postername', root else $ '.commentpostername', root if filter root, value, isOP
filter.test 'name', name.textContent return true
false
name: (root, isOP) ->
name = if isOP then $ '.postername', root else $ '.commentpostername', root
name.textContent
tripcode: (root) -> tripcode: (root) ->
if trip = $ '.postertrip', root if trip = $ '.postertrip', root
filter.test 'tripcode', trip.textContent return trip.textContent
false
email: (root) -> email: (root) ->
if mail = $ '.linkmail', root unless mail = $ '.linkmail', root
filter.test 'email', mail.href return mail.href
subject: (root) -> false
sub = if root.className is 'op' then $ '.filetitle', root else $ '.replytitle', root subject: (root, isOP) ->
filter.test 'subject', sub.textContent sub = if isOP then $ '.filetitle', root else $ '.replytitle', root
sub.textContent
comment: (root) -> comment: (root) ->
filter.test 'comment', ($.el 'a', innerHTML: $('blockquote', root).innerHTML.replace /<br>/g, '\n').textContent ($.el 'a', innerHTML: $('blockquote', root).innerHTML.replace /<br>/g, '\n').textContent
filename: (root) -> filename: (root) ->
if file = $ '.filesize span', root if file = $ '.filesize > span', root
filter.test 'filename', file.title return file.title
false
filesize: (root) -> filesize: (root) ->
if img = $ 'img[md5]', root if img = $ 'img[md5]', root
filter.test 'filesize', img.alt return img.alt
false
md5: (root) -> md5: (root) ->
if img = $ 'img[md5]', root if img = $ 'img[md5]', root
filter.test 'md5', img.getAttribute('md5') return img.getAttribute 'md5'
false
strikethroughQuotes = strikethroughQuotes =
init: -> init: ->
@ -3306,13 +3360,16 @@ img[md5], img[md5] + img {
.inlined { .inlined {
opacity: .5; opacity: .5;
} }
.inline td.reply { .inline .reply {
background-color: rgba(255, 255, 255, 0.15); background-color: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(128, 128, 128, 0.5); border: 1px solid rgba(128, 128, 128, 0.5);
} }
.filetitle, .replytitle, .postername, .commentpostername, .postertrip { .filetitle, .replytitle, .postername, .commentpostername, .postertrip {
background: none; background: none;
} }
.filter_highlight {
box-shadow: -5px 0 rgba(255,0,0,0.5);
}
.filtered { .filtered {
text-decoration: line-through; text-decoration: line-through;
} }