class Post @callbacks = new Callbacks 'Post' toString: -> @ID constructor: (root, @thread, @board) -> <% if (tests_enabled) { %> root2 = root.cloneNode true for el in $$ '.mobile', root2 $.rm el for el in $$ 'a[href]', root2 href = el.href href = href.replace /(^\w+:\/\/boards.4chan.org\/[^\/]+\/thread\/\d+)\/.*/, '$1' el.setAttribute 'href', href for el in $$ 'a[rel=canonical]', root2 el.removeAttribute 'rel' for el in $$ 'img[src]', root2 el.src = el.src.replace /(spoiler-\w+)\d(\.png)$/, '$11$2' Fourchan.code.call nodes: comment: $ '.postMessage', root2 for el in $$ 'pre[style=""]', root2 el.removeAttribute 'style' textNodes = $.X './/text()', root2 i = 0 while node = textNodes.snapshotItem i++ node.data = node.data.replace /\s+/g, ' ' $.rm node if node.data is '' @normalizedOriginal = root2 <% } %> @ID = +root.id[2..] @fullID = "#{@board}.#{@ID}" @context = @ root.dataset.fullID = @fullID post = $ '.post', root info = $ '.postInfo', post @nodes = root: root post: post info: info nameBlock: $ '.nameBlock', info quote: $ '.postNum > a:nth-of-type(2)', info comment: $ '.postMessage', post links: [] quotelinks: [] # XXX Edge invalidates HTMLCollections when an ancestor node is inserted into another node. # https://connect.microsoft.com/IE/feedback/details/1198967/ie11-appendchild-provoke-an-error-on-an-htmlcollection if $.engine is 'edge' Object.defineProperty @nodes, 'backlinks', configurable: true enumerable: true get: -> info.getElementsByClassName 'backlink' else @nodes.backlinks = info.getElementsByClassName 'backlink' unless @isReply = $.hasClass post, 'reply' @thread.OP = @ @thread.isArchived = !!$ '.archivedIcon', info @thread.isSticky = !!$ '.stickyIcon', info @thread.isClosed = @thread.isArchived or !!$ '.closedIcon', info @thread.kill() if @thread.isArchived @info = {} @info.nameBlock = if Conf['Anonymize'] 'Anonymous' else @nodes.nameBlock.textContent.trim() if subject = $ '.subject', info @nodes.subject = subject @info.subject = subject.textContent or undefined 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() @isDead = false @isHidden = false @clones = [] if g.posts[@fullID] @isRebuilt = true @clones = g.posts[@fullID].clones clone.origin = @ for clone in @clones @board.posts.push @ID, @ @thread.posts.push @ID, @ g.posts.push @fullID, @ 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/) # Fortunes. (/s4s/) bq = @nodes.comment.cloneNode true for node in $$ '.abbr + br, .exif, b, .fortune', bq $.rm node if abbr = $ '.abbr', bq $.rm abbr @info.comment = @nodesToText bq if abbr @info.comment = @info.comment.replace /\n\n$/, '' # Hide spoilers. # Remove: # Preceding and following new lines. # Trailing spaces. commentDisplay = @info.comment unless Conf['Remove Spoilers'] or Conf['Reveal Spoilers'] spoilers = $$ 's', bq if spoilers.length for node in spoilers $.replace node, $.tn '[spoiler]' commentDisplay = @nodesToText bq @info.commentDisplay = commentDisplay.trim().replace /\s+$/gm, '' nodesToText: (bq) -> text = "" nodes = $.X './/br|.//text()', bq i = 0 while node = nodes.snapshotItem i++ text += node.data or '\n' text parseQuotes: -> @quotes = [] # XXX https://github.com/4chan/4chan-JS/issues/77 # 4chan currently creates quote links inside [code] tags; ignore them for quotelink in $$ ':not(pre) > .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) match = quotelink.href.match /// ^https?://boards\.4chan\.org/+ ([^/]+) # boardID /+(?:res|thread)/+\d+(?:/[^#]*)?#p (\d+) # postID $ /// return unless match or (@isClone and quotelink.dataset.postID) # normal or resurrected quote @nodes.quotelinks.push quotelink return if @isClone # ES6 Set when? fullID = "#{match[1]}.#{match[2]}" @quotes.push fullID unless fullID in @quotes parseFile: -> return unless fileEl = $ '.file', @nodes.post return unless link = $ '.fileText > a, .fileText-original > a', fileEl return unless info = link.nextSibling?.textContent.match /\(([\d.]+ [KMG]?B).*\)/ fileText = fileEl.firstElementChild @file = text: fileText link: link url: link.href name: fileText.title or link.title or link.textContent size: info[1] isImage: /(jpg|png|gif)$/i.test link.href isVideo: /webm$/i.test link.href dimensions: info[0].match(/\d+x\d+/)?[0] tag: info[0].match(/,[^,]*, ([a-z]+)\)/i)?[1] 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 if (thumb = $ '.fileThumb > [data-md5]', fileEl) $.extend @file, thumb: thumb thumbURL: if m = link.href.match(/\d+(?=\.\w+$)/) then "#{location.protocol}//i.4cdn.org/#{@board}/#{m[0]}s.jpg" MD5: thumb.dataset.md5 isSpoiler: $.hasClass thumb.parentNode, 'imgspoiler' kill: (file) -> if file return if @file.isDead @file.isDead = true $.addClass @nodes.root, 'deleted-file' else return if @isDead @isDead = true $.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 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 Workaround for 4chan's racing condition # giving us false-positive dead posts. resurrect: -> @isDead = false $.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 @ when $.hasClass quotelink, 'deadlink' quotelink.textContent = quotelink.textContent.replace '\u00A0(Dead)', '' $.rmClass quotelink, 'deadlink' return collect: -> 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