296 lines
8.8 KiB
CoffeeScript
296 lines
8.8 KiB
CoffeeScript
Filter =
|
|
filters: {}
|
|
results: {}
|
|
init: ->
|
|
return unless g.VIEW in ['index', 'thread'] and Conf['Filter']
|
|
|
|
unless Conf['Filtered Backlinks']
|
|
$.addClass doc, 'hide-backlinks'
|
|
|
|
nsfwBoards = BoardConfig.sfwBoards(false).join(',')
|
|
sfwBoards = BoardConfig.sfwBoards(true).join(',')
|
|
|
|
for key of Config.filter
|
|
for line in Conf[key].split '\n'
|
|
continue if line[0] is '#'
|
|
|
|
if not (regexp = line.match /\/(.+)\/(\w*)/)
|
|
continue
|
|
|
|
# Don't mix up filter flags with the regular expression.
|
|
filter = line.replace regexp[0], ''
|
|
|
|
# Comma-separated list of the boards this filter applies to.
|
|
# Defaults to global.
|
|
boards = filter.match(/boards:([^;]+)/)?[1].toLowerCase() or 'global'
|
|
boards = boards.replace('nsfw', nsfwBoards).replace('sfw', sfwBoards)
|
|
boards = if boards is 'global' then null else boards.split(',')
|
|
|
|
# boards to exclude from an otherwise global rule
|
|
# due to the sfw and nsfw keywords, also works on all filters
|
|
# replaces 'nsfw' and 'sfw' for consistency
|
|
excludes = filter.match(/exclude:([^;]+)/)?[1].toLowerCase() or null
|
|
excludes = if excludes is null then null else excludes.replace('nsfw', nsfwBoards).replace('sfw', sfwBoards).split(',')
|
|
|
|
if key in ['uniqueID', 'MD5']
|
|
# MD5 filter will use strings instead of regular expressions.
|
|
regexp = regexp[1]
|
|
else
|
|
try
|
|
# Please, don't write silly regular expressions.
|
|
regexp = RegExp regexp[1], regexp[2]
|
|
catch err
|
|
# I warned you, bro.
|
|
new Notice 'warning', [
|
|
$.tn "Invalid #{key} filter:"
|
|
$.el 'br'
|
|
$.tn line
|
|
$.el 'br'
|
|
$.tn err.message
|
|
], 60
|
|
continue
|
|
|
|
# Filter OPs along with their threads, replies only, or both.
|
|
# Defaults to both.
|
|
op = filter.match(/[^t]op:(yes|no|only)/)?[1] or 'yes'
|
|
|
|
# Overrule the `Show Stubs` setting.
|
|
# Defaults to stub showing.
|
|
stub = switch filter.match(/stub:(yes|no)/)?[1]
|
|
when 'yes'
|
|
true
|
|
when 'no'
|
|
false
|
|
else
|
|
Conf['Stubs']
|
|
|
|
# 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] 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] or 'yes'
|
|
top = top is 'yes' # Turn it into a boolean
|
|
|
|
# Fields that this filter applies to (for 'general' filters)
|
|
if key is 'general'
|
|
if (types = filter.match /(?:^|;)\s*type:([^;]*)/)
|
|
types = types[1].split(',').filter (x) ->
|
|
x of Config.filter and x isnt 'general'
|
|
else
|
|
types = ['subject', 'name', 'filename', 'comment']
|
|
|
|
filter = @createFilter regexp, boards, excludes, op, stub, hl, top
|
|
if key is 'general'
|
|
for type in types
|
|
(@filters[type] or= []).push filter
|
|
else
|
|
(@filters[key] or= []).push filter
|
|
|
|
return unless Object.keys(@filters).length
|
|
Callbacks.Post.push
|
|
name: 'Filter'
|
|
cb: @node
|
|
|
|
createFilter: (regexp, boards, excludes, op, stub, hl, top) ->
|
|
test =
|
|
if typeof regexp is 'string'
|
|
# MD5 checking
|
|
(value) -> regexp is value
|
|
else
|
|
(value) -> regexp.test value
|
|
|
|
settings =
|
|
hide: !hl
|
|
stub: stub
|
|
class: hl
|
|
top: top
|
|
|
|
(value, boardID, isReply) ->
|
|
if boards and boardID not in boards
|
|
return false
|
|
if excludes and boardID in excludes
|
|
return false
|
|
if isReply and op is 'only' or !isReply and op is 'no'
|
|
return false
|
|
unless test value
|
|
return false
|
|
settings
|
|
|
|
test: (post, hideable=true) ->
|
|
return post.filterResults if post.filterResults
|
|
hide = false
|
|
stub = true
|
|
hl = undefined
|
|
top = false
|
|
if !Conf['Filter Self'] and QuoteYou.isYou(post)
|
|
hideable = false
|
|
for key of Filter.filters when ((value = Filter[key] post)?)
|
|
# Continue if there's nothing to filter (no tripcode for example).
|
|
for filter in Filter.filters[key] when (result = filter value, post.boardID, post.isReply)
|
|
if result.hide
|
|
if hideable
|
|
hide = true
|
|
stub and= result.stub
|
|
else
|
|
unless hl and result.class in hl
|
|
(hl or= []).push result.class
|
|
top or= result.top
|
|
if hide
|
|
{hide, stub}
|
|
else
|
|
{hl, top}
|
|
|
|
node: ->
|
|
return if @isClone
|
|
{hide, stub, hl, top} = Filter.test @, (!@isFetchedQuote and (@isReply or g.VIEW is 'index'))
|
|
if hide
|
|
if @isReply
|
|
PostHiding.hide @, stub
|
|
else
|
|
ThreadHiding.hide @thread, stub
|
|
else
|
|
if hl
|
|
@highlights = hl
|
|
$.addClass @nodes.root, hl...
|
|
return
|
|
|
|
isHidden: (post) ->
|
|
!!Filter.test(post).hide
|
|
|
|
postID: (post) -> "#{post.ID}"
|
|
name: (post) -> post.info.name
|
|
uniqueID: (post) -> post.info.uniqueID
|
|
tripcode: (post) -> post.info.tripcode
|
|
capcode: (post) -> post.info.capcode
|
|
pass: (post) -> post.info.pass
|
|
subject: (post) -> post.info.subject or (if post.isReply then undefined else '')
|
|
comment: (post) -> (post.info.comment ?= Build.parseComment post.info.commentHTML.innerHTML)
|
|
flag: (post) -> post.info.flag
|
|
filename: (post) -> post.file?.name
|
|
dimensions: (post) -> post.file?.dimensions
|
|
filesize: (post) -> post.file?.size
|
|
MD5: (post) -> post.file?.MD5
|
|
|
|
addFilter: (type, re, cb) ->
|
|
$.get type, Conf[type], (item) ->
|
|
save = item[type]
|
|
# Add a new line before the regexp unless the text is empty.
|
|
save =
|
|
if save
|
|
"#{save}\n#{re}"
|
|
else
|
|
re
|
|
$.set type, save, cb
|
|
|
|
quickFilterMD5: ->
|
|
post = Get.postFromNode @
|
|
return unless post.file
|
|
Filter.addFilter 'MD5', "/#{post.file.MD5}/"
|
|
origin = post.origin or post
|
|
if origin.isReply
|
|
PostHiding.hide origin
|
|
else if g.VIEW is 'index'
|
|
ThreadHiding.hide origin.thread
|
|
# If post is still visible, give an indication that the MD5 was filtered.
|
|
if post.nodes.post.getBoundingClientRect().height
|
|
new Notice 'info', 'MD5 filtered.', 2
|
|
|
|
escape: (value) ->
|
|
value.replace ///
|
|
/
|
|
| \\
|
|
| \^
|
|
| \$
|
|
| \n
|
|
| \.
|
|
| \(
|
|
| \)
|
|
| \{
|
|
| \}
|
|
| \[
|
|
| \]
|
|
| \?
|
|
| \*
|
|
| \+
|
|
| \|
|
|
///g, (c) ->
|
|
if c is '\n'
|
|
'\\n'
|
|
else if c is '\\'
|
|
'\\\\'
|
|
else
|
|
"\\#{c}"
|
|
|
|
menu:
|
|
init: ->
|
|
return unless g.VIEW in ['index', 'thread'] and Conf['Menu'] and Conf['Filter']
|
|
|
|
div = $.el 'div',
|
|
textContent: 'Filter'
|
|
|
|
entry =
|
|
el: div
|
|
order: 50
|
|
open: (post) ->
|
|
Filter.menu.post = post
|
|
true
|
|
subEntries: []
|
|
|
|
for type in [
|
|
['Name', 'name']
|
|
['Unique ID', 'uniqueID']
|
|
['Tripcode', 'tripcode']
|
|
['Capcode', 'capcode']
|
|
['Pass Date', 'pass']
|
|
['Subject', 'subject']
|
|
['Comment', 'comment']
|
|
['Flag', 'flag']
|
|
['Filename', 'filename']
|
|
['Image dimensions', 'dimensions']
|
|
['Filesize', 'filesize']
|
|
['Image MD5', 'MD5']
|
|
]
|
|
# Add a sub entry for each filter type.
|
|
entry.subEntries.push Filter.menu.createSubEntry type[0], type[1]
|
|
|
|
Menu.menu.addEntry entry
|
|
|
|
createSubEntry: (text, type) ->
|
|
el = $.el 'a',
|
|
href: 'javascript:;'
|
|
textContent: text
|
|
el.dataset.type = type
|
|
$.on el, 'click', Filter.menu.makeFilter
|
|
|
|
return {
|
|
el: el
|
|
open: (post) ->
|
|
value = Filter[type] post
|
|
value? and not (g.BOARD.ID is 'f' and type is 'MD5')
|
|
}
|
|
|
|
makeFilter: ->
|
|
{type} = @dataset
|
|
# Convert value -> regexp, unless type is MD5
|
|
value = Filter[type] Filter.menu.post
|
|
re = if type in ['uniqueID', 'MD5'] then value else Filter.escape(value)
|
|
re = if type in ['uniqueID', 'MD5']
|
|
"/#{re}/"
|
|
else
|
|
"/^#{re}$/"
|
|
|
|
Filter.addFilter type, re, ->
|
|
# Open the settings and display & focus the relevant filter textarea.
|
|
Settings.open 'Filter'
|
|
section = $ '.section-container'
|
|
select = $ 'select[name=filter]', section
|
|
select.value = type
|
|
Settings.selectFilter.call select
|
|
$.onExists section, 'textarea', (ta) ->
|
|
tl = ta.textLength
|
|
ta.setSelectionRange tl, tl
|
|
ta.focus()
|