Build =
staticPath: '//s.4cdn.org/image/'
gifIcon: if window.devicePixelRatio >= 2 then '@2x.gif' else '.gif'
spoilerRange: {}
shortFilename: (filename, isReply) ->
# FILENAME SHORTENING SCIENCE:
# OPs have a +10 characters threshold.
# The file extension is not taken into account.
threshold = if isReply then 30 else 40
ext = filename.match(/\.[^.]+$/)[0]
if filename.length - ext.length > threshold
"#{filename[...threshold - 5]}(...)#{ext}"
else
filename
thumbRotate: do ->
n = 0
-> n = (n + 1) % 3
postFromObject: (data, boardID) ->
o =
# id
postID: data.no
threadID: data.resto or data.no
boardID: boardID
# info
name: data.name
capcode: data.capcode
tripcode: data.trip
uniqueID: data.id
email: if data.email then encodeURI data.email.replace /"/g, '"' else ''
subject: data.sub
flagCode: data.country
flagName: data.country_name
date: data.now
dateUTC: data.time
comment: data.com
# thread status
isSticky: !!data.sticky
isClosed: !!data.closed
# file
if data.ext or data.filedeleted
o.file =
name: data.filename + data.ext
timestamp: "#{data.tim}#{data.ext}"
url: if boardID is 'f'
"//i.4cdn.org/#{boardID}/src/#{data.filename}#{data.ext}"
else
"//i.4cdn.org/#{boardID}/src/#{data.tim}#{data.ext}"
height: data.h
width: data.w
MD5: data.md5
size: data.fsize
turl: "//#{Build.thumbRotate()}.t.4cdn.org/#{boardID}/thumb/#{data.tim}s.jpg"
theight: data.tn_h
twidth: data.tn_w
isSpoiler: !!data.spoiler
isDeleted: !!data.filedeleted
Build.post o
post: (o, isArchived) ->
###
This function contains code from 4chan-JS (https://github.com/4chan/4chan-JS).
@license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
###
{
postID, threadID, boardID
name, capcode, tripcode, uniqueID, email, subject, flagCode, flagName, date, dateUTC
isSticky, isClosed
comment
file
} = o
isOP = postID is threadID
{staticPath, gifIcon} = Build
tripcode = if tripcode
" #{tripcode}"
else
''
if email
emailStart = ''
emailEnd = ''
else
emailStart = ''
emailEnd = ''
switch capcode
when 'admin', 'admin_highlight'
capcodeClass = " capcodeAdmin"
capcodeStart = " ## Admin"
capcodeIcon = "
"
when 'mod'
capcodeClass = " capcodeMod"
capcodeStart = " ## Mod"
capcodeIcon = "
"
when 'developer'
capcodeClass = " capcodeDeveloper"
capcodeStart = " ## Developer"
capcodeIcon = "
"
else
capcodeClass = ''
capcodeStart = ''
capcodeIcon = ''
userID =
if !capcode and uniqueID
" (ID: " +
"#{uniqueID}) "
else
''
flag = unless flagCode
''
else if boardID is 'pol'
"
"
else
" "
if file?.isDeleted
fileHTML = if isOP
"
" +
"
" +
" "
else
"" +
"
" +
" "
else if file
fileSize = $.bytesToString file.size
fileThumb = file.turl
if file.isSpoiler
fileSize = "Spoiler Image, #{fileSize}"
unless isArchived
fileThumb = "#{staticPath}spoiler"
if spoilerRange = Build.spoilerRange[boardID]
# Randomize the spoiler image.
fileThumb += "-#{boardID}" + Math.floor 1 + spoilerRange * Math.random()
fileThumb += '.png'
file.twidth = file.theight = 100
imgSrc = if boardID is 'f'
''
else
"" +
"
" +
""
# html -> text, translate WebKit's %22s into "s
a = $.el 'a', innerHTML: file.name
filename = a.textContent.replace /%22/g, '"'
# shorten filename, get html
a.textContent = Build.shortFilename filename
shortFilename = a.innerHTML
# get html
a.textContent = filename
filename = a.innerHTML.replace /'/g, '''
fileDims = if file.name[-3..] is 'pdf' then 'PDF' else "#{file.width}x#{file.height}"
fileInfo = "File:
#{file.timestamp}" +
"-(#{fileSize}, #{fileDims}#{
if file.isSpoiler
''
else
",
#{shortFilename}"
}" + ")
"
fileHTML = "#{fileInfo}#{imgSrc}
"
else
fileHTML = ''
sticky = if isSticky
"
"
else
''
closed = if isClosed
"
"
else
''
if isOP and g.VIEW is 'index'
pageNum = Index.liveThreadData.keys.indexOf("#{postID}") // Index.threadsNumPerPage
pageIcon = " Page #{pageNum}"
replyLink = " [Reply]"
else
pageIcon = replyLink = ''
container = $.el 'div',
id: "pc#{postID}"
className: "postContainer #{if isOP then 'op' else 'reply'}Container"
innerHTML: <%= grunt.file.read('src/General/html/Build/post.html').replace(/>\s+/g, '>').replace(/\s+
for quote in $$ '.quotelink', container
href = quote.getAttribute 'href'
continue if href[0] is '/' # Cross-board quote, or board link
quote.href = "/#{boardID}/res/#{href}" # Fix pathnames
container
summary: (boardID, threadID, posts, files) ->
text = []
text.push "#{posts} post#{if posts > 1 then 's' else ''}"
text.push "and #{files} image repl#{if files > 1 then 'ies' else 'y'}" if files
text.push 'omitted.'
$.el 'a',
className: 'summary'
textContent: text.join ' '
href: "/#{boardID}/res/#{threadID}"
thread: (board, data, full) ->
Build.spoilerRange[board] = data.custom_spoiler
if (OP = board.posts[data.no]) and root = OP.nodes.root.parentNode
$.rmAll root
$.add root, OP.nodes.root
else
root = $.el 'div',
className: 'thread'
id: "t#{data.no}"
$.add root, Build[if full then 'fullThread' else 'excerptThread'] board, data, OP
root
excerptThread: (board, data, OP) ->
nodes = [if OP then OP.nodes.root else Build.postFromObject data, board.ID]
if data.omitted_posts or !Conf['Show Replies'] and data.replies
[posts, files] = if Conf['Show Replies']
[data.omitted_posts, data.omitted_images]
else
[data.replies, data.images]
nodes.push Build.summary board.ID, data.no, posts, files
nodes
fullThread: (board, data) -> Build.postFromObject data, board.ID
catalogThread: (thread) ->
{staticPath, gifIcon} = Build
data = Index.liveThreadData[thread.ID]
postCount = data.replies + 1
fileCount = data.images + !!data.ext
pageCount = Index.liveThreadData.keys.indexOf("#{thread.ID}") // Index.threadsNumPerPage
subject = if thread.OP.info.subject
"#{thread.OP.info.subject}
"
else
''
comment = thread.OP.nodes.comment.innerHTML.replace /(
\s*){2,}/g, '
'
root = $.el 'div',
className: 'catalog-thread'
innerHTML: <%= importHTML('Features/Thread-catalog-view') %>
root.dataset.fullID = thread.fullID
$.addClass root, 'pinned' if thread.isPinned
$.addClass root, thread.OP.highlights... if thread.OP.highlights.length
thumb = root.firstElementChild
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'
$.addClass thumb, 'spoiler-file'
else if data.filedeleted
src = "#{staticPath}filedeleted-res#{gifIcon}"
$.addClass thumb, 'deleted-file'
else if thread.OP.file
src = thread.OP.file.thumbURL
thumb.dataset.width = data.tn_w
thumb.dataset.height = data.tn_h
else
src = "#{staticPath}nofile.png"
$.addClass thumb, 'no-file'
thumb.style.backgroundImage = "url(#{src})"
if Conf['Open threads in a new tab']
thumb.target = '_blank'
for quotelink in $$ '.quotelink', root.lastElementChild
$.replace quotelink, [quotelink.childNodes...]
for pp in $$ '.prettyprint', root.lastElementChild
$.replace pp, $.tn pp.textContent
if thread.isSticky
$.add $('.thread-icons', root), $.el 'img',
src: "#{staticPath}sticky#{gifIcon}"
className: 'stickyIcon'
title: 'Sticky'
if thread.isClosed
$.add $('.thread-icons', root), $.el 'img',
src: "#{staticPath}closed#{gifIcon}"
className: 'closedIcon'
title: 'Closed'
if data.bumplimit
$.addClass $('.post-count', root), 'warning'
if data.imagelimit
$.addClass $('.file-count', root), 'warning'
root