Continue merging, cleanup Globals.coffee

This commit is contained in:
Zixaphir 2015-01-08 20:35:03 -07:00
parent 3b097a31f2
commit 2461036a4e
10 changed files with 4238 additions and 3746 deletions

View File

@ -48,6 +48,8 @@ module.exports = (grunt) ->
'src/General/Cheats.coffee' 'src/General/Cheats.coffee'
'src/General/Config.coffee' 'src/General/Config.coffee'
'src/General/Globals.coffee' 'src/General/Globals.coffee'
'src/General/Mascots.coffee'
'src/General/Themes.coffee'
'src/General/lib/*.coffee' 'src/General/lib/*.coffee'
'src/General/Header.coffee' 'src/General/Header.coffee'
'src/General/Index.coffee' 'src/General/Index.coffee'
@ -57,8 +59,8 @@ module.exports = (grunt) ->
'src/General/CrossOrigin.coffee' 'src/General/CrossOrigin.coffee'
'src/Filtering/**/*.coffee' 'src/Filtering/**/*.coffee'
'src/Quotelinks/**/*.coffee' 'src/Quotelinks/**/*.coffee'
'src/Posting/Captcha.coffee' 'src/Posting/**/QR.coffee'
'src/Posting/**/*.coffee' 'src/Posting/**/QR.*.coffee'
'src/Images/**/*.coffee' 'src/Images/**/*.coffee'
'src/Linkification/**/*.coffee' 'src/Linkification/**/*.coffee'
'src/Menu/**/*.coffee' 'src/Menu/**/*.coffee'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -83,7 +83,7 @@ Redirect =
securityCheck: (URL) -> securityCheck: (URL) ->
/^https:\/\//.test(URL) or /^https:\/\//.test(URL) or
location.protocol is 'http:' or location.protocol is 'http:' or
Conf['Except Archives from Encryption'] Conf['Exempt Archives from Encryption']
navigate: (URL, alternative) -> navigate: (URL, alternative) ->
if URL and ( if URL and (

View File

@ -0,0 +1,87 @@
<% if (tests_enabled) { %>
BuildTest =
init: ->
return if !Conf['Menu'] or g.VIEW not in ['index', 'thread']
a = $.el 'a',
textContent: 'Test HTML building'
$.on a, 'click', @testOne
Menu.menu.addEntry
el: a
open: (post) ->
a.dataset.fullID = post.fullID
true
a2 = $.el 'a',
textContent: 'Test HTML building'
$.on a2, 'click', @testAll
Header.menu.addEntry
el: a2
firstDiff: (x, y) ->
x2 = x.cloneNode false
y2 = y.cloneNode false
return [x2, y2] unless x2.isEqualNode y2
i = 0
while true
x2 = x.childNodes[i]
y2 = y.childNodes[i]
return [x2, y2] unless x2 and y2
return BuildTest.firstDiff(x2, y2) unless x2.isEqualNode y2
i++
testOne: (post) ->
BuildTest.postsRemaining++
$.cache "//a.4cdn.org/#{post.board.ID}/thread/#{post.thread.ID}.json", ->
{posts} = @response
Build.spoilerRange[post.board.ID] = posts[0].custom_spoiler
for postData in posts
if postData.no is post.ID
t1 = new Date().getTime()
root = Build.postFromObject postData, post.board.ID
t2 = new Date().getTime()
BuildTest.time += t2 - t1
post2 = new Post root, post.thread, post.board
x = post.normalizedOriginal
y = post2.normalizedOriginal
if x.isEqualNode y
c.log "#{post.fullID} correct"
else
c.log "#{post.fullID} differs"
BuildTest.postsFailed++
[x2, y2] = BuildTest.firstDiff x, y
c.log x2
c.log y2
c.log x.outerHTML
c.log y.outerHTML
BuildTest.postsRemaining--
BuildTest.report() if BuildTest.postsRemaining is 0
post2.isFetchedQuote = true
Main.callbackNodes Post, [post2]
testAll: ->
g.posts.forEach (post) ->
unless post.isClone or post.isFetchedQuote or $ '.abbr', post.nodes.comment
BuildTest.testOne post
return
postsRemaining: 0
postsFailed: 0
time: 0
report: ->
if BuildTest.postsFailed
new Notice 'warning', "#{BuildTest.postsFailed} post(s) differ (#{BuildTest.time} ms)", 30
else
new Notice 'success', "All correct (#{BuildTest.time} ms)", 5
BuildTest.postsFailed = BuildTest.time = 0
cb:
testOne: ->
BuildTest.testOne g.posts[@dataset.fullID]
Menu.menu.close()
testAll: ->
BuildTest.testAll()
Header.menu.close()
<% } %>

View File

@ -25,6 +25,10 @@ Config =
true true
'Redirect dead threads and images.' 'Redirect dead threads and images.'
] ]
'Exempt Archives from Encryption': [
false
'Permit loading content from, and warningless redirects to, HTTP-only archives from HTTPS pages.'
]
'Keybinds': [ 'Keybinds': [
true true
'Bind actions to keyboard shortcuts.' 'Bind actions to keyboard shortcuts.'
@ -37,6 +41,11 @@ Config =
true true
'Display dates like "3 minutes ago". Tooltip shows the timestamp.' 'Display dates like "3 minutes ago". Tooltip shows the timestamp.'
] ]
'Relative Date Title': [
true
'Show Relative Post Date only when hovering over dates.'
1
]
'File Info Formatting': [ 'File Info Formatting': [
true true
'Reformat the file information.' 'Reformat the file information.'
@ -62,7 +71,7 @@ Config =
'Show dice that were entered into the email field.' 'Show dice that were entered into the email field.'
] ]
'Color User IDs': [ 'Color User IDs': [
false true
'Assign unique colors to user IDs on boards that use them' 'Assign unique colors to user IDs on boards that use them'
] ]
'Remove Spoilers': [ 'Remove Spoilers': [
@ -77,6 +86,10 @@ Config =
true true
'Warn if your browser is unsupported. 4chan X may not operate correctly on unsupported browser versions.' 'Warn if your browser is unsupported. 4chan X may not operate correctly on unsupported browser versions.'
] ]
'Normalize URL': [
true
'Rewrite the URL of the current page, removing stubs and changing /res/ to /thread/.'
]
'Announcement Hiding': [ 'Announcement Hiding': [
true true
'Enable announcements to be hidden.' 'Enable announcements to be hidden.'
@ -89,15 +102,23 @@ Config =
] ]
'Embedding': [ 'Embedding': [
true true
'Embed supported services.' 'Embed supported services. Note: Some services don\'t work on HTTPS.'
1
] ]
'Auto-embed': [ 'Auto-embed': [
false false
'Auto-embed Linkify Embeds.' 'Auto-embed Linkify Embeds.'
2
]
'Floating Embeds': [
false
'Embed content in a frame that remains in place when the page is scrolled.'
2
] ]
'Link Title': [ 'Link Title': [
true true
'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud, and Github gists' 'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud, and Github gists'
1
] ]
'Filtering': 'Filtering':
@ -137,15 +158,21 @@ Config =
] ]
'Image Hover in Catalog': [ 'Image Hover in Catalog': [
false false
'Show a floating expanded image on hover in the catalog.' 'Show full image / video on mouseover on hover in the catalog.'
] ]
'Gallery': [ 'Gallery': [
true true
'Adds a simple and cute image gallery.' 'Adds a simple and cute image gallery.'
] ]
'Fullscreen Gallery': [
false
'Open gallery in fullscreen mode.'
1
]
'PDF in Gallery': [ 'PDF in Gallery': [
false false
'Show PDF files in gallery.' 'Show PDF files in gallery.'
1
] ]
'Sauce': [ 'Sauce': [
true true
@ -173,31 +200,31 @@ Config =
] ]
'Image Prefetching': [ 'Image Prefetching': [
false false
'Preload images' 'Add link in header menu to turn on image preloading.'
] ]
'Fappe Tyme': [ 'Fappe Tyme': [
false false
'Hide posts without images when toggled. *hint* *hint*' 'Hide posts without images when header menu item is checked. *hint* *hint*'
] ]
'Werk Tyme': [ 'Werk Tyme': [
false false
'Hide all post images when toggled.' 'Hide all post images when header menu item is checked.'
] ]
'Autoplay': [ 'Autoplay': [
true true
'Videos begin playing immediately when opened.' 'Videos begin playing immediately when opened.'
] ]
'Restart when Opened': [
true
'Restart GIFs and WebMs when you hover over or expand them.'
]
'Show Controls': [ 'Show Controls': [
true true
'Show controls on videos expanded inline. Turn this off if you want to contract videos by clicking on them.' 'Show controls on videos expanded inline. Turn this off if you want to contract videos by clicking on them.'
] ]
'Allow Sound': [
true
'Allow sound in videos.'
]
'Loop in New Tab': [ 'Loop in New Tab': [
true true
'Loop videos opened in their own tabs, and apply settings for inline expanded videos to them.' 'Loop videos opened in their own tabs.'
] ]
'Menu': 'Menu':
@ -208,6 +235,7 @@ Config =
'Report Link': [ 'Report Link': [
true true
'Add a report link to the menu.' 'Add a report link to the menu.'
1
] ]
'Post Hiding Link': [ 'Post Hiding Link': [
true true
@ -216,10 +244,17 @@ Config =
'Delete Link': [ 'Delete Link': [
true true
'Add post and image deletion links to the menu.' 'Add post and image deletion links to the menu.'
1
]
'Download Link': [
true
'Add a download with original filename link to the menu.'
1
] ]
'Archive Link': [ 'Archive Link': [
true true
'Add an archive link to the menu.' 'Add an archive link to the menu.'
1
] ]
'Monitoring': 'Monitoring':
@ -231,9 +266,15 @@ Config =
true true
'Show the unread posts count in the tab title.' 'Show the unread posts count in the tab title.'
] ]
'Quoted Title': [
false
'Change the page title to reflect you\'ve been quoted.'
1
]
'Hide Unread Count at (0)': [ 'Hide Unread Count at (0)': [
false false
'Hide the unread posts count in the tab title when it reaches 0.' 'Hide the unread posts count in the tab title when it reaches 0.'
1
] ]
'Unread Favicon': [ 'Unread Favicon': [
true true
@ -249,7 +290,11 @@ Config =
] ]
'Thread Excerpt': [ 'Thread Excerpt': [
true true
'Show an excerpt of the thread in the tab title.' 'Show an excerpt of the thread in the tab title if not already present.'
]
'Remove Thread Excerpt': [
false
'Replace the excerpt of the thread in the tab title with the board title.'
] ]
'Thread Stats': [ 'Thread Stats': [
true true
@ -258,10 +303,12 @@ Config =
'IP Count in Stats': [ 'IP Count in Stats': [
true true
'Display the unique IP count in the thread stats.' 'Display the unique IP count in the thread stats.'
1
] ]
'Page Count in Stats': [ 'Page Count in Stats': [
true true
'Display the page count in the thread stats.' 'Display the page count in the thread stats.'
1
] ]
'Updater and Stats in Header': [ 'Updater and Stats in Header': [
true, true,
@ -288,30 +335,46 @@ Config =
'Persistent QR': [ 'Persistent QR': [
true true
'The Quick reply won\'t disappear after posting.' 'The Quick reply won\'t disappear after posting.'
1
] ]
'Auto Hide QR': [ 'Auto Hide QR': [
true true
'Automatically hide the quick reply when posting.' 'Automatically hide the quick reply when posting.'
1
] ]
'Open Post in New Tab': [ 'Open Post in New Tab': [
true true
'Open new threads or replies to a thread from the index in a new tab.' 'Open new threads or replies to a thread from the index in a new tab.'
1
]
'Remember QR Size': [
false
'Remember the size of the quick reply\'s comment field.'
1
] ]
'Remember Spoiler': [ 'Remember Spoiler': [
false false
'Remember the spoiler state, instead of resetting after posting.' 'Remember the spoiler state, instead of resetting after posting.'
] ]
'Remember QR Size': [ 'Show Name and Subject': [
false false
'Remember the size of the quick reply\'s comment field.' 'Show the classic name, email, and subject fields in the QR, even when 4chan doesn\'t use them all.'
1
] ]
'Cooldown': [ 'Cooldown': [
true true
'Indicate the remaining time before posting again.' 'Indicate the remaining time before posting again.'
1
] ]
'Posting Success Notifications': [ 'Posting Success Notifications': [
true true
'Show notifications on successful post creation or file uploading.' 'Show notifications on successful post creation or file uploading.'
1
]
'Force Noscript Captcha': [
false
'Use the non-Javascript fallback captcha in the QR even if Javascript is enabled.'
1
] ]
'Captcha Warning Notifications': [ 'Captcha Warning Notifications': [
true true
@ -320,16 +383,24 @@ Config =
'Dump List Before Comment': [ 'Dump List Before Comment': [
false false
'Position of the QR\'s Dump List.' 'Position of the QR\'s Dump List.'
1
] ]
'Auto-load captcha': [ 'Auto-load captcha': [
false false
'Automatically load the captcha in the QR even if your post is empty.' 'Automatically load the captcha in the QR even if your post is empty.'
1
]
'Post on Captcha Completion': [
false
'Submit the post immediately when the captcha is completed.'
1
] ]
'Quote Links': 'Quote Links':
'Quote Backlinks': [ 'Quote Backlinks': [
true true
'Add quote backlinks.' 'Add quote backlinks.'
1
] ]
'OP Backlinks': [ 'OP Backlinks': [
true true
@ -342,10 +413,12 @@ Config =
'Quote Hash Navigation': [ 'Quote Hash Navigation': [
false false
'Include an extra link after quotes for autoscrolling to quoted posts.' 'Include an extra link after quotes for autoscrolling to quoted posts.'
1
] ]
'Forward Hiding': [ 'Forward Hiding': [
true true
'Hide original posts of inlined backlinks.' 'Hide original posts of inlined backlinks.'
1
] ]
'Quote Previewing': [ 'Quote Previewing': [
true true
@ -354,14 +427,15 @@ Config =
'Quote Highlighting': [ 'Quote Highlighting': [
true true
'Highlight the previewed post.' 'Highlight the previewed post.'
1
] ]
'Resurrect Quotes': [ 'Resurrect Quotes': [
true true
'Link dead quotes to the archives.' 'Link dead quotes to the archives.'
] ]
'Quoted Title': [ 'Mark Quotes of You': [
false true
'Change the page title to reflect you\'ve been quoted.' 'Add \'(You)\' to quotes linking to your posts.'
] ]
'Highlight Posts Quoting You': [ 'Highlight Posts Quoting You': [
false false
@ -371,14 +445,6 @@ Config =
false false
'Highlights own posts if Quote Markers are enabled.' 'Highlights own posts if Quote Markers are enabled.'
] ]
'Quote Threading': [
false
'Thread conversations'
]
'Mark Quotes of You': [
true
'Add \'(You)\' to quotes linking to your posts.'
]
'Mark OP Quotes': [ 'Mark OP Quotes': [
true true
'Add \'(OP)\' to OP quotes.' 'Add \'(OP)\' to OP quotes.'
@ -388,23 +454,31 @@ Config =
'Add \'(Cross-thread)\' to cross-threads quotes.' 'Add \'(Cross-thread)\' to cross-threads quotes.'
'Highlights own posts if Quote Markers are enabled.' 'Highlights own posts if Quote Markers are enabled.'
] ]
'Quote Threading': [
false
'Thread conversations'
]
imageExpansion: imageExpansion:
'Fit width': [ 'Fit width': [
false true
'' ''
] ]
'Fit height': [ 'Fit height': [
false false
'' ''
] ]
'Expand spoilers': [ 'Scroll into view': [
true true
'Scroll down when expanding images to bring the full image into view.'
]
'Expand spoilers': [
false
'Expand all images along with spoilers.' 'Expand all images along with spoilers.'
] ]
'Expand videos': [ 'Expand videos': [
false false
'Expand all images also expands videos (no autoplay).' 'Expand all images also expands videos.'
] ]
'Expand from here': [ 'Expand from here': [
false false
@ -428,6 +502,9 @@ Config =
'Scroll to Post': [ 'Scroll to Post': [
true true
] ]
'Slide Delay': [
6.0
]
style: style:
@ -789,7 +866,7 @@ Config =
'Only show watched threads from the current board.' 'Only show watched threads from the current board.'
] ]
'Auto Watch': [ 'Auto Watch': [
true false
'Automatically watch threads you start.' 'Automatically watch threads you start.'
] ]
'Auto Watch Reply': [ 'Auto Watch Reply': [
@ -798,7 +875,7 @@ Config =
] ]
'Auto Prune': [ 'Auto Prune': [
false false
'Automatically prune 404\'d threads.' 'Automatically remove dead threads.'
] ]
filter: filter:
@ -824,8 +901,6 @@ Config =
#/Admin$/;highlight:moot;op:yes #/Admin$/;highlight:moot;op:yes
""" """
email: ""
subject: """ subject: """
# Filter Generals on /v/: # Filter Generals on /v/:
#/general/i;boards:v;op:only #/general/i;boards:v;op:only
@ -877,6 +952,8 @@ Config =
'Threads per Page': 0 'Threads per Page': 0
'Open threads in a new tab': false 'Open threads in a new tab': false
'Show Replies': true 'Show Replies': true
'Pin Watched Threads': false
'Anchor Hidden Threads': true
'Refreshed Navigation': false 'Refreshed Navigation': false
Header: Header:
@ -890,8 +967,22 @@ Config =
boardnav: """ boardnav: """
[ toggle-all ] [ toggle-all ]
[current-title] a-replace
[external-text:"FAQ","https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions"] c-replace
g-replace
k-replace
v-replace
vg-replace
vr-replace
ck-replace
co-replace
fit-replace
jp-replace
mu-replace
sp-replace
tv-replace
vp-replace
[external-text:"FAQ","<%= meta.faq %>"]
""" """
@ -909,29 +1000,29 @@ Config =
favicon: 'ferongr' favicon: 'ferongr'
usercss: """ usercss: """
/* Tripcode Italics: */ /* Tripcode Italics: */
/* /*
span.postertrip { span.postertrip {
font-style: italic; font-style: italic;
} }
*/ */
/* Add a rounded border to thumbnails (but not expanded images): */ /* Add a rounded border to thumbnails (but not expanded images): */
/* /*
.fileThumb > img:first-child { .fileThumb > img:first-child {
border: solid 2px rgba(0,0,100,0.5); border: solid 2px rgba(0,0,100,0.5);
border-radius: 10px; border-radius: 10px;
} }
*/ */
/* Make highlighted posts look inset on the page: */ /* Make highlighted posts look inset on the page: */
/* /*
div.post:target, div.post:target,
div.post.highlight { div.post.highlight {
box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2); box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
} }
*/ */
""" """
hotkeys: hotkeys:
# QR & Options # QR & Options
@ -977,7 +1068,7 @@ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
] ]
'Toggle sage': [ 'Toggle sage': [
'Alt+s' 'Alt+s'
'Toggle sage in email field.' 'Toggle sage in options field.'
] ]
'Submit QR': [ 'Submit QR': [
'Ctrl+Enter' 'Ctrl+Enter'
@ -994,7 +1085,7 @@ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
] ]
'Update': [ 'Update': [
'r' 'r'
'Update the thread now.' 'Update the thread / refresh the index.'
] ]
# Images # Images
'Expand image': [ 'Expand image': [
@ -1009,13 +1100,21 @@ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
'g' 'g'
'Opens the gallery.' 'Opens the gallery.'
] ]
'Pause': [
'p'
'Pause/play videos in the gallery.'
]
'Slideshow': [
's'
'Toggle the gallery slideshow mode.'
]
'fappeTyme': [ 'fappeTyme': [
'f' 'f'
'Fappe Tyme.' 'Toggle Fappe Tyme.'
] ]
'werkTyme': [ 'werkTyme': [
'Shift+w' 'Shift+w'
'Werk Tyme' 'Toggle Werk Tyme'
] ]
# Board Navigation # Board Navigation
'Front page': [ 'Front page': [
@ -1034,6 +1133,10 @@ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
'Ctrl+Left' 'Ctrl+Left'
'Jump to the previous page.' 'Jump to the previous page.'
] ]
'Open catalog': [
'Shift+c'
'Open the catalog of the current board.'
]
'Search form': [ 'Search form': [
'Ctrl+Alt+s' 'Ctrl+Alt+s'
'Focus the search field on the board index.' 'Focus the search field on the board index.'

View File

@ -1,9 +1,10 @@
Get = Get =
threadExcerpt: (thread) -> threadExcerpt: (thread) ->
{OP} = thread {OP} = thread
excerpt = OP.info.subject?.trim() or excerpt = "/#{thread.board}/ - " + (
OP.info.subject?.trim() or
OP.info.comment.replace(/\n+/g, ' // ') or OP.info.comment.replace(/\n+/g, ' // ') or
OP.info.nameBlock OP.info.nameBlock)
if excerpt.length > 70 if excerpt.length > 70
excerpt = "#{excerpt[...67]}..." excerpt = "#{excerpt[...67]}..."
"/#{thread.board}/ - #{excerpt}" "/#{thread.board}/ - #{excerpt}"
@ -36,7 +37,7 @@ Get =
# Get quotelinks & backlinks linking to the given post. # Get quotelinks & backlinks linking to the given post.
quotelinks = [] quotelinks = []
{posts} = g {posts} = g
fullID = {post} {fullID} = post
handleQuotes = (qPost, type) -> handleQuotes = (qPost, type) ->
quotelinks.push qPost.nodes[type]... quotelinks.push qPost.nodes[type]...
quotelinks.push clone.nodes[type]... for clone in qPost.clones quotelinks.push clone.nodes[type]... for clone in qPost.clones
@ -63,6 +64,11 @@ Get =
{boardID, postID} = Get.postDataFromLink quotelink {boardID, postID} = Get.postDataFromLink quotelink
boardID is post.board.ID and postID is post.ID boardID is post.board.ID and postID is post.ID
scriptData: ->
for script in $$ 'script:not([src])', d.head
return script.textContent if /\bcooldowns *=/.test script.textContent
''
postClone: (boardID, threadID, postID, root, context) -> postClone: (boardID, threadID, postID, root, context) ->
if post = g.posts["#{boardID}.#{postID}"] if post = g.posts["#{boardID}.#{postID}"]
Get.insert post, root, context Get.insert post, root, context
@ -72,12 +78,9 @@ Get =
if threadID if threadID
$.cache "//a.4cdn.org/#{boardID}/thread/#{threadID}.json", -> $.cache "//a.4cdn.org/#{boardID}/thread/#{threadID}.json", ->
Get.fetchedPost @, boardID, threadID, postID, root, context Get.fetchedPost @, boardID, threadID, postID, root, context
else if url = Redirect.to 'post', {boardID, postID} else
$.cache url, Get.archivedPost boardID, postID, root, context
-> Get.archivedPost @, boardID, postID, root, context
,
responseType: 'json'
withCredentials: url.archive.withCredentials
insert: (post, root, context) -> insert: (post, root, context) ->
# Stop here if the container has been removed while loading. # Stop here if the container has been removed while loading.
return unless root.parentNode return unless root.parentNode
@ -91,6 +94,8 @@ Get =
$.rmAll root $.rmAll root
$.add root, nodes.root $.add root, nodes.root
$.event 'PostsInserted'
fetchedPost: (req, boardID, threadID, postID, root, context) -> fetchedPost: (req, boardID, threadID, postID, root, context) ->
# In case of multiple callbacks for the same request, # In case of multiple callbacks for the same request,
# don't parse the same original post more than once. # don't parse the same original post more than once.
@ -101,19 +106,14 @@ Get =
{status} = req {status} = req
unless status in [200, 304] unless status in [200, 304]
# The thread can die by the time we check a quote. # The thread can die by the time we check a quote.
if url = Redirect.to 'post', {boardID, postID} return if Get.archivedPost boardID, postID, root, context
$.cache url,
-> Get.archivedPost @, boardID, postID, root, context $.addClass root, 'warning'
, root.textContent =
responseType: 'json' if status is 404
withCredentials: url.archive.withCredentials "Thread No.#{threadID} 404'd."
else else
$.addClass root, 'warning' "Error #{req.statusText} (#{req.status})."
root.textContent =
if status is 404
"Thread No.#{threadID} 404'd."
else
"Error #{req.statusText} (#{req.status})."
return return
{posts} = req.response {posts} = req.response
@ -122,15 +122,19 @@ Get =
break if post.no is postID # we found it! break if post.no is postID # we found it!
if post.no isnt postID if post.no isnt postID
# Cached requests can be stale and must be rechecked.
if req.cached
api = "//a.4cdn.org/#{boardID}/thread/#{threadID}.json"
$.cleanCache (url) -> url is api
$.cache api, ->
Get.fetchedPost @, boardID, threadID, postID, root, context
return
# The post can be deleted by the time we check a quote. # The post can be deleted by the time we check a quote.
if url = Redirect.to 'post', {boardID, postID} return if Get.archivedPost boardID, postID, root, context
$.cache url,
-> Get.archivedPost @, boardID, postID, root, context $.addClass root, 'warning'
, root.textContent = "Post No.#{postID} was not found."
withCredentials: url.archive.withCredentials
else
$.addClass root, 'warning'
root.textContent = "Post No.#{postID} was not found."
return return
board = g.boards[boardID] or board = g.boards[boardID] or
@ -138,36 +142,60 @@ Get =
thread = g.threads["#{boardID}.#{threadID}"] or thread = g.threads["#{boardID}.#{threadID}"] or
new Thread threadID, board new Thread threadID, board
post = new Post Build.postFromObject(post, boardID), thread, board post = new Post Build.postFromObject(post, boardID), thread, board
post.isFetchedQuote = true
Post.callbacks.execute [post] Post.callbacks.execute [post]
Get.insert post, root, context Get.insert post, root, context
archivedPost: (req, boardID, postID, root, context) ->
archivedPost: (boardID, postID, root, context) ->
return false unless Conf['Resurrect Quotes']
return false unless url = Redirect.to 'post', {boardID, postID}
if /^https:\/\//.test(url) or location.protocol is 'http:'
$.cache url,
-> Get.parseArchivedPost @response, boardID, postID, root, context
,
responseType: 'json'
withCredentials: url.archive.withCredentials
return true
else if Conf['Exempt Archives from Encryption']
CrossOrigin.json url, (response) ->
{media} = response
if media then for key of media when /_link$/.test key
# Image/thumbnail URLs loaded over HTTP can be modified in transit.
# Require them to be from a known HTTP host so that no referrer is sent to them from an HTTPS page.
delete media[key] unless media[key]? and media[key].match(/^(http:\/\/[^\/]+\/)?/)[0] in url.archive.imagehosts
Get.parseArchivedPost response, boardID, postID, root, context
return true
return false
parseArchivedPost: (data, boardID, postID, root, context) ->
# In case of multiple callbacks for the same request, # In case of multiple callbacks for the same request,
# don't parse the same original post more than once. # don't parse the same original post more than once.
if post = g.posts["#{boardID}.#{postID}"] if post = g.posts["#{boardID}.#{postID}"]
Get.insert post, root, context Get.insert post, root, context
return return
data = req.response
if data.error if data.error
$.addClass root, 'warning' $.addClass root, 'warning'
root.textContent = data.error root.textContent = data.error
return return
# convert comment to html
bq = $.el 'blockquote', textContent: data.comment # set this first to convert text to HTML entities
# https://github.com/eksopl/fuuka/blob/master/Board/Yotsuba.pm#L413-452 # https://github.com/eksopl/fuuka/blob/master/Board/Yotsuba.pm#L413-452
# https://github.com/eksopl/asagi/blob/master/src/main/java/net/easymodo/asagi/Yotsuba.java#L109-138 # https://github.com/eksopl/asagi/blob/master/src/main/java/net/easymodo/asagi/Yotsuba.java#L109-138
bq.innerHTML = bq.innerHTML.replace /// comment = (data.comment or '').split /(\n|\[\/?(?:b|spoiler|code|moot|banned)\])/
\n comment = for text, i in comment
| if i % 2 is 1
\[/?[a-z]+(:lit)?\] Get.archiveTags[text]
///g, Get.parseMarkup else
greentext = text[0] is '>'
comment = bq.innerHTML text = text.replace /(\[\/?[a-z]+):lit(\])/, '$1$2'
# greentext text = for text2, j in text.split /(>>(?:>\/[a-z\d]+\/)?\d+)/g
.replace /(^|>)(&gt;[^<$]*)(<|$)/g, '$1<span class=quote>$2</span>$3' if j % 2 is 1
# quotes <%= html('<span class="deadlink">${text2}</span>') %>
.replace /((&gt;){2}(&gt;\/[a-z\d]+\/)?\d+)/g, '<span class=deadlink>$1</span>' else
<%= html('${text2}') %>
text = <%= html('@{text}') %>
text = <%= html('<span class="quote">&{text}</span>') %> if greentext
text
comment = <%= html('@{comment}') %>
threadID = +data.thread_num threadID = +data.thread_num
o = o =
@ -176,26 +204,27 @@ Get =
threadID: threadID threadID: threadID
boardID: boardID boardID: boardID
# info # info
name: data.name_processed name: data.name
capcode: switch data.capcode capcode: switch data.capcode
when 'M' then 'mod' when 'M' then 'mod'
when 'A' then 'admin' when 'A' then 'admin'
when 'D' then 'developer' when 'D' then 'developer'
tripcode: data.trip tripcode: data.trip
uniqueID: data.poster_hash uniqueID: data.poster_hash
email: if data.email then encodeURI data.email else '' email: data.email or ''
subject: data.title_processed subject: data.title
flagCode: data.poster_country flagCode: data.poster_country
flagName: data.poster_country_name_processed flagName: data.poster_country_name
date: data.fourchan_date date: data.fourchan_date
dateUTC: data.timestamp dateUTC: data.timestamp
comment: comment comment: comment
# file # file
if data.media?.media_filename if data.media?.media_filename
o.file = o.file =
name: data.media.media_filename_processed name: data.media.media_filename
timestamp: data.media.media_orig timestamp: data.media.media_orig
url: data.media.media_link or data.media.remote_media_link url: data.media.media_link or data.media.remote_media_link or
"//i.4cdn.org/#{boardID}/#{encodeURIComponent data.media[if boardID is 'f' then 'media_filename' else 'media_orig']}"
height: data.media.media_h height: data.media.media_h
width: data.media.media_w width: data.media.media_w
MD5: data.media.media_hash MD5: data.media.media_hash
@ -204,26 +233,26 @@ Get =
theight: data.media.preview_h theight: data.media.preview_h
twidth: data.media.preview_w twidth: data.media.preview_w
isSpoiler: data.media.spoiler is '1' isSpoiler: data.media.spoiler is '1'
o.file.tag = JSON.parse(data.media.exif).Tag if boardID is 'f'
board = g.boards[boardID] or board = g.boards[boardID] or
new Board boardID new Board boardID
thread = g.threads["#{boardID}.#{threadID}"] or thread = g.threads["#{boardID}.#{threadID}"] or
new Thread threadID, board new Thread threadID, board
post = new Post Build.post(o, true), thread, board, {isArchived: true} post = new Post Build.post(o), thread, board, {isArchived: true}
$('.page-num', post.nodes.info)?.hidden = true post.file.thumbURL = o.file.turl if post.file
post.isFetchedQuote = true
Post.callbacks.execute [post] Post.callbacks.execute [post]
Get.insert post, root, context Get.insert post, root, context
parseMarkup: (text) -> archiveTags:
{ '\n': <%= html('<br>') %>
'\n': '<br>' '[b]': <%= html('<b>') %>
'[b]': '<b>' '[/b]': <%= html('</b>') %>
'[/b]': '</b>' '[spoiler]': <%= html('<s>') %>
'[spoiler]': '<s>' '[/spoiler]': <%= html('</s>') %>
'[/spoiler]': '</s>' '[code]': <%= html('<pre class="prettyprint">') %>
'[code]': '<pre class=prettyprint>' '[/code]': <%= html('</pre>') %>
'[/code]': '</pre>' '[moot]': <%= html('<div style="padding:5px;margin-left:.5em;border-color:#faa;border:2px dashed rgba(255,0,0,.1);border-radius:2px">') %>
'[moot]': '<div style="padding:5px;margin-left:.5em;border-color:#faa;border:2px dashed rgba(255,0,0,.1);border-radius:2px">' '[/moot]': <%= html('</div>') %>
'[/moot]': '</div>' '[banned]': <%= html('<strong style="color: red;">') %>
'[banned]': '<strong style="color: red;">' '[/banned]': <%= html('</strong>') %>
'[/banned]': '</strong>'
}[text] or text.replace ':lit', ''

File diff suppressed because it is too large Load Diff

476
src/General/Mascots.coffee Normal file
View File

@ -0,0 +1,476 @@
Mascots =
'Akiyama_Mio':
category: 'Anime'
image: '//i.minus.com/ibrWLbKvjRnHZS.png'
'Akiyama_Mio_2':
category: 'Anime'
image: '//i.minus.com/ibmZgHvl3ZSxYk.png'
'Akiyama_Mio_3':
category: 'Anime'
image: '//i.minus.com/irFbpefCFt1cT.png'
center: true
'Applejack':
category: 'Ponies'
image: '//i.minus.com/inZ8jSVsEhfnC.png'
center: true
'Asuka_Langley_Soryu':
category: 'Anime'
image: '//i.minus.com/ib2z9ME9QKEBaS.png'
center: true
'Asuka_Langley_Soryu_2':
category: 'Anime'
image: '//i.minus.com/iI3QR5SywNfg9.png'
center: true
'Asuka_Langley_Soryu_3':
category: 'Anime'
image: '//i4.minus.com/jbq2hFrE4q75KT.png'
center: true
'Asuka_Langley_Soryu_4':
category: 'Anime'
image: '//i.minus.com/ibiiInQGLGnYNj.png'
center: true
'Asuka_Langley_Soryu_6':
category: 'Anime'
image: '//i.minus.com/ibzbnBcaEtoqck.png'
position: 'bottom'
'Ayanami_Rei':
category: 'Anime'
image: '//i.minus.com/ib0ft5OmqRZx2r.png'
center: true
'Ayase':
category: 'Anime'
image: '//i.minus.com/ibmArq5Wb4Po4v.png'
center: true
'BLACK_ROCK_SHOOTER':
category: 'Anime'
image: '//i.minus.com/ibMe9MrTMdvAT.png'
center: true
'Brioche_d_Arquien':
category: 'Anime'
image: '//i.minus.com/ibobXYJ2k3JXK.png'
center: true
'CC':
category: 'Anime'
image: '//i.minus.com/iwndO4Pn6SO0X.png'
center: true
'Chie':
category: 'Anime'
image: '//i.minus.com/ib0HI16h9FSjSp.png'
center: true
'Cirno_2':
category: 'Anime'
image: '//i.minus.com/iSZ06ZxrcqAKq.png'
center: true
'Dawn_Hikari':
category: 'Anime'
image: '//i.minus.com/iL3J1EmcDkFzE.png'
center: true
'Doppleganger':
category: 'Anime'
image: '//i.minus.com/iPvv86W9r3Rxm.png'
'Dragonkid':
category: 'Anime'
image: '//i.minus.com/iq9fuyWSjIDWf.png'
center: true
'Dragonkid_2':
category: 'Anime'
image: '//i.minus.com/i7sdxK3G12RB6.png'
center: true
'Eclair':
category: 'Anime'
image: '//i.minus.com/ibsk5mMYVR5zuA.png'
center: true
'Evangeline_AK_McDowell':
category: 'Anime'
image: '//i.minus.com/ibuq7a8zWKi2gl.png'
center: true
'Fluttershy':
category: 'Ponies'
image: '//i.minus.com/ibwEFEGlRm0Uxy.png'
'Fluttershy_2':
category: 'Ponies'
image: '//i.minus.com/ibjtz6EU2OFPgh.png'
center: true
'Fluttershy_Cutiemark':
category: 'Ponies'
image: '//i.minus.com/i5WVpIAlHQdhs.png'
center: true
'Fujiwara_no_Mokou':
category: 'Anime'
image: '//i.minus.com/ibpwDyMGodvni6.png'
'Furudo_Erika':
category: 'Anime'
image: '//i.minus.com/iCrRzQ8WvHiSM.png'
center: true
'Gally':
category: 'Anime'
image: '//i.minus.com/iblWZGuSlWtDI6.png'
center: true
'Gasai_Yuno':
category: 'Anime'
image: '//i.minus.com/iEQsK6K85jX2n.png'
'George_Costanza':
category: 'Western'
image: '//i.minus.com/iFWdpFGfzLs6v.png'
'Hanako':
category: 'Anime'
image: '//i.minus.com/iRLF8gCIZbGjo.png'
center: true
'Hasekura_Youko':
category: 'Anime'
image: '//i.minus.com/iqBTFZf5UhLpR.png'
center: true
'Hatsune_Miku_3':
category: 'Anime'
image: '//i.minus.com/iLJ4uDTcg1T8r.png'
center: true
'Hatsune_Miku_4':
category: 'Anime'
image: '//i.minus.com/ibjkPMLT8Uxitp.png'
center: true
'Hatsune_Miku_5':
category: 'Anime'
image: '//i.minus.com/i9Evu9dyvok4G.png'
center: true
'Hirasawa_Yui':
category: 'Anime'
image: '//i.minus.com/iuGe5uDaTNmhR.png'
center: true
'Homura_Akemi':
category: 'Anime'
image: '//i.minus.com/iPtrwFEEtPLhn.png'
'Horo':
category: 'Silhouette'
image: '//i.minus.com/ibbMKiznORGJ00.png'
silhouette: true
'Horo_2':
category: 'Silhouette'
image: '//i.minus.com/ibyT9dlTe1HN5P.png'
silhouette: true
width: 205
'Ika_Musume':
category: 'Anime'
image: '//i.minus.com/ibqVu5GNfKx5bC.png'
center: true
'Ika_Musume_2':
category: 'Anime'
image: '//i.minus.com/ibhnEiE8HabEqC.png'
center: true
'Iwakura_Lain':
category: 'Anime'
image: '//i.minus.com/iBXRRT19scoHf.png'
center: true
'Iwakura_Lain_2':
category: 'Anime'
image: '//i.minus.com/ioMltWNYUWeJ3.png'
center: true
'KOn_Girls':
category: 'Anime'
image: '//i.minus.com/ibndVLiH09uINs.png'
center: true
'Kagamine_Rin_2':
category: 'Anime'
image: '//i.minus.com/jbkL01TIeJwEN6.png'
'Kagari_Izuriha':
category: 'Anime'
image: '//i.minus.com/ihaFHsvFfL0vH.png'
'Kaname_Madoka':
category: 'Anime'
image: '//i.minus.com/iRuEFK8cdAHxB.png'
center: true
'Karina':
category: 'Anime'
image: '//i.minus.com/iUADBOpQYPfeP.png'
center: true
'Kigurumi_Harokitei':
category: 'Anime'
image: '//i.minus.com/ibb17W5i3rQvut.png'
center: true
'Kinomoto_Sakura':
category: 'Anime'
image: '//i.minus.com/iVmsLKa4zLwZR.png'
center: true
'Kirisame_Marisa':
category: 'Anime'
image: '//i.minus.com/ibikDZH5CZ0V30.png'
'Koiwai_Yotsuba':
category: 'Anime'
image: '//i.minus.com/iKFKyVVBato2N.png'
center: true
'Koko':
category: 'Anime'
image: '//i.minus.com/ieVyNMSjXpBs2.png'
center: true
'Kotobuki_Tsumugi':
category: 'Anime'
image: '//i.minus.com/i6doAUnM6jMAY.png'
center: true
'Kurisu_Makise':
category: 'Anime'
image: '//i.minus.com/ib1eMtRHdvc9ix.png'
'Kuroko_Shirai':
category: 'Anime'
image: '//i.minus.com/i3K8F7lu2SHfn.png'
'Kyouko_Sakura':
category: 'Anime'
image: '//i.minus.com/iMrFOS1mfzIJP.png'
center: true
'Kyubee':
category: 'Anime'
image: '//i.minus.com/iD0SEJPeZa0Dw.png'
'Kyubee_2':
category: 'Anime'
image: '//i.minus.com/iGlKiDZvM3xi8.png'
center: true
'Li_Syaoran':
category: 'Anime'
image: '//i.minus.com/ib0IWPBRSHyiDe.png'
'Link':
category: 'Anime'
image: '//i.minus.com/ibd1JShAMTdJBH.png'
center: true
'Lizardgirl':
category: 'Anime'
image: '//i.minus.com/is7h27Q6lsmyx.png'
'Luka':
category: 'Anime'
image: '//i.minus.com/inds5h2BOmVBy.png'
'Madotsuki':
category: 'Anime'
image: '//i.minus.com/ik6QYfTfgx9Za.png'
'Makoto':
category: 'Anime'
image: '//i.minus.com/i7q6aOuUqqA9F.png'
center: true
'Mantis':
category: 'Anime'
image: '//i.minus.com/iBmluUJOZivY2.png'
'Megurine_Luka':
category: 'Anime'
image: '//i.minus.com/ibxe63yidpz9Gz.png'
center: true
'Mei_Sunohara':
category: 'Anime'
image: '//i.minus.com/i7ElzNY4xQHHz.png'
center: true
'Millefiori':
category: 'Anime'
image: '//i.minus.com/ifVzPtH8JHXjl.png'
center: true
'Millefiori_2':
category: 'Anime'
image: '//i.minus.com/iMSUiQxRBylQG.png'
center: true
'Millefiori_3':
category: 'Anime'
image: '//i.minus.com/iDOe3ltSvOYXZ.png'
center: true
'Misaki_Mei':
category: 'Anime'
image: '//i.minus.com/icmYGJ9vIOFjr.png'
center: true
'Mizunashi_Akari':
category: 'Anime'
image: '//i.minus.com/iNy9kHlNsUoVK.png'
center: true
'Motoko':
category: 'Anime'
image: '//i.minus.com/irFtkWWyMChSA.png'
center: true
'Nagato_Yuki':
category: 'Anime'
image: '//i.minus.com/it3pEawWIxY84.png'
center: true
'Nagato_Yuki_2':
category: 'Anime'
image: '//i.minus.com/iuspcZbLvmqpb.png'
center: true
'Nagato_Yuki_3':
category: 'Anime'
image: '//i.minus.com/ibndIkldw4njbD.png'
center: true
'Nagato_Yuki_5':
category: 'Silhouette'
width: 202
image: '//i.minus.com/iFQQPEaC3aEV7.png'
silhouette: true
center: true
'Nakano_Azusa':
category: 'Anime'
image: '//i.minus.com/iiptfoMlr4v1k.png'
'Nichijou':
category: 'Anime'
image: '//i.minus.com/iE8lbZ5f3OT2B.png'
'Noir_VinoCacao':
category: 'Anime'
image: '//i.minus.com/ibo8aCWF0OwNwP.png'
center: true
'Pinkie_Pie':
category: 'Ponies'
image: '//i.minus.com/ib1kcpqxvsyZWG.png'
center: true
'Pinkie_Pie_2':
category: 'Ponies'
image: '//i.minus.com/i8QRRgE7iKpw7.png'
center: true
'Oshino_Shinobu':
category: 'Anime'
image: '//i.minus.com/ibwhAyR6D7OBAB.png'
'Oshino_Shinobu_2':
category: 'Anime'
image: '//i.minus.com/ibqoNiWzynsVvg.png'
position: 'bottom'
'Patchouli_Knowledge':
category: 'Anime'
image: '//i.minus.com/ibnOEAxXaKlctB.png'
center: true
'Patchouli_Knowledge_2':
category: 'Anime'
image: '//i.minus.com/i1MOPTmohOsMD.png'
'Pink_Doggy':
category: 'Anime'
image: '//i.minus.com/i1SpWAzfcIEQc.png'
center: true
'Pink_Hair':
category: 'Anime'
image: '//i.minus.com/ibdwMaIPwdscao.png'
center: true
'Railgun_2':
category: 'Anime'
image: '//i.minus.com/iNhpDDO0GSTeM.png'
center: true
'Railgun_3':
category: 'Anime'
image: '//i.minus.com/iiW02dmqUwRcy.png'
'Railgun_4':
category: 'Anime'
image: '//i.minus.com/iR3j0mGgd1927.png'
center: true
'Rainbow_Dash':
category: 'Ponies'
image: '//i.minus.com/ibthr5EDMZHV9j.png'
center: true
'Rarity':
category: 'Ponies'
image: '//i.minus.com/ibkraGhhUh25CU.png'
center: true
'Revi':
category: 'Anime'
image: '//i.minus.com/ivUMKcy5ow6Ab.png'
position: 'bottom'
center: true
'Ruri_Gokou':
category: 'Anime'
image: '//i.minus.com/ibtZo1fdOk8NCB.png'
position: 'bottom'
center: true
'Ryuu':
category: 'Anime'
image: '//i.minus.com/iecVz4p2SuqK4.png'
position: 'bottom'
'Samus_Aran':
category: 'Anime'
image: '//i.minus.com/iWG1GFJ89A05p.png'
center: true
'Samus_Aran_2':
category: 'Anime'
image: '//i.minus.com/ibl4efsNtHpkXg.png'
'Shana':
category: 'Anime'
image: '//i.minus.com/ib2cTJMF0cYIde.png'
center: true
'Shana_2':
category: 'Anime'
image: '//i.minus.com/ioRICGu0Ipzj9.png'
center: true
'Shiki':
category: 'Anime'
image: '//i.minus.com/iIZm1JxxDIDQ1.png'
'Shinji_and_Girls':
category: 'Anime'
image: '//i.minus.com/itMrEn56GzvzE.png'
center: true
'Shinonome_Hakase':
category: 'Anime'
image: '//i.minus.com/iocCwDCnNgI19.png'
center: true
'Shirakiin_Ririchiyo':
category: 'Anime'
image: '//i.minus.com/i1m0rdzmVLYLa.png'
position: 'bottom'
center: true
'Shirohibe':
category: 'Anime'
image: '//i.minus.com/iGu91k3KZeg00.png'
position: 'bottom'
'Suruga_Kanbaru':
category: 'Anime'
image: '//i.minus.com/irEL7AgC80qKD.png'
center: true
'Suzumiya_Haruhi':
category: 'Anime'
image: '//i.minus.com/iM9qMfUNh9Qi9.png'
center: true
'Suzumiya_Haruhi_2':
category: 'Anime'
image: '//i.minus.com/ibnomd5iasjceY.png'
center: true
'Tardis':
category: 'Western'
image: '//i.minus.com/iQL2bwpDfOgk.png'
center: true
'Tewi_Inaba':
category: 'Anime'
image: '//i.minus.com/ib2k9lwQIkmb66.png'
'Tomozo_Kaoru':
category: 'Anime'
image: '//i.minus.com/islUcBaPRYAgv.png'
center: true
'Twilight_Sparkle':
category: 'Ponies'
image: '//i.minus.com/ibnMYVTZEykrKU.png'
center: true
'Vivian_James_1':
category: 'Anime'
image: '//i.minus.com/iYZ05HyI7aPUA.png'
'Vivian_James_2':
category: 'Anime'
image: '//i.minus.com/ibtKEioz8jN37k.png'
'White_Curious':
category: 'Anime'
image: '//i.minus.com/ibfkj5osu99axe.png'
center: true
'Yakumo_Ran':
category: 'Anime'
image: '//i.minus.com/ivKqn8vL9A8cQ.png'
'Yin':
category: 'Anime'
image: '//i.minus.com/iL9DlVtaAGFdq.png'
'Yin_2':
category: 'Anime'
image: '//i.minus.com/izkTpyjr1XlLR.png'
center: true
'Yoko_Littner_2':
category: 'Anime'
image: '//i.minus.com/i7aUDY4h9uB1T.png'
center: true
'Yoko_Littner_3':
category: 'Anime'
image: '//i.minus.com/iYVd5DhCmB7VJ.png'
center: true
'Yozora_Mikazuki':
category: 'Anime'
image: '//i.minus.com/iIFEsDzoDALQd.png'
'Yuzuki_Yukari':
category: 'Anime'
image: '//i.minus.com/iYQOz0iGM9ygq.png'
center: true
'Yukkikaze':
category: 'Anime'
image: '//i.minus.com/ioQJAnyXebHDJ.png'
center: true
'Yukkihaze_2':
category: 'Anime'
image: '//i.minus.com/inpgaDlJtZ9Sc.png'
center: true

2457
src/General/Themes.coffee Normal file

File diff suppressed because it is too large Load Diff