Build = staticPath: '//s.4cdn.org/image/' gifIcon: if window.devicePixelRatio >= 2 then '@2x.gif' else '.gif' spoilerRange: {} shortFilename: (filename) -> ext = filename.match(/\.?[^\.]*$/)[0] if filename.length - ext.length > 30 "#{filename.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[^]){0,25}/)[0]}(...)#{ext}" else filename spoilerThumb: (boardID) -> if spoilerRange = Build.spoilerRange[boardID] # Randomize the spoiler image. "#{Build.staticPath}spoiler-#{boardID}#{Math.floor 1 + spoilerRange * Math.random()}.png" else "#{Build.staticPath}spoiler.png" sameThread: (boardID, threadID) -> g.VIEW is 'thread' and g.BOARD.ID is boardID and g.THREADID is +threadID threadURL: (boardID, threadID) -> if boardID isnt g.BOARD.ID "//#{BoardConfig.domain(boardID)}/#{boardID}/thread/#{threadID}" else if g.VIEW isnt 'thread' or +threadID isnt g.THREADID "/#{boardID}/thread/#{threadID}" else '' postURL: (boardID, threadID, postID) -> "#{Build.threadURL(boardID, threadID)}#p#{postID}" parseJSON: (data, {siteID, boardID}) -> o = # id ID: data.no postID: data.no threadID: data.resto or data.no boardID: boardID siteID: siteID isReply: !!data.resto # thread status isSticky: !!data.sticky isClosed: !!data.closed isArchived: !!data.archived # file status fileDeleted: !!data.filedeleted filesDeleted: if data.filedeleted then [0] else [] o.info = subject: $.unescape data.sub email: $.unescape data.email name: $.unescape(data.name) or '' tripcode: data.trip pass: if data.since4pass? then "#{data.since4pass}" else undefined uniqueID: data.id flagCode: data.country flagCodeTroll: data.troll_country flag: $.unescape data.country_name dateUTC: data.time dateText: data.now commentHTML: {innerHTML: data.com or ''} if data.capcode o.info.capcode = data.capcode.replace(/_highlight$/, '').replace(/_/g, ' ').replace(/\b\w/g, (c) -> c.toUpperCase()) o.capcodeHighlight = /_highlight$/.test data.capcode delete o.info.uniqueID o.files = [] if data.ext o.file = SW.yotsuba.Build.parseJSONFile(data, {siteID, boardID}) o.files.push o.file # Temporary JSON properties for events such as April 1 / Halloween for key of data when key[0] is 'x' o[key] = data[key] o parseJSONFile: (data, {siteID, boardID}) -> site = g.sites[siteID] filename = if site.software is 'yotsuba' and boardID is 'f' "#{encodeURIComponent data.filename}#{data.ext}" else "#{data.tim}#{data.ext}" o = name: ($.unescape data.filename) + data.ext url: site.urls.file({siteID, boardID}, filename) height: data.h width: data.w MD5: data.md5 size: $.bytesToString data.fsize thumbURL: site.urls.thumb({siteID, boardID}, "#{data.tim}s.jpg") theight: data.tn_h twidth: data.tn_w isSpoiler: !!data.spoiler tag: data.tag hasDownscale: !!data.m_img o.dimensions = "#{o.width}x#{o.height}" if data.h? and !/\.pdf$/.test(o.url) o parseComment: (html) -> html = html .replace(//gi, '\n') .replace(/\n\n]*>/g, '') $.unescape html parseCommentDisplay: (html) -> # Hide spoilers. unless Conf['Remove Spoilers'] or Conf['Reveal Spoilers'] while (html2 = html.replace /(?:(?!<\/?s>).)*<\/s>/g, '[spoiler]') isnt html html = html2 html = html .replace(/^Rolled [^<]*<\/b>/i, '') # Rolls (/tg/, /qst/) .replace(/ o = Build.parseJSON data, {boardID, siteID: g.SITE.ID} Build.post o post: (o) -> {ID, threadID, boardID, file} = o {subject, email, name, tripcode, capcode, pass, uniqueID, flagCode, flagCodeTroll, flag, dateUTC, dateText, commentHTML} = o.info {staticPath, gifIcon} = Build ### Post Info ### if capcode capcodeLC = capcode.toLowerCase() if capcode is 'Founder' capcodePlural = 'the Founder' capcodeDescription = "4chan's Founder" else if capcode is 'Verified' capcodePlural = 'Verified Users' capcodeDescription = '' else capcodeLong = {'Admin': 'Administrator', 'Mod': 'Moderator'}[capcode] or capcode capcodePlural = "#{capcodeLong}s" capcodeDescription = "a 4chan #{capcodeLong}" url = Build.threadURL boardID, threadID postLink = "#{url}#p#{ID}" quoteLink = if Build.sameThread boardID, threadID "javascript:quote('#{+ID}');" else "#{url}#q#{ID}" postInfo = <%= readHTML('PostInfo.html') %> ### File Info ### if file protocol = /^https?:(?=\/\/i\.4cdn\.org\/)/ fileURL = file.url.replace protocol, '' shortFilename = Build.shortFilename file.name fileThumb = if file.isSpoiler then Build.spoilerThumb(boardID) else file.thumbURL.replace(protocol, '') fileBlock = <%= readHTML('File.html') %> ### Whole Post ### postClass = if o.isReply then 'reply' else 'op' wholePost = <%= readHTML('Post.html') %> container = $.el 'div', className: "postContainer #{postClass}Container" id: "pc#{ID}" $.extend container, wholePost # Fix quotelinks for quote in $$ '.quotelink', container href = quote.getAttribute 'href' if (href[0] is '#') if !Build.sameThread(boardID, threadID) quote.href = Build.threadURL(boardID, threadID) + href else if (match = quote.href.match SW.yotsuba.regexp.quotelink) and (Build.sameThread match[1], match[2]) quote.href = href.match(/(#[^#]*)?$/)[0] or '#' container summaryText: (status, posts, files) -> text = '' text += "#{status} " if status text += "#{posts} post#{if posts > 1 then 's' else ''}" text += " and #{files} image repl#{if files > 1 then 'ies' else 'y'}" if +files text += " #{if status is '-' then 'shown' else 'omitted'}." summary: (boardID, threadID, posts, files) -> $.el 'a', className: 'summary' textContent: Build.summaryText '', posts, files href: "/#{boardID}/thread/#{threadID}" thread: (thread, data, withReplies) -> if (root = thread.nodes.root) $.rmAll root else thread.nodes.root = root = $.el 'div', className: 'thread' id: "t#{data.no}" $.add root, Build.hat.cloneNode(false) if Build.hat $.add root, thread.OP.nodes.root if data.omitted_posts or !withReplies and data.replies [posts, files] = if withReplies # XXX data.omitted_images is not accurate. [data.omitted_posts, data.images - data.last_replies.filter((data) -> !!data.ext).length] else [data.replies, data.images] summary = Build.summary thread.board.ID, data.no, posts, files $.add root, summary root catalogThread: (thread, data, pageCount) -> {staticPath, gifIcon} = Build {tn_w, tn_h} = data if data.spoiler and !Conf['Reveal Spoiler Thumbnails'] src = "#{staticPath}spoiler" if spoilerRange = Build.spoilerRange[thread.board] # Randomize the spoiler image. src += ("-#{thread.board}") + Math.floor 1 + spoilerRange * Math.random() src += '.png' imgClass = 'spoiler-file' cssText = "--tn-w: 100; --tn-h: 100;" else if data.filedeleted src = "#{staticPath}filedeleted-res#{gifIcon}" imgClass = 'deleted-file' else if thread.OP.file src = thread.OP.file.thumbURL ratio = 250 / Math.max(tn_w, tn_h) cssText = "--tn-w: #{tn_w * ratio}; --tn-h: #{tn_h * ratio};" else src = "#{staticPath}nofile.png" imgClass = 'no-file' postCount = data.replies + 1 fileCount = data.images + !!data.ext container = $.el 'div', <%= readHTML('CatalogThread.html') %> $.before thread.OP.nodes.info, [container.childNodes...] for br in $$('br', thread.OP.nodes.comment) when br.previousSibling and br.previousSibling.nodeName is 'BR' $.addClass br, 'extra-linebreak' root = $.el 'div', className: 'thread catalog-thread' id: "t#{thread}" $.addClass root, thread.OP.highlights... if thread.OP.highlights $.addClass root, 'noFile' unless thread.OP.file root.style.cssText = cssText or '' root catalogReply: (thread, data) -> excerpt = '' if data.com excerpt = Build.parseCommentDisplay(data.com).replace(/>>\d+/g, '').trim().replace(/\n+/g, ' // ') if data.ext excerpt or= "#{$.unescape data.filename}#{data.ext}" if data.com excerpt or= $.unescape data.com.replace(//gi, ' // ') excerpt or= '\xA0' excerpt = "#{excerpt[...70]}..." if excerpt.length > 73 link = Build.postURL thread.board.ID, thread.ID, data.no $.el 'div', {className: 'catalog-reply'}, <%= readHTML('CatalogReply.html') %> SW.yotsuba.Build = Build