class Post @callbacks = new Callbacks 'Post' toString: -> @ID constructor: (root, @thread, @board, that={}) -> @ID = +root.id[2..] @fullID = "#{@board}.#{@ID}" @cleanup root if that.isOriginalMarkup post = $ '.post', root info = $ '.postInfo', post @nodes = root: root post: post info: info comment: $ '.postMessage', post links: [] quotelinks: [] backlinks: info.getElementsByClassName 'backlink' unless @isReply = $.hasClass post, 'reply' @thread.OP = @ @thread.isSticky = !!$ '.stickyIcon', info @thread.isClosed = !!$ '.closedIcon', info @info = {} if subject = $ '.subject', info @nodes.subject = subject @info.subject = subject.textContent if name = $ '.name', info @nodes.name = name @info.name = name.textContent if email = $ '.useremail', info @nodes.email = email @info.email = decodeURIComponent email.href[7..] if tripcode = $ '.postertrip', info @nodes.tripcode = tripcode @info.tripcode = tripcode.textContent if uniqueID = $ '.posteruid', info @nodes.uniqueID = uniqueID @info.uniqueID = uniqueID.firstElementChild.textContent if capcode = $ '.capcode.hand', info @nodes.capcode = capcode @info.capcode = capcode.textContent.replace '## ', '' if flag = $ '.flag, .countryFlag', info @nodes.flag = flag @info.flag = flag.title if date = $ '.dateTime', info @nodes.date = date @info.date = new Date date.dataset.utc * 1000 @parseComment() @parseQuotes() @parseFile that @clones = [] g.posts.push @fullID, thread.posts.push @, board.posts.push @, @ @kill() if that.isArchived parseComment: -> # Merge text nodes and remove empty ones. @nodes.comment.normalize() # Get the comment's text. #
-> \n # Remove: # 'Comment too long'... # EXIF data. (/p/) # Rolls. (/tg/) # Preceding and following new lines. # Trailing spaces. bq = @nodes.comment.cloneNode true nodes = $$ '.abbr, .exif, b', bq i = 0 while node = nodes[i++] $.rm node text = "" nodes = $.X './/br|.//text()', bq i = 0 while node = nodes.snapshotItem i++ text += node.data or '\n' @info.comment = text.trim().replace /\s+$/gm, '' parseQuotes: -> @quotes = [] for quotelink in $$ '.quotelink', @nodes.comment @parseQuote quotelink return parseQuote: (quotelink) -> # Only add quotes that link to posts on an imageboard. # Don't add: # - board links. (>>>/b/) # - catalog links. (>>>/b/catalog or >>>/b/search) # - rules links. (>>>/a/rules) # - text-board quotelinks. (>>>/img/1234) return unless match = quotelink.href.match /// boards\.4chan\.org/ ([^/]+) # boardID /(?:res|thread)/\d+#p (\d+) # postID $ /// @nodes.quotelinks.push quotelink return if @isClone # ES6 Set when? fullID = "#{match[1]}.#{match[2]}" @quotes.push fullID unless fullID in @quotes parseFile: (that) -> return unless (fileEl = $ '.file', @nodes.post) and thumb = $ 'img[data-md5]', fileEl # Supports JPG/PNG/GIF/PDF. # Flash files are not supported. anchor = thumb.parentNode fileText = fileEl.firstElementChild @file = text: fileText thumb: thumb URL: anchor.href size: thumb.alt.match(/[\d.]+\s\w+/)[0] MD5: thumb.dataset.md5 isSpoiler: $.hasClass anchor, 'imgspoiler' size = +@file.size.match(/[\d.]+/)[0] unit = ['B', 'KB', 'MB', 'GB'].indexOf @file.size.match(/\w+$/)[0] size *= 1024 while unit-- > 0 @file.sizeInBytes = size @file.thumbURL = if that.isArchived thumb.src else "#{location.protocol}//t.4cdn.org/#{@board}/#{@file.URL.match(/(\d+)\./)[1]}s.jpg" @file.isImage = /(jpg|png|gif)$/i.test @file.URL @file.isVideo = /webm$/i.test @file.URL if @file.isImage or @file.isVideo @file.dimensions = fileText.childNodes[2].textContent.match(/\d+x\d+/)?[0] @file.name = fileText.title or do -> nameNode = $('span', fileText) or $('a', fileText) nameNode?.title or nameNode?.textContent <% if (type === 'crx') { %> # replace %22 with quotes, see: # crbug.com/81193 # webk.it/62107 # https://www.w3.org/Bugs/Public/show_bug.cgi?id=16909 # http://www.whatwg.org/specs/web-apps/current-work/#multipart-form-data @file.name = @file.name?.replace /%22/g, '"' <% } %> cleanup: (root) -> for node in $$ '.mobile', root $.rm node for node in $$ '.desktop', root $.rmClass node, 'desktop' return kill: (file, now) -> now or= new Date() if file return if @file.isDead @file.isDead = true @file.timeOfDeath = now $.addClass @nodes.root, 'deleted-file' else return if @isDead @isDead = true @timeOfDeath = now $.addClass @nodes.root, 'deleted-post' unless strong = $ 'strong.warning', @nodes.info strong = $.el 'strong', className: 'warning' textContent: if @isReply then '[Deleted]' else '[Dead]' $.after $('input', @nodes.info), strong strong.textContent = if file then '[File deleted]' else '[Deleted]' return if @isClone for clone in @clones clone.kill file, now return if file # Get quotelinks/backlinks to this post # and paint them (Dead). for quotelink in Get.allQuotelinksLinkingTo @ when not $.hasClass quotelink, 'deadlink' quotelink.textContent = quotelink.textContent + '\u00A0(Dead)' $.addClass quotelink, 'deadlink' return # XXX tmp fix for 4chan's racing condition # giving us false-positive dead posts. resurrect: -> delete @isDead delete @timeOfDeath $.rmClass @nodes.root, 'deleted-post' strong = $ 'strong.warning', @nodes.info # no false-positive files if @file and @file.isDead strong.textContent = '[File deleted]' else $.rm strong return if @isClone for clone in @clones clone.resurrect() for quotelink in Get.allQuotelinksLinkingTo @ if $.hasClass quotelink, 'deadlink' quotelink.textContent = quotelink.textContent.replace '\u00A0(Dead)', '' $.rmClass quotelink, 'deadlink' return collect: -> @kill() g.posts.rm @fullID @thread.posts.rm @ @board.posts.rm @ addClone: (context, contractThumb) -> new Clone @, context, contractThumb rmClone: (index) -> @clones.splice index, 1 for clone in @clones[index..] clone.nodes.root.dataset.clone = index++ return