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 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()