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/Config.coffee'
'src/General/Globals.coffee'
'src/General/Mascots.coffee'
'src/General/Themes.coffee'
'src/General/lib/*.coffee'
'src/General/Header.coffee'
'src/General/Index.coffee'
@ -57,8 +59,8 @@ module.exports = (grunt) ->
'src/General/CrossOrigin.coffee'
'src/Filtering/**/*.coffee'
'src/Quotelinks/**/*.coffee'
'src/Posting/Captcha.coffee'
'src/Posting/**/*.coffee'
'src/Posting/**/QR.coffee'
'src/Posting/**/QR.*.coffee'
'src/Images/**/*.coffee'
'src/Linkification/**/*.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) ->
/^https:\/\//.test(URL) or
location.protocol is 'http:' or
Conf['Except Archives from Encryption']
Conf['Exempt Archives from Encryption']
navigate: (URL, alternative) ->
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
'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': [
true
'Bind actions to keyboard shortcuts.'
@ -37,6 +41,11 @@ Config =
true
'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': [
true
'Reformat the file information.'
@ -62,7 +71,7 @@ Config =
'Show dice that were entered into the email field.'
]
'Color User IDs': [
false
true
'Assign unique colors to user IDs on boards that use them'
]
'Remove Spoilers': [
@ -77,6 +86,10 @@ Config =
true
'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': [
true
'Enable announcements to be hidden.'
@ -89,15 +102,23 @@ Config =
]
'Embedding': [
true
'Embed supported services.'
'Embed supported services. Note: Some services don\'t work on HTTPS.'
1
]
'Auto-embed': [
false
'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': [
true
'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud, and Github gists'
1
]
'Filtering':
@ -137,15 +158,21 @@ Config =
]
'Image Hover in Catalog': [
false
'Show a floating expanded image on hover in the catalog.'
'Show full image / video on mouseover on hover in the catalog.'
]
'Gallery': [
true
'Adds a simple and cute image gallery.'
]
'Fullscreen Gallery': [
false
'Open gallery in fullscreen mode.'
1
]
'PDF in Gallery': [
false
'Show PDF files in gallery.'
1
]
'Sauce': [
true
@ -173,31 +200,31 @@ Config =
]
'Image Prefetching': [
false
'Preload images'
'Add link in header menu to turn on image preloading.'
]
'Fappe Tyme': [
false
'Hide posts without images when toggled. *hint* *hint*'
'Hide posts without images when header menu item is checked. *hint* *hint*'
]
'Werk Tyme': [
false
'Hide all post images when toggled.'
'Hide all post images when header menu item is checked.'
]
'Autoplay': [
true
'Videos begin playing immediately when opened.'
]
'Restart when Opened': [
true
'Restart GIFs and WebMs when you hover over or expand them.'
]
'Show Controls': [
true
'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': [
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':
@ -208,6 +235,7 @@ Config =
'Report Link': [
true
'Add a report link to the menu.'
1
]
'Post Hiding Link': [
true
@ -216,10 +244,17 @@ Config =
'Delete Link': [
true
'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': [
true
'Add an archive link to the menu.'
1
]
'Monitoring':
@ -231,9 +266,15 @@ Config =
true
'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)': [
false
'Hide the unread posts count in the tab title when it reaches 0.'
1
]
'Unread Favicon': [
true
@ -249,7 +290,11 @@ Config =
]
'Thread Excerpt': [
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': [
true
@ -258,10 +303,12 @@ Config =
'IP Count in Stats': [
true
'Display the unique IP count in the thread stats.'
1
]
'Page Count in Stats': [
true
'Display the page count in the thread stats.'
1
]
'Updater and Stats in Header': [
true,
@ -288,30 +335,46 @@ Config =
'Persistent QR': [
true
'The Quick reply won\'t disappear after posting.'
1
]
'Auto Hide QR': [
true
'Automatically hide the quick reply when posting.'
1
]
'Open Post in New Tab': [
true
'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': [
false
'Remember the spoiler state, instead of resetting after posting.'
]
'Remember QR Size': [
'Show Name and Subject': [
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': [
true
'Indicate the remaining time before posting again.'
1
]
'Posting Success Notifications': [
true
'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': [
true
@ -320,16 +383,24 @@ Config =
'Dump List Before Comment': [
false
'Position of the QR\'s Dump List.'
1
]
'Auto-load captcha': [
false
'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 Backlinks': [
true
'Add quote backlinks.'
1
]
'OP Backlinks': [
true
@ -342,10 +413,12 @@ Config =
'Quote Hash Navigation': [
false
'Include an extra link after quotes for autoscrolling to quoted posts.'
1
]
'Forward Hiding': [
true
'Hide original posts of inlined backlinks.'
1
]
'Quote Previewing': [
true
@ -354,14 +427,15 @@ Config =
'Quote Highlighting': [
true
'Highlight the previewed post.'
1
]
'Resurrect Quotes': [
true
'Link dead quotes to the archives.'
]
'Quoted Title': [
false
'Change the page title to reflect you\'ve been quoted.'
'Mark Quotes of You': [
true
'Add \'(You)\' to quotes linking to your posts.'
]
'Highlight Posts Quoting You': [
false
@ -371,14 +445,6 @@ Config =
false
'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': [
true
'Add \'(OP)\' to OP quotes.'
@ -388,23 +454,31 @@ Config =
'Add \'(Cross-thread)\' to cross-threads quotes.'
'Highlights own posts if Quote Markers are enabled.'
]
'Quote Threading': [
false
'Thread conversations'
]
imageExpansion:
'Fit width': [
false
true
''
]
'Fit height': [
false
''
]
'Expand spoilers': [
'Scroll into view': [
true
'Scroll down when expanding images to bring the full image into view.'
]
'Expand spoilers': [
false
'Expand all images along with spoilers.'
]
'Expand videos': [
false
'Expand all images also expands videos (no autoplay).'
'Expand all images also expands videos.'
]
'Expand from here': [
false
@ -428,6 +502,9 @@ Config =
'Scroll to Post': [
true
]
'Slide Delay': [
6.0
]
style:
@ -789,7 +866,7 @@ Config =
'Only show watched threads from the current board.'
]
'Auto Watch': [
true
false
'Automatically watch threads you start.'
]
'Auto Watch Reply': [
@ -798,7 +875,7 @@ Config =
]
'Auto Prune': [
false
'Automatically prune 404\'d threads.'
'Automatically remove dead threads.'
]
filter:
@ -824,8 +901,6 @@ Config =
#/Admin$/;highlight:moot;op:yes
"""
email: ""
subject: """
# Filter Generals on /v/:
#/general/i;boards:v;op:only
@ -877,6 +952,8 @@ Config =
'Threads per Page': 0
'Open threads in a new tab': false
'Show Replies': true
'Pin Watched Threads': false
'Anchor Hidden Threads': true
'Refreshed Navigation': false
Header:
@ -890,8 +967,22 @@ Config =
boardnav: """
[ toggle-all ]
[current-title]
[external-text:"FAQ","https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions"]
a-replace
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'
usercss: """
/* Tripcode Italics: */
/*
span.postertrip {
font-style: italic;
}
*/
/* Tripcode Italics: */
/*
span.postertrip {
font-style: italic;
}
*/
/* Add a rounded border to thumbnails (but not expanded images): */
/*
.fileThumb > img:first-child {
border: solid 2px rgba(0,0,100,0.5);
border-radius: 10px;
}
*/
/* Add a rounded border to thumbnails (but not expanded images): */
/*
.fileThumb > img:first-child {
border: solid 2px rgba(0,0,100,0.5);
border-radius: 10px;
}
*/
/* Make highlighted posts look inset on the page: */
/*
div.post:target,
div.post.highlight {
box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
}
*/
"""
/* Make highlighted posts look inset on the page: */
/*
div.post:target,
div.post.highlight {
box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
}
*/
"""
hotkeys:
# QR & Options
@ -977,7 +1068,7 @@ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
]
'Toggle sage': [
'Alt+s'
'Toggle sage in email field.'
'Toggle sage in options field.'
]
'Submit QR': [
'Ctrl+Enter'
@ -994,7 +1085,7 @@ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
]
'Update': [
'r'
'Update the thread now.'
'Update the thread / refresh the index.'
]
# Images
'Expand image': [
@ -1009,13 +1100,21 @@ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
'g'
'Opens the gallery.'
]
'Pause': [
'p'
'Pause/play videos in the gallery.'
]
'Slideshow': [
's'
'Toggle the gallery slideshow mode.'
]
'fappeTyme': [
'f'
'Fappe Tyme.'
'Toggle Fappe Tyme.'
]
'werkTyme': [
'Shift+w'
'Werk Tyme'
'Toggle Werk Tyme'
]
# Board Navigation
'Front page': [
@ -1034,6 +1133,10 @@ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
'Ctrl+Left'
'Jump to the previous page.'
]
'Open catalog': [
'Shift+c'
'Open the catalog of the current board.'
]
'Search form': [
'Ctrl+Alt+s'
'Focus the search field on the board index.'

View File

@ -1,9 +1,10 @@
Get =
threadExcerpt: (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.nameBlock
OP.info.nameBlock)
if excerpt.length > 70
excerpt = "#{excerpt[...67]}..."
"/#{thread.board}/ - #{excerpt}"
@ -36,7 +37,7 @@ Get =
# Get quotelinks & backlinks linking to the given post.
quotelinks = []
{posts} = g
fullID = {post}
{fullID} = post
handleQuotes = (qPost, type) ->
quotelinks.push qPost.nodes[type]...
quotelinks.push clone.nodes[type]... for clone in qPost.clones
@ -63,6 +64,11 @@ Get =
{boardID, postID} = Get.postDataFromLink quotelink
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) ->
if post = g.posts["#{boardID}.#{postID}"]
Get.insert post, root, context
@ -72,12 +78,9 @@ Get =
if threadID
$.cache "//a.4cdn.org/#{boardID}/thread/#{threadID}.json", ->
Get.fetchedPost @, boardID, threadID, postID, root, context
else if url = Redirect.to 'post', {boardID, postID}
$.cache url,
-> Get.archivedPost @, boardID, postID, root, context
,
responseType: 'json'
withCredentials: url.archive.withCredentials
else
Get.archivedPost boardID, postID, root, context
insert: (post, root, context) ->
# Stop here if the container has been removed while loading.
return unless root.parentNode
@ -91,6 +94,8 @@ Get =
$.rmAll root
$.add root, nodes.root
$.event 'PostsInserted'
fetchedPost: (req, boardID, threadID, postID, root, context) ->
# In case of multiple callbacks for the same request,
# don't parse the same original post more than once.
@ -101,19 +106,14 @@ Get =
{status} = req
unless status in [200, 304]
# The thread can die by the time we check a quote.
if url = Redirect.to 'post', {boardID, postID}
$.cache url,
-> Get.archivedPost @, boardID, postID, root, context
,
responseType: 'json'
withCredentials: url.archive.withCredentials
else
$.addClass root, 'warning'
root.textContent =
if status is 404
"Thread No.#{threadID} 404'd."
else
"Error #{req.statusText} (#{req.status})."
return if Get.archivedPost boardID, postID, root, context
$.addClass root, 'warning'
root.textContent =
if status is 404
"Thread No.#{threadID} 404'd."
else
"Error #{req.statusText} (#{req.status})."
return
{posts} = req.response
@ -122,15 +122,19 @@ Get =
break if post.no is postID # we found it!
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.
if url = Redirect.to 'post', {boardID, postID}
$.cache url,
-> Get.archivedPost @, boardID, postID, root, context
,
withCredentials: url.archive.withCredentials
else
$.addClass root, 'warning'
root.textContent = "Post No.#{postID} was not found."
return if Get.archivedPost boardID, postID, root, context
$.addClass root, 'warning'
root.textContent = "Post No.#{postID} was not found."
return
board = g.boards[boardID] or
@ -138,36 +142,60 @@ Get =
thread = g.threads["#{boardID}.#{threadID}"] or
new Thread threadID, board
post = new Post Build.postFromObject(post, boardID), thread, board
post.isFetchedQuote = true
Post.callbacks.execute [post]
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,
# don't parse the same original post more than once.
if post = g.posts["#{boardID}.#{postID}"]
Get.insert post, root, context
return
data = req.response
if data.error
$.addClass root, 'warning'
root.textContent = data.error
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/asagi/blob/master/src/main/java/net/easymodo/asagi/Yotsuba.java#L109-138
bq.innerHTML = bq.innerHTML.replace ///
\n
|
\[/?[a-z]+(:lit)?\]
///g, Get.parseMarkup
comment = bq.innerHTML
# greentext
.replace /(^|>)(&gt;[^<$]*)(<|$)/g, '$1<span class=quote>$2</span>$3'
# quotes
.replace /((&gt;){2}(&gt;\/[a-z\d]+\/)?\d+)/g, '<span class=deadlink>$1</span>'
comment = (data.comment or '').split /(\n|\[\/?(?:b|spoiler|code|moot|banned)\])/
comment = for text, i in comment
if i % 2 is 1
Get.archiveTags[text]
else
greentext = text[0] is '>'
text = text.replace /(\[\/?[a-z]+):lit(\])/, '$1$2'
text = for text2, j in text.split /(>>(?:>\/[a-z\d]+\/)?\d+)/g
if j % 2 is 1
<%= html('<span class="deadlink">${text2}</span>') %>
else
<%= html('${text2}') %>
text = <%= html('@{text}') %>
text = <%= html('<span class="quote">&{text}</span>') %> if greentext
text
comment = <%= html('@{comment}') %>
threadID = +data.thread_num
o =
@ -176,26 +204,27 @@ Get =
threadID: threadID
boardID: boardID
# info
name: data.name_processed
name: data.name
capcode: switch data.capcode
when 'M' then 'mod'
when 'A' then 'admin'
when 'D' then 'developer'
tripcode: data.trip
uniqueID: data.poster_hash
email: if data.email then encodeURI data.email else ''
subject: data.title_processed
email: data.email or ''
subject: data.title
flagCode: data.poster_country
flagName: data.poster_country_name_processed
flagName: data.poster_country_name
date: data.fourchan_date
dateUTC: data.timestamp
comment: comment
# file
if data.media?.media_filename
o.file =
name: data.media.media_filename_processed
name: data.media.media_filename
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
width: data.media.media_w
MD5: data.media.media_hash
@ -204,26 +233,26 @@ Get =
theight: data.media.preview_h
twidth: data.media.preview_w
isSpoiler: data.media.spoiler is '1'
o.file.tag = JSON.parse(data.media.exif).Tag if boardID is 'f'
board = g.boards[boardID] or
new Board boardID
thread = g.threads["#{boardID}.#{threadID}"] or
new Thread threadID, board
post = new Post Build.post(o, true), thread, board, {isArchived: true}
$('.page-num', post.nodes.info)?.hidden = true
post = new Post Build.post(o), thread, board, {isArchived: true}
post.file.thumbURL = o.file.turl if post.file
post.isFetchedQuote = true
Post.callbacks.execute [post]
Get.insert post, root, context
parseMarkup: (text) ->
{
'\n': '<br>'
'[b]': '<b>'
'[/b]': '</b>'
'[spoiler]': '<s>'
'[/spoiler]': '</s>'
'[code]': '<pre class=prettyprint>'
'[/code]': '</pre>'
'[moot]': '<div style="padding:5px;margin-left:.5em;border-color:#faa;border:2px dashed rgba(255,0,0,.1);border-radius:2px">'
'[/moot]': '</div>'
'[banned]': '<strong style="color: red;">'
'[/banned]': '</strong>'
}[text] or text.replace ':lit', ''
archiveTags:
'\n': <%= html('<br>') %>
'[b]': <%= html('<b>') %>
'[/b]': <%= html('</b>') %>
'[spoiler]': <%= html('<s>') %>
'[/spoiler]': <%= html('</s>') %>
'[code]': <%= html('<pre class="prettyprint">') %>
'[/code]': <%= html('</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]': <%= html('</div>') %>
'[banned]': <%= html('<strong style="color: red;">') %>
'[/banned]': <%= html('</strong>') %>

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