diff --git a/.gitignore b/.gitignore
index f86fa8e..9883fc9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,12 @@
# https://git-scm.com/docs/gitignore
# https://help.github.com/articles/ignoring-files
-# Example .gitignore files: https://github.com/github/gitignore
\ No newline at end of file
+# Example .gitignore files: https://github.com/github/gitignore
+/storage/
+/node_modules/
+/config.json
+/persist/
+/errors/
+/TODO
+/thumbnails/
+/logs/
+.sass-cache
diff --git a/app.coffee b/app.coffee
new file mode 100644
index 0000000..85cb323
--- /dev/null
+++ b/app.coffee
@@ -0,0 +1,263 @@
+express = require('express')
+uuid = require "uuid"
+fs = require "fs"
+domain = require "domain"
+
+app = express()
+
+reportError = (err, type = "error", sync = false) ->
+ errorPayload = {}
+
+ Object.getOwnPropertyNames(err).forEach (key) ->
+ errorPayload[key] = err[key]
+
+ filename = "errors/#{type}-#{Date.now()}-#{uuid.v4()}.json"
+
+ if sync
+ fs.writeFileSync filename, JSON.stringify(errorPayload)
+ else
+ fs.writeFile filename, JSON.stringify(errorPayload)
+
+# To make absolutely sure that we can log and exit on all uncaught errors.
+if app.get("env") == "production"
+ logAndExit = (err) ->
+ reportError(err, "error", true)
+ process.exit(1)
+
+ rootDomain = domain.create()
+ rootDomain.on "error", logAndExit
+
+ runWrapper = (func) ->
+ rootDomain.run ->
+ try
+ func()
+ catch err
+ logAndExit(err)
+else
+ # In development, we don't have to catch uncaught errors. Just run the specified function directly.
+ runWrapper = (func) -> func()
+
+runWrapper ->
+ path = require('path')
+
+ favicon = require('serve-favicon')
+ logger = require('morgan')
+ cookieParser = require('cookie-parser')
+ bodyParser = require('body-parser')
+ fancyshelf = require "fancyshelf"
+ session = require "express-session"
+ csurf = require "csurf"
+ fileStreamRotator = require "file-stream-rotator"
+ domainMiddleware = require("express-domain-middleware")
+
+ errors = require "errors"
+ PrettyError = require "pretty-error"
+ Convert = require "ansi-to-html"
+ marked = require "marked"
+ moment = require "moment"
+
+ persist = require "./lib/persist"
+ useCsrf = require "./lib/use-csrf"
+ templateUtil = require("./lib/template-util")
+ sessionStore = require("./lib/persist-session")(session)
+
+ config = require "./config.json"
+ knexfile = require("./knexfile").development
+
+ ansiHTML = new Convert(escapeXML: true, newline: true)
+
+ # Error handling
+ pe = PrettyError.start()
+ pe.skipPackage "bluebird", "coffee-script", "express", "express-promise-router", "jade"
+ pe.skipNodeFiles()
+
+ errors.create
+ name: "UploadError"
+
+ errors.create
+ name: "UploadTooLarge"
+ parents: errors.UploadError
+ status: 413
+
+ errors.create
+ name: "InvalidFiletype"
+ parents: errors.UploadError
+ status: 415
+
+ errors.create
+ name: "NotAuthenticated"
+ status: 403
+
+ errors.create
+ name: "InvalidInput"
+ status: 422
+
+ # Database setup
+ shelf = fancyshelf
+ engine: knexfile.client
+ host: knexfile.connection.host
+ username: knexfile.connection.user
+ password: knexfile.connection.password
+ database: knexfile.connection.database
+ debug: (app.get("env") == "development")
+
+ # Task runner
+ TaskRunner = require("./lib/task-runner")
+ runner = new TaskRunner(app: app, db: shelf, config: config, thumbnailPath: path.join(__dirname, 'thumbnails'))
+ runner.addTask "mirror", require("./tasks/mirror"), maxConcurrency: 5
+ runner.addTask "thumbnail", require("./tasks/thumbnail")
+ runner.run()
+
+ runner.on "taskQueued", (taskType, task) ->
+ persist.increment "task:#{taskType}:queued"
+
+ runner.on "taskStarted", (taskType, task) ->
+ persist.decrement "task:#{taskType}:queued"
+ persist.increment "task:#{taskType}:running"
+
+ runner.on "taskFailed", (taskType, task) ->
+ persist.decrement "task:#{taskType}:running"
+ persist.increment "task:#{taskType}:failed"
+
+ runner.on "taskCompleted", (taskType, task) ->
+ persist.decrement "task:#{taskType}:running"
+ persist.increment "task:#{taskType}:completed"
+
+ if app.get("env") == "development"
+ runner.on "taskFailed", (taskType, task, err) ->
+ console.log err.stack
+ else
+ runner.on "taskFailed", (taskType, task, err) ->
+ reportError err, "taskFailed"
+
+ # Configure Express
+ app.set('views', path.join(__dirname, 'views'))
+ app.set('view engine', 'jade')
+
+ # Middleware
+ if app.get("env") == "development"
+ app.use(logger('dev'))
+ else
+ accessLogStream = fileStreamRotator.getStream frequency: (config.accessLog.frequency ? "24h"), filename: config.accessLog.filename
+ app.use logger (config.accessLog.format ? "combined"), stream: accessLogStream
+
+ # Using /static/ paths to maintain backwards compatibility.
+ app.use("/static/thumbs", express.static(path.join(__dirname, 'thumbnails')))
+ app.use("/static/pdfjs", express.static(path.join(__dirname, 'public/pdfjs')))
+ app.use(express.static(path.join(__dirname, 'public')))
+ #app.use(favicon(__dirname + '/public/favicon.ico'))
+
+ app.use domainMiddleware
+ app.use templateUtil
+ app.use shelf.express
+
+ app.use session
+ store: new sessionStore
+ persist: persist
+ secret: config.session.signingKey
+ resave: true # TODO: Implement `touch` for the session store, and/or switch to a different store.
+ saveUninitialized: false
+
+ # Load models
+ require("./models")(shelf)
+
+ app.use(bodyParser.json())
+ app.use(bodyParser.urlencoded({ extended: false }))
+ app.use(cookieParser())
+
+ app.use (req, res, next) ->
+ res.locals.md = marked
+
+ req.appConfig = config
+ req.persist = persist
+
+ jadeExports = [
+ "announcementVisible"
+ "announcementText"
+ "announcementLinkText"
+ "announcementLink"
+ "maintenanceMode"
+ "maintenanceModeText"
+ "donationGoal"
+ "donationTotal"
+ "showNotice"
+ ]
+
+ for key in jadeExports
+ res.locals[key] = persist.getItem "var:#{key}"
+
+ res.locals.bitcoinAddress = config.donations.bitcoinAddress
+ res.locals.isAdmin = req.session.isAdmin
+
+ # This is for logging/reporting errors that should not occur, but that are known to not cause any adverse side-effects.
+ # This should NOT be used for errors that could leave the application in an undefined state!
+ req.reportError = reportError
+
+ req.taskRunner = runner
+
+ next()
+
+ app.use "/", require("./routes/index")
+ app.use "/donate", require("./routes/donate")
+ app.use "/d", require("./routes/document")
+ app.use "/upload", require("./routes/upload")
+ app.use "/gallery", require("./routes/gallery")
+ app.use "/blog", require("./routes/blog")
+ app.use "/admin", csurf(), useCsrf, require("./routes/admin")
+
+ # If no routes match, cause a 404
+ app.use (req, res, next) ->
+ next new errors.Http404Error("The requested page was not found.")
+
+ # Error handling middleware
+ app.use "/static/thumbs", (err, req, res, next) ->
+ # TODO: For some reason, Chrome doesn't always display images that are sent with a 404 status code. Let's stick with 200 for now...
+ #res.status 404
+ if err.status == '404'
+ fs.createReadStream path.join(__dirname, "public/images/no-thumbnail.png")
+ .pipe res
+ else
+ next(err)
+
+ app.use (err, req, res, next) ->
+ statusCode = err.status || 500
+ res.status(statusCode)
+
+ # Dump the error to disk if it's a 500, so that the error reporter can deal with it.
+ if app.get("env") != "development" and statusCode == 500
+ reportError(err)
+
+ # Display the error to the user - amount of details depending on whether the application is running in production mode or not.
+ if (app.get('env') == 'development')
+ stack = err
+ else
+ if statusCode == 500
+ errorMessage = "An unknown error occurred."
+ stack = {stack: "An administrator has been notified, and the error will be resolved as soon as possible. Apologies for the inconvenience."}
+ else
+ errorMessage = err.message
+ stack = {stack: err.explanation, subMessage: err.subMessage}
+
+ if req.headers["X-Requested-With"] == "XMLHttpRequest"
+ res.send {
+ message: errorMessage,
+ error: stack
+ }
+ else
+ if err instanceof errors.NotAuthenticated
+ res.redirect "/admin/login"
+ else
+ if app.get("env") == "development"
+ htmlStack = ansiHTML.toHtml(stack.stack)
+ .replace /#555/g, "#b5b5b5"
+
+ # TODO: It seems aborted responses will result in an attempt to send an error page/header - of course this can't succeed, as the headers have already been sent by that time, and an error is thrown.
+ res.render('error', {
+ message: errorMessage,
+ error: stack,
+ statusCode: statusCode,
+ htmlStack: htmlStack
+ })
+
+
+module.exports = app
diff --git a/bin/www.coffee b/bin/www.coffee
new file mode 100755
index 0000000..d5fe7bd
--- /dev/null
+++ b/bin/www.coffee
@@ -0,0 +1,56 @@
+#!/usr/bin/env coffee
+
+app = require('../app')
+debug = require('debug')('pdfy:server')
+http = require('http')
+
+normalizePort = (val) ->
+ port = parseInt val, 10
+
+ if isNaN port
+ return val
+
+ if port >= 0
+ return port
+
+ return false
+
+onError = (error) ->
+ if error.syscall != "listen"
+ throw error
+
+ bind = if typeof port == "string"
+ "Pipe #{port}"
+ else
+ "Port #{port}"
+
+ switch error.code
+ when "EACCES"
+ console.error "#{bind} requires elevated privileges"
+ process.exit 1
+ when "EADDRINUSE"
+ console.error "#{bind} is already in use"
+ process.exit 1
+ else
+ throw error
+
+
+onListening = ->
+ addr = server.address()
+
+ bind = if typeof port == "string"
+ "pipe #{port}"
+ else
+ "port #{port}"
+
+ debug("Listening on #{bind}")
+
+
+port = normalizePort(process.env.PORT || '3000')
+app.set('port', port)
+
+server = http.createServer(app)
+
+server.listen(port)
+server.on('error', onError)
+server.on('listening', onListening)
diff --git a/error-reporter.coffee b/error-reporter.coffee
new file mode 100644
index 0000000..12ddb4c
--- /dev/null
+++ b/error-reporter.coffee
@@ -0,0 +1,51 @@
+chokidar = require "chokidar"
+nodemailer = require "nodemailer"
+path = require "path"
+fs = require "fs"
+
+watcher = chokidar.watch "./errors", depth: 0, ignoreInitial: true
+mailer = nodemailer.createTransport()
+
+processFile = (filePath) ->
+ fs.readFile filePath, (err, data) ->
+ try
+ parsedData = JSON.parse(data)
+ catch error
+ console.log "Error report not complete yet, retrying #{filePath} in 1 second..."
+ setTimeout (->
+ processFile(filePath)
+ ), 1000
+ return
+
+ errorMessage = parsedData?.message ? "UNKNOWN ERROR"
+ textStack = parsedData?.stack?.replace(/\u001b(?:\[\??(?:\d\;*)*[A-HJKSTfminsulh])?/g, "") ? ""
+
+ message = """
+ A failure occurred. #{filePath} is attached.
+
+ #{textStack}
+ """
+
+ htmlMessage = """
+ A failure occurred. #{filePath} is attached.
+
+
#{textStack}
+ """.replace(/\n/g, "
")
+
+ mailer.sendMail
+ from: "ops@pdf.yt"
+ to: "admin@cryto.net"
+ subject: "Automatic failure report: #{errorMessage}"
+ text: message
+ html: htmlMessage
+ attachments: [
+ filename: path.basename(filePath)
+ path: filePath
+ contentType: "application/json"
+ ]
+
+watcher.on "add", (filePath) ->
+ console.log "PANIC! Sending report:", filePath
+ processFile(filePath)
+
+console.log "Running..."
diff --git a/frontend/index.coffee b/frontend/index.coffee
new file mode 100644
index 0000000..4b22cef
--- /dev/null
+++ b/frontend/index.coffee
@@ -0,0 +1,35 @@
+require "./lib/upload"
+require "./lib/embed"
+require "./lib/donate"
+require "./lib/form-popup"
+
+$ = require "jquery"
+autosize = require "autosize"
+marked = require "marked"
+scrollFloat = require "./lib/scroll-float"
+
+$ ->
+ $(".checkAll").on "change", (event) ->
+ newValue = $(this).prop("checked")
+
+ $(this)
+ .closest "table"
+ .find "input[type='checkbox']"
+ .filter ->
+ return !$(this).hasClass "checkAll"
+ .prop "checked", newValue
+
+ scrollFloat($(".floating"))
+
+ autosize($(".md-editor"))
+
+ updatePreview = ->
+ $(".md-preview").html(marked($(this).val()))
+
+ $(".md-editor")
+ .on "change input propertychange", updatePreview
+ .each updatePreview
+
+
+
+
diff --git a/frontend/lib/donate.coffee b/frontend/lib/donate.coffee
new file mode 100644
index 0000000..e5609ea
--- /dev/null
+++ b/frontend/lib/donate.coffee
@@ -0,0 +1,199 @@
+$ = require "jquery"
+require "Base64"
+
+$ ->
+ selectedMethod = undefined
+ selectedAmount = undefined
+ selectedHasPrice = undefined
+ pulsateRemove = undefined
+
+ paypalHandler = (block) ->
+ block.find "input.amount"
+ .val (Math.round(selectedAmount * 100) / 100)
+
+ paymentMethodHandlers =
+ paypal: paypalHandler
+ "paypal-weekly": paypalHandler
+ "paypal-monthly": paypalHandler
+ bitcoin: (block) ->
+ block.find(".loading-message").show()
+ block.find(".loaded-content").hide()
+
+ $.get "/donate/convert/btc?amount=#{selectedAmount}", (btcAmount) ->
+ $.get "/donate/bip21?amount=#{btcAmount}", (uri) ->
+ $(".bip21").attr("href", uri)
+
+ block.find(".loading-message").hide()
+ block.find(".loaded-content").show()
+
+ $(".bip21-qr").attr("src", "/donate/bip21/qr?amount=#{btcAmount}")
+ $(".btc-amount").text(Math.round(btcAmount * 100000000) / 100000000)
+
+ setCustomValue = (value) ->
+ value = parseFloat(value)
+
+ if isNaN value
+ # TODO: Validation error!
+ value = 0
+
+ selectedAmount = value
+
+ $ "#custom_amount_input"
+ .on "keyup", (event) ->
+ setCustomValue $(this).val()
+ showInstructions()
+ .on "change", (event) ->
+ setCustomValue $(this).val()
+ showInstructions()
+ .on "click", (event) ->
+ #$ this
+ # .closest ".option"
+ # .find "input[type='radio']"
+ # .click()
+
+ #event.stopPropagation()
+
+
+ $ "#amount_custom"
+ .on "click", (event) ->
+ setCustomValue $("#custom_amount_input").val()
+
+ $ ".donation-page section.types .option input[type='radio']"
+ .on "click", (event) ->
+ # Hide any instructions that were already visible.
+ # TODO: Automatically show instructions again when switching back...?
+ $ ".donation-page section.instructions div"
+ .hide()
+
+ $ ".donation-page section.instructions .placeholder"
+ .show()
+
+ type = $ this
+ .closest ".option"
+ .data "type"
+
+ $ ".donation-page section.methods .option"
+ .hide()
+
+ $ ".donation-page section.methods .option[data-type='#{type}']"
+ .show()
+
+ $ ".donation-page section.methods .option input[type='radio']"
+ .on "click", (event) ->
+ optionElement = $ this
+ .closest ".option"
+
+ selectedMethod = method = optionElement.data "name"
+ selectedHasPrice = setPrice = !!(optionElement.data "set-price")
+
+ if setPrice
+ $ ".donation-page section.amount"
+ .slideDown(400)
+
+ instructionSection = $ ".donation-page section.instructions"
+
+ instructionSection
+ .children "h3.set-amount"
+ .show()
+
+ instructionSection
+ .children "h3.no-set-amount"
+ .hide()
+ else
+ $ ".donation-page section.amount"
+ .slideUp(400)
+
+ instructionSection = $ ".donation-page section.instructions"
+
+ instructionSection
+ .children "h3.set-amount"
+ .hide()
+
+ instructionSection
+ .children "h3.no-set-amount"
+ .show()
+
+ showInstructions()
+
+ $ ".donation-page section.amount .option input[type='radio']"
+ .on "click", (event) ->
+ if not ($(this).attr("id") == "amount_custom")
+ selectedAmount = $(this).val()
+
+ showInstructions()
+
+ showInstructions = ->
+ $ ".donation-page section.instructions .method"
+ .hide()
+
+ if selectedMethod? and (selectedAmount? or !selectedHasPrice)
+ $ ".donation-page section.instructions .placeholder"
+ .hide()
+
+ $ ".donation-page section.instructions .method-#{selectedMethod}"
+ .show()
+
+ #$ "html, body"
+ # .animate scrollTop: "#{$('.donation-page section.instructions').offset().top}px", 500
+
+ $ ".donation-page section.instructions"
+ .addClass "pulsate"
+
+ if pulsateRemove?
+ clearTimeout pulsateRemove
+
+ pulsateRemove = setTimeout (->
+ $ ".donation-page section.instructions"
+ .removeClass "pulsate"
+ ), 3000
+
+ if selectedMethod of paymentMethodHandlers
+ paymentMethodHandlers[selectedMethod]($(".donation-page section.instructions .method-#{selectedMethod}"))
+ else
+ $ ".donation-page section.instructions .placeholder"
+ .show()
+
+
+ $ ".donation-page .option input[type='radio']"
+ .on "click", (event) ->
+ $ this
+ .closest "section"
+ .find ".option"
+ .removeClass "selected"
+
+ $ this
+ .closest ".option"
+ .addClass "selected"
+
+ $ this
+ .closest ".option"
+ .find "input[type='number']"
+ .focus()
+
+ event.stopPropagation()
+
+ $ ".donation-page .option"
+ .on "click", (event) ->
+ $ this
+ .find "input[type='radio']"
+ .click()
+
+ $ ".donation-page section.types .option[data-type='once']"
+ .click()
+
+ $ ".donation-page #amount_10"
+ .click()
+
+ $ ".donation-page section.instructions .method, .donation-page section.instructions h3.no-set-amount"
+ .hide()
+
+ $ ".js-unavailable"
+ .hide()
+
+ $ ".js-available"
+ .show()
+
+ $ ".decodable"
+ .each ->
+ $(this).html atob($(this).html())
+
diff --git a/frontend/lib/embed.coffee b/frontend/lib/embed.coffee
new file mode 100644
index 0000000..b0d840e
--- /dev/null
+++ b/frontend/lib/embed.coffee
@@ -0,0 +1,33 @@
+$ = require "jquery"
+
+castBoolean = (value) ->
+ if value == true
+ return 1
+ else
+ return 0
+
+updateEmbedCode = ->
+ showToolbar = $("#show_toolbar").prop("checked")
+ showDonationLink = $("#show_donation").prop("checked")
+
+ embedCode = embed_template
+ .replace "{SPARSE}", castBoolean(not showToolbar)
+ .replace "{DONATION}", castBoolean(showDonationLink)
+
+ $(".embed_code").val embedCode
+
+$ ->
+ $ ".autoselect"
+ .on "click", (event) ->
+ $ this
+ .focus()
+ .select()
+
+ $ "#show_toolbar, #show_donation"
+ .on "change", (event) ->
+ updateEmbedCode()
+
+ # Linkify has a tendency of breaking our embed codes, so we re-set the embed code here to make sure that that doesn't happen.
+ if embed_template?
+ updateEmbedCode()
+ # do things and stuff
diff --git a/frontend/lib/form-popup.coffee b/frontend/lib/form-popup.coffee
new file mode 100644
index 0000000..029f1ad
--- /dev/null
+++ b/frontend/lib/form-popup.coffee
@@ -0,0 +1,16 @@
+$ = require "jquery"
+
+$ ->
+ $(".popup").hide()
+
+ $(".form-popup").each ->
+ elem = $(this)
+ target = ".popup-#{elem.data('popup')}"
+
+ elem.on "click", ->
+ $(target).show()
+
+ $(".popup .close").on "click", ->
+ $(this)
+ .closest ".popup"
+ .hide()
diff --git a/frontend/lib/scroll-float.coffee b/frontend/lib/scroll-float.coffee
new file mode 100644
index 0000000..ce34ee2
--- /dev/null
+++ b/frontend/lib/scroll-float.coffee
@@ -0,0 +1,49 @@
+$ = require "jquery"
+
+module.exports = (jqueryObj) ->
+ jqueryObj.each ->
+ elem = $(this)
+ minTop = threshold = basePos = undefined
+ currentlyFloating = false
+
+ originalPositioning = elem.css "position"
+ originalLeft = elem.css "left"
+ originalTop = elem.css "top"
+ originalLeft = elem.css "right"
+
+ updateMetrics = ->
+ needRestore = currentlyFloating
+
+ if needRestore
+ makeNotFloating()
+
+ basePos = elem.offset()
+ minTop = elem.data("min-top") ? 0
+ threshold = basePos.top - minTop
+
+ if needRestore
+ makeFloating()
+
+ makeFloating = ->
+ currentlyFloating = true
+ elem.css
+ position: "fixed"
+ top: "#{minTop}px"
+ right: "auto"
+ left: "#{basePos.left}px"
+
+ makeNotFloating = ->
+ currentlyFloating = false
+ elem.attr "style", ""
+ #elem.css
+ # position: originalPositioning
+ # top: originalTop
+
+ updateMetrics()
+ $(window).on "resize", updateMetrics
+
+ $(document).on "scroll", (event) ->
+ if $(document).scrollTop() >= threshold
+ makeFloating()
+ else
+ makeNotFloating()
diff --git a/frontend/lib/upload.coffee b/frontend/lib/upload.coffee
new file mode 100644
index 0000000..3726806
--- /dev/null
+++ b/frontend/lib/upload.coffee
@@ -0,0 +1,176 @@
+$ = require "jquery"
+prettyUnits = require "pretty-units"
+
+# The AMD loader for this package doesn't work for some reason - so we explicitly disable it. This will force it to fall back to the CommonJS API.
+require "imports?define=>false!blueimp-file-upload"
+
+data_object = null
+
+uploadDone = (response) ->
+ switch response.status
+ when 415
+ errorHeader = "Oops! That's not a PDF file."
+ errorMessage = "The file you tried to upload is not a valid PDF file. Currently, only PDF files are accepted."
+ when 413
+ errorHeader = "Oops! That file is too big."
+ errorMessage = "The file you tried to upload is too big. Currently, you can only upload PDF files up to 150MB in size."
+ when 200 # Nothing, success!
+ else
+ errorHeader = "Oops! Something went wrong."
+ errorMessage = "An unknown error occurred. Please reload the page and try again. If the error keeps occurring, send us an e-mail!"
+
+ if errorMessage?
+ triggerError errorHeader, errorMessage
+ reinitializeUploader()
+ else
+ if response.responseJSON.redirect?
+ window.location = response.responseJSON.redirect
+ else
+ # TODO: Wat do?
+
+triggerError = (header, message) ->
+ $(".upload-form .privacySettings, .upload-form .progress, .upload-form .button-submit").hide()
+ $(".upload").removeClass("faded")
+
+ errorBox = $("#uploadError")
+ .show()
+
+ errorBox.find "h3"
+ .html header
+
+ errorBox.find ".message"
+ .html message
+
+ data_object = null
+
+reinitializeUploader = ->
+ $("#upload_element").replaceWith($("#upload_element").clone(true))
+
+filePicked = (data) ->
+ $(".upload-form .privacySettings, .upload-form .button-submit").show()
+ $("#uploadError").hide()
+ $(".upload-form .fileinfo").removeClass("faded")
+
+ fileinfo = $(".fileinfo")
+ filesize = data.files[0].size
+
+ # TODO: Use filesize limit from configuration file!
+ if filesize > (150 * 1024 * 1024)
+ reinitializeUploader()
+ triggerError("Oops! That file is too big.", "The file you tried to upload is too big. Currently, you can only upload PDF files up to 150MB in size.")
+ return
+
+ filesize_text = prettyUnits(filesize) + "B"
+
+ fileinfo.find ".filename"
+ .text data.files[0].name
+
+ fileinfo.find ".filesize"
+ .text filesize_text
+
+ $ ".info"
+ .hide()
+
+ fileinfo
+ .show()
+
+ $ ".upload"
+ .addClass "faded"
+
+updateUploadProgress = (event) ->
+ if event.lengthComputable
+ percentage = event.loaded / event.total * 100
+
+ done_text = prettyUnits(event.loaded) + "B"
+ total_text = prettyUnits(event.total) + "B"
+
+ progress = $ ".progress"
+
+ progress.find ".done"
+ .text done_text
+
+ progress.find ".total"
+ .text total_text
+
+ progress.find ".percentage"
+ .text (Math.ceil(percentage * 100) / 100)
+
+ progress.find ".bar-inner"
+ .css width: "#{percentage}%"
+
+ if event.loaded >= event.total
+ # Completed!
+ progress.find ".numbers"
+ .hide()
+
+ progress.find ".wait"
+ .show()
+
+$ ->
+ if $().fileupload?
+ # Only run this if the fileupload plugin is loaded; we don't need all this on eg. the 'view' page.
+
+ $ "#upload_form"
+ .fileupload
+ fileInput: null
+ type: "POST"
+ url: "/upload"
+ paramName: "file"
+ autoUpload: false
+ maxNumberOfFiles: 1
+ formData: (form) ->
+ form = $ "#upload_form"
+ form.serializeArray()
+ progressall: (e, data) ->
+ updateUploadProgress
+ lengthComputable: true
+ loaded: data.loaded
+ total: data.total
+ add: (e, data) ->
+ data_object = data
+ filePicked(data)
+ always: (e, data) ->
+ uploadDone(data.jqXHR)
+
+ $ "#upload_activator"
+ .on "click", (event) ->
+ $("#upload_element").click()
+
+ $ "#upload_element"
+ .on "change", (event) ->
+ filePicked(this)
+
+ $ "#upload_form"
+ .on "submit", (event) ->
+ event.stopPropagation()
+ event.preventDefault()
+
+ $ ".fileinfo"
+ .addClass "faded"
+
+ $ ".progress"
+ .show()
+
+ if data_object == null
+ # Only do this if the drag-and-drop dropzone hasn't been used.
+ formData = new FormData(this)
+
+ $.ajax
+ method: "POST"
+ url: "/upload"
+ data: formData
+ cache: false
+ contentType: false
+ processData: false
+ xhr: ->
+ customHandler = $.ajaxSettings.xhr()
+
+ if customHandler.upload?
+ customHandler.upload.addEventListener "progress", updateUploadProgress, false
+
+ return customHandler
+ complete: (result) ->
+ uploadDone(result)
+ else
+ # If the dropzone was used...
+ data_object.submit()
diff --git a/gen-hash.coffee b/gen-hash.coffee
new file mode 100755
index 0000000..3c2a0dd
--- /dev/null
+++ b/gen-hash.coffee
@@ -0,0 +1,17 @@
+#!/usr/bin/env coffee
+
+scrypt = require "scrypt-for-humans"
+Promise = require "bluebird"
+read = Promise.promisify(require "read")
+
+Promise.try ->
+ read(prompt: "Enter a password:", silent: true)
+.spread (password, isDefault) ->
+ if password.trim().length == 0
+ console.log "You didn't enter a password!"
+ process.exit(1)
+
+ scrypt.hash(password)
+.then (hash) ->
+ console.log "Hash:", hash
+ console.log "Set this hash in your config.json to use it."
diff --git a/graphics/ia-icon.svg b/graphics/ia-icon.svg
new file mode 100644
index 0000000..7add2ca
--- /dev/null
+++ b/graphics/ia-icon.svg
@@ -0,0 +1,87 @@
+
+
\ No newline at end of file
diff --git a/graphics/no-thumbnail.svg b/graphics/no-thumbnail.svg
new file mode 100644
index 0000000..03fe96d
--- /dev/null
+++ b/graphics/no-thumbnail.svg
@@ -0,0 +1,95 @@
+
+
+
+
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000..d726968
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,85 @@
+var gulp = require('gulp');
+
+/* CoffeeScript compile deps */
+var path = require('path');
+var gutil = require('gulp-util');
+var concat = require('gulp-concat');
+var rename = require('gulp-rename');
+var coffee = require('gulp-coffee');
+var cache = require('gulp-cached');
+var remember = require('gulp-remember');
+var plumber = require('gulp-plumber');
+var livereload = require('gulp-livereload');
+var nodemon = require("gulp-nodemon");
+var net = require("net");
+var webpack = require("gulp-webpack");
+var sass = require("gulp-sass");
+
+task = {
+ "source": ["public/**/*.coffee", "routes/**/*.coffee", "models/**/*.coffee", "tasks/**/*.coffee", "app.coffee", "util.coffee", "migrate.coffee", "db.coffee"]
+}
+
+/*
+gulp.task('coffee', function() {
+ return gulp.src(task.source, {base: "."})
+ .pipe(plumber())
+ .pipe(cache("coffee"))
+ .pipe(coffee({bare: true}).on('error', gutil.log)).on('data', gutil.log)
+ .pipe(remember("coffee"))
+ .pipe(gulp.dest("."));
+});*/
+
+gulp.task('webpack', function(){
+ return gulp.src("frontend/index.coffee")
+ .pipe(webpack({
+ watch: true,
+ module: {
+ loaders: [{ test: /\.coffee$/, loader: "coffee-loader" }]
+ },
+ resolve: { extensions: ["", ".web.coffee", ".web.js", ".coffee", ".js"] }
+ }))
+ .pipe(rename("bundle.js"))
+ .pipe(gulp.dest("public/js/"));
+});
+
+gulp.task('sass', function(){
+ // TODO: Put the source SCSS file in a more logical place...
+ return gulp.src("./public/css/*.scss")
+ .pipe(sass())
+ .pipe(gulp.dest("./public/css"));
+});
+
+function checkServerUp(){
+ setTimeout(function(){
+ var sock = new net.Socket();
+ sock.setTimeout(50);
+ sock.on("connect", function(){
+ console.log("Trigger page reload...");
+ livereload.changed();
+ sock.destroy();
+ })
+ .on("timeout", checkServerUp)
+ .on("error", checkServerUp)
+ .connect(3000);
+ }, 70);
+}
+
+gulp.task('watch', function () {
+ livereload.listen();
+ gulp.watch(['./**/*.css', 'views/**/*.jade', 'package.json', "./public/js/**/*.js"]).on('change', livereload.changed);
+ gulp.watch(["./public/css/style.scss"], ["sass"])
+ //gulp.watch(task.source, ['coffee']);
+ // theseus disabled for now, it was screwing with my tracebacks
+ //nodemon({script: "./bin/www", ext: "js", nodeArgs: ['/usr/bin/node-theseus']}).on("start", checkServerUp);
+ nodemon({
+ script: "./bin/www.coffee",
+ ext: "coffee",
+ delay: 500,
+ ignore: ["./frontend/"],
+ watch: ["app.coffee", "bin", "lib", "models", "routes", "tasks"]
+ }).on("start", checkServerUp).on("restart", function(file){
+ console.log("Restarted triggered by:", file);
+ });
+});
+
+gulp.task('default', [/*'coffee',*/ 'sass', 'watch', 'webpack']);
diff --git a/knexfile.coffee b/knexfile.coffee
new file mode 100644
index 0000000..03ceef2
--- /dev/null
+++ b/knexfile.coffee
@@ -0,0 +1,17 @@
+# Update with your config settings.
+
+config = require "./config.json"
+
+module.exports =
+ # TODO: Do we need an environment name here?
+ development:
+ client: "mysql2"
+ connection:
+ database: config.database.database
+ user: config.database.username
+ password: config.database.password
+ pool:
+ min: 2
+ max: 10
+ migrations:
+ tableName: "knex_migrations"
diff --git a/lib/disable-in-maintenance-mode.coffee b/lib/disable-in-maintenance-mode.coffee
new file mode 100644
index 0000000..449ac4c
--- /dev/null
+++ b/lib/disable-in-maintenance-mode.coffee
@@ -0,0 +1,5 @@
+module.exports = (req, res, next) ->
+ if (not res.locals.maintenanceMode) or req.session.isAdmin
+ next()
+ else
+ res.status(503).send(res.locals.maintenanceModeText)
diff --git a/lib/get-rates.coffee b/lib/get-rates.coffee
new file mode 100644
index 0000000..47176ae
--- /dev/null
+++ b/lib/get-rates.coffee
@@ -0,0 +1,25 @@
+Promise = require "bluebird"
+bhttp = require "bhttp"
+
+lastRates = null
+lastRateCheck = 0
+
+module.exports = ->
+ Promise.try ->
+ if Date.now() > lastRateCheck + (5 * 60 * 1000)
+ # We need fresh API data, 5 minutes have elapsed.
+ Promise.try ->
+ Promise.all [
+ bhttp.get "http://api.fixer.io/latest", decodeJSON: true
+ bhttp.get "https://blockchain.info/ticker", decodeJSON: true
+ ]
+ .spread (fixerRates, blockchainRates) ->
+ eurRates = fixerRates.body.rates
+ eurRates.BTC = 1 / blockchainRates.body.EUR["15m"]
+ Promise.resolve eurRates
+ .then (rates) ->
+ lastRates = rates
+ lastRateCheck = Date.now()
+ Promise.resolve rates
+ else
+ Promise.resolve lastRates
diff --git a/lib/middleware-auth.coffee b/lib/middleware-auth.coffee
new file mode 100644
index 0000000..072bf50
--- /dev/null
+++ b/lib/middleware-auth.coffee
@@ -0,0 +1,7 @@
+errors = require "errors"
+
+module.exports = (req, res, next) ->
+ if req.session?.isAdmin?
+ next()
+ else
+ next(new errors.NotAuthenticated("You are not logged in as an administrator."))
diff --git a/lib/parse-amount.coffee b/lib/parse-amount.coffee
new file mode 100644
index 0000000..a402c1c
--- /dev/null
+++ b/lib/parse-amount.coffee
@@ -0,0 +1,11 @@
+errors = require "errors"
+
+amountRegex = /^[0-9]+(?:\.[0-9]+)?$/
+
+module.exports = (amount) ->
+ parsedAmount = parseFloat(amount)
+
+ if amountRegex.exec(amount) == null or isNaN(parsedAmount)
+ throw new errors.InvalidInput("The specified amount is invalid.")
+
+ return parsedAmount
diff --git a/lib/parse-boolean.coffee b/lib/parse-boolean.coffee
new file mode 100644
index 0000000..158a3c0
--- /dev/null
+++ b/lib/parse-boolean.coffee
@@ -0,0 +1,5 @@
+module.exports = (param) ->
+ if not param?
+ return undefined
+ else
+ return !!(parseInt(param))
\ No newline at end of file
diff --git a/lib/persist-brute.coffee b/lib/persist-brute.coffee
new file mode 100644
index 0000000..ecaadea
--- /dev/null
+++ b/lib/persist-brute.coffee
@@ -0,0 +1,71 @@
+# NOTE: This module does not currently ensure correct writes. Callbacks are called immediately (but asynchronously).
+
+AbstractClientStore = require "express-brute/lib/AbstractClientStore"
+
+module.exports = class PersistBruteStore extends AbstractClientStore
+ constructor: (options) ->
+ @persist = options.persist
+ @prefix = options.prefix ? "brute"
+
+ @_timers = {}
+ @_keyMatcher = new RegExp("^#{@prefix}:")
+
+ @persist.keys()
+ .filter (key) => key.match(@_keyMatcher)
+ .forEach (key) =>
+ @_createExpiryTimer key, @persist.getItem(key).expiry
+
+ _createExpiryTimer: (key, expiry) ->
+ @_removeExpiryTimer(key)
+
+ ttl = expiry - Date.now()
+
+ if ttl < 0
+ @_createRemover(key)()
+ else
+ setTimeout @_createRemover(key), ttl
+
+ _removeExpiryTimer: (key) ->
+ if @_timers[key]?
+ clearTimeout @_timers[key]
+ delete @_timers[key]
+
+ _createRemover: (key) ->
+ return =>
+ @_removeExpiryTimer(key)
+
+ prefixedKey = "#{@prefix}:#{key}"
+
+ # TODO: Error handling?
+ @persist.removeItem prefixedKey
+
+ set: (key, value, lifetime, callback) ->
+ prefixedKey = "#{@prefix}:#{key}"
+ expiry = (Date.now() + lifetime)
+
+ @_createExpiryTimer key, expiry
+ @persist.setItem prefixedKey, {value: value, expiry: expiry}, callback
+
+ process.nextTick ->
+ callback(null)
+
+ get: (key, callback) ->
+ prefixedKey = "#{@prefix}:#{key}"
+ result = @persist.getItem(prefixedKey)
+ value = result?.value
+
+ # Normalize to dates if we're reading from disk-persisted data... on disk, they're saved as strings.
+ if value?.firstRequest? and value?.firstRequest not instanceof Date
+ value.firstRequest = new Date(value.firstRequest)
+
+ if value?.lastRequest? and value?.lastRequest not instanceof Date
+ value.lastRequest = new Date(value.lastRequest)
+
+ process.nextTick ->
+ callback(null, value)
+
+ reset: (key, callback) ->
+ # I don't really understand why this is called 'reset'. It's quite clearly a 'remove' function...
+ @_createRemover(key)()
+ process.nextTick ->
+ callback(null)
diff --git a/lib/persist-session.coffee b/lib/persist-session.coffee
new file mode 100644
index 0000000..6664833
--- /dev/null
+++ b/lib/persist-session.coffee
@@ -0,0 +1,40 @@
+# This will break horribly in a multi-process setup! Don't do that!
+# NOTE: Does not currently ensure writes.
+
+module.exports = (session) ->
+ class PersistSessionStore extends session.Store
+ constructor: (options) ->
+ @persist = options.persist
+
+ get: (sid, callback) ->
+ sessionData = @persist.getItem "session:#{sid}"
+ process.nextTick ->
+ callback(null, sessionData)
+
+ set: (sid, sessionData, callback) ->
+ sessionData.__lastAccess = Date.now()
+ @persist.setItem "session:#{sid}", sessionData
+ process.nextTick ->
+ callback(null)
+
+ destroy: (sid, session, callback) ->
+ @persist.removeItem "session:#{sid}"
+ process.nextTick ->
+ callback(null)
+
+ length: (callback) ->
+ length = @persist.valuesWithKeyMatch(/^session:/).length
+ process.nextTick ->
+ callback(null, length)
+
+ clear: (callback) ->
+ @persist.keys()
+ .filter (key) -> key.match(/^session:/)
+ .forEach (key) ->
+ @persist.removeItem key
+
+ process.nextTick ->
+ callback(null)
+
+ # TODO: .touch
+
diff --git a/lib/persist.coffee b/lib/persist.coffee
new file mode 100644
index 0000000..fc5a357
--- /dev/null
+++ b/lib/persist.coffee
@@ -0,0 +1,74 @@
+Promise = require "bluebird"
+persist = require "node-persist"
+path = require "path"
+xtend = require "xtend"
+
+# We MUST explicitly specify the `persist` directory, otherwise node-persist will bug out and write to its own module directory...
+persist.initSync(continuous: false, dir: path.join(__dirname, "../persist"))
+
+persist.increment = (key, amount = 1) ->
+ persist.setItem key, (persist.getItem(key) + amount)
+
+persist.decrement = (key, amount = 1) ->
+ persist.setItem key, (persist.getItem(key) - amount)
+
+persist.addListItem = (key, item) ->
+ newList = [item].concat (persist.getItem(key) ? [])
+
+ persist.setItem key, newList
+
+persist.removeListItem = (key, item) ->
+ newList = (persist.getItem(key) ? [])
+ .filter (existingItem) ->
+ return (item == existingItem)
+
+ persist.setItem key, newList
+
+persist.removeListItemByFilter = (key, filter) ->
+ newList = (persist.getItem(key) ? [])
+ .filter (item) ->
+ return !filter(item)
+
+ persist.setItem key, newList
+
+persist.setProperty = (key, propertyKey, value) ->
+ newObj = {}
+ newObj[propertyKey] = value
+ oldObj = persist.get(key)
+
+ persist.setItem key, xtend(oldObj, newObj)
+
+persist.removeProperty = (key, propertyKey) ->
+ # Extremely ghetto shallow clone
+ obj = xtend({}, persist.get(key))
+ delete obj[propertyKey]
+
+ persist.setItem key, obj
+
+# Rough shim for write queueing...
+writeQueue = []
+currentlyWriting = false
+_setItem = persist.setItem
+
+persist.setItem = (key, value) ->
+ new Promise (resolve, reject) ->
+ _setItem.call(persist, key, value)
+ addItemToQueue key, value, resolve, reject
+ triggerWrite()
+
+addItemToQueue = (key, value, resolveFunc, rejectFunc) ->
+ writeQueue.push [key, value, resolveFunc, rejectFunc]
+
+triggerWrite = ->
+ if not currentlyWriting and writeQueue.length > 0
+ currentlyWriting = 1
+ [key, value, resolveFunc, rejectFunc] = writeQueue.shift()
+
+ Promise.resolve(persist.persistKey(key))
+ .then (result) -> resolveFunc(result)
+ .catch (err) -> rejectFunc(err)
+ .finally ->
+ currentlyWriting = false
+ triggerWrite()
+
+module.exports = persist
diff --git a/lib/random-string.coffee b/lib/random-string.coffee
new file mode 100644
index 0000000..7f86f13
--- /dev/null
+++ b/lib/random-string.coffee
@@ -0,0 +1,15 @@
+Promise = require "bluebird"
+crypto = Promise.promisifyAll(require "crypto")
+
+module.exports = (length = 16) ->
+ Promise.try ->
+ byteLength = Math.ceil(length / 4) * 3
+ return crypto.randomBytesAsync(byteLength)
+ .then (bytes) ->
+ bytes = bytes
+ .toString "base64"
+ .replace /\+/g, "-"
+ .replace /\//g, "_"
+ .slice 0, length
+
+ Promise.resolve bytes
diff --git a/lib/rate-limiter.coffee b/lib/rate-limiter.coffee
new file mode 100644
index 0000000..eb06ecb
--- /dev/null
+++ b/lib/rate-limiter.coffee
@@ -0,0 +1,34 @@
+class RateLimiter
+ constructor: (@limit, @interval, @funcA, @funcB) ->
+ @_totalCalls = 0
+ @_startTimer()
+ _startTimer: ->
+ @_timer = setInterval @_clearCalls, @interval
+ _stopTimer: ->
+ if @_timer?
+ clearInterval @_timer
+ @_timer = null
+ _clearCalls: ->
+ @_totalCalls = 0
+ call: ->
+ @_totalCalls += 1
+
+ targetFunc = switch
+ when @_totalCalls <= @limit then @funcA
+ else @funcB
+
+ targetFunc.apply this, arguments
+ setInterval: (interval) ->
+ @_stopTimer()
+ @interval = interval
+ @_startTimer()
+ setLimit: (limit) ->
+ @limit = limit
+
+module.exports = ->
+ return (funcA, funcB, options) ->
+ if not options.limit?
+ throw new Error("No limit specified.")
+ options.interval ?= 60 # Default: 60 seconds ie. 1 minute.
+
+ return new RateLimiter(options.limit, options.interval, funcA, funcB)
diff --git a/lib/stream-contains.coffee b/lib/stream-contains.coffee
new file mode 100644
index 0000000..435d7ef
--- /dev/null
+++ b/lib/stream-contains.coffee
@@ -0,0 +1,12 @@
+Promise = require "bluebird"
+concatStream = require "concat-stream"
+buffertools = require "buffertools"
+
+module.exports = (stream, needle) ->
+ # CAUTION: This buffers up in memory!
+ new Promise (resolve, reject) ->
+ stream
+ .pipe concatStream (result) ->
+ resolve buffertools.indexOf(result, needle) != -1
+ .on "error", (err) ->
+ reject err
diff --git a/lib/tap-error.coffee b/lib/tap-error.coffee
new file mode 100644
index 0000000..35cd54b
--- /dev/null
+++ b/lib/tap-error.coffee
@@ -0,0 +1,13 @@
+# NOTE: This is purely a `tap` equivalent for errors! Any resolves or rejections are ignored - promises can only be used to wait for async execution of something.
+
+Promise = require "bluebird"
+
+module.exports = (func) ->
+ return (err) ->
+ Promise.try ->
+ func(err)
+ .catch ->
+ # Consume any errors
+ Promise.resolve()
+ .then ->
+ Promise.reject(err)
diff --git a/lib/task-runner.coffee b/lib/task-runner.coffee
new file mode 100644
index 0000000..8fefcdd
--- /dev/null
+++ b/lib/task-runner.coffee
@@ -0,0 +1,95 @@
+EventEmitter = require("events").EventEmitter
+Promise = require "bluebird"
+debug = require("debug")("task-runner")
+
+makeExternalPromise = ->
+ extResolve = extReject = null
+ new Promise (resolve, reject) ->
+ extResolve = resolve
+ extReject = reject
+
+module.exports = class TaskRunner extends EventEmitter
+ constructor: (@context = {}) ->
+ @_taskTypes = {}
+ @_queue = {}
+ @_runningCount = {}
+ @running = false
+ @context.taskRunner = this
+
+ _checkRunTask: ->
+ if not @running
+ return
+
+ debug "checking for runnable tasks..."
+
+ for taskType, options of @_taskTypes
+ if @_runningCount[taskType] < (options.maxConcurrent ? Infinity)
+ if options.maxConcurrent?
+ tasksToRun = options.maxConcurrent - @_runningCount[taskType]
+ else
+ tasksToRun = Infinity
+
+ debug "running #{tasksToRun} tasks"
+
+ for i in [0...tasksToRun]
+ if @_queue[taskType].length == 0
+ debug "ran out of tasks"
+ @emit "tasksDepleted"
+ break
+
+ @_doRunTask taskType, @_queue[taskType].shift()
+
+ debug "started #{tasksToRun} tasks, waiting for completion..."
+
+ _doRunTask: (taskType, taskData) ->
+ taskOptions = @_taskTypes[taskType]
+ @_runningCount[taskType] += 1
+ @emit "taskStarted", taskType, taskData.task
+
+ Promise.resolve(taskOptions.taskFunc(taskData.task, @context))
+ .then (value) =>
+ @_markTaskCompleted taskType, taskData
+ taskData.resolveFunc(value)
+ .catch (err) =>
+ @emit "taskFailed", taskType, taskData.task, err
+ taskData.rejectFunc(err)
+
+ debug "started task"
+
+ _markTaskCompleted: (taskType, task) ->
+ @_runningCount[taskType] -= 1
+ @emit "taskCompleted", taskType, task.task
+ debug "completed task"
+ @_checkRunTask()
+
+ addTask: (taskType, taskFunc, options = {}) =>
+ options.taskFunc = taskFunc
+ @_taskTypes[taskType] = options
+ @_queue[taskType] = []
+ @_runningCount[taskType] = 0
+ debug "added task"
+
+ setTaskOptions: (taskType, options) =>
+ options.taskFunc = @_taskTypes[taskType].taskFunc
+ @_taskTypes[taskType] = options
+
+ do: (taskType, task) =>
+ @emit "taskQueued", taskType, task
+ debug "queued task"
+
+ new Promise (resolve, reject) =>
+ @_queue[taskType].push
+ resolveFunc: resolve
+ rejectFunc: reject
+ task: task
+
+ @_checkRunTask()
+
+ run: =>
+ @running = true
+ debug "started task loop"
+ @_checkRunTask()
+
+ pause: =>
+ @running = false
+ debug "paused task loop"
diff --git a/lib/template-util.coffee b/lib/template-util.coffee
new file mode 100644
index 0000000..b821198
--- /dev/null
+++ b/lib/template-util.coffee
@@ -0,0 +1,15 @@
+moment = require "moment"
+
+module.exports = (req, res, next) ->
+ res.locals.conditionalClasses = (always, conditionals) ->
+ applicableConditionals = (className for className, condition of conditionals when condition)
+ applicableClasses = always.concat applicableConditionals
+ return applicableClasses.join " "
+
+ res.locals.makeBreakable = (string) ->
+ require("jade/lib/runtime").escape(string).replace(/_/g, "_")
+
+ res.locals.shortDate = (date) ->
+ moment(date).format "MMM Do, YYYY hh:mm:ss"
+
+ next()
diff --git a/lib/use-csrf.coffee b/lib/use-csrf.coffee
new file mode 100644
index 0000000..482ad49
--- /dev/null
+++ b/lib/use-csrf.coffee
@@ -0,0 +1,4 @@
+module.exports = (req, res, next) ->
+ token = req.csrfToken()
+ res.locals.csrfToken = token
+ next()
diff --git a/migrations/20150228184510_cdn-storage.coffee b/migrations/20150228184510_cdn-storage.coffee
new file mode 100644
index 0000000..7e9d9bd
--- /dev/null
+++ b/migrations/20150228184510_cdn-storage.coffee
@@ -0,0 +1,8 @@
+
+exports.up = (knex, Promise) ->
+ knex.schema.table "documents", (table) ->
+ table.boolean "CDN"
+
+exports.down = (knex, Promise) ->
+ knex.schema.table "documents", (table) ->
+ table.dropColumn "CDN"
diff --git a/migrations/20150228185518_thumbnails.coffee b/migrations/20150228185518_thumbnails.coffee
new file mode 100644
index 0000000..12dbd0a
--- /dev/null
+++ b/migrations/20150228185518_thumbnails.coffee
@@ -0,0 +1,8 @@
+
+exports.up = (knex, Promise) ->
+ knex.schema.table "documents", (table) ->
+ table.boolean "Thumbnailed"
+
+exports.down = (knex, Promise) ->
+ knex.schema.table "documents", (table) ->
+ table.dropColumn "Thumbnailed"
diff --git a/migrations/20150321122401_public-index.coffee b/migrations/20150321122401_public-index.coffee
new file mode 100644
index 0000000..8af5ed4
--- /dev/null
+++ b/migrations/20150321122401_public-index.coffee
@@ -0,0 +1,8 @@
+
+exports.up = (knex, Promise) ->
+ knex.schema.table "documents", (table) ->
+ knex.schema.raw "ALTER TABLE documents ADD INDEX (Public)"
+
+
+exports.down = (knex, Promise) ->
+ #
diff --git a/migrations/20150321141933_blog.coffee b/migrations/20150321141933_blog.coffee
new file mode 100644
index 0000000..f459e0a
--- /dev/null
+++ b/migrations/20150321141933_blog.coffee
@@ -0,0 +1,13 @@
+
+exports.up = (knex, Promise) ->
+ knex.schema.createTable "blog_posts", (table) ->
+ table.bigIncrements("Id")
+ table.string("Slug")
+ table.string("Title")
+ table.text("Body", "longtext")
+ table.timestamp("Posted").nullable()
+ table.timestamp("Edited").nullable()
+
+
+exports.down = (knex, Promise) ->
+ knex.schema.dropTable "blog_posts"
diff --git a/migrations/20150321183245_persist-init.coffee b/migrations/20150321183245_persist-init.coffee
new file mode 100644
index 0000000..af5fd81
--- /dev/null
+++ b/migrations/20150321183245_persist-init.coffee
@@ -0,0 +1,61 @@
+rfr = require "rfr"
+persist = rfr "lib/persist"
+
+initializeVariable = (name, type, initialValue) ->
+ Promise.all [
+ persist.addListItem "variableTypes",
+ name: name
+ type: type
+
+ persist.setItem "var:#{name}", initialValue
+ ]
+
+removeVariable = (name) ->
+ Promise.all [
+ persist.removeListItemByFilter "variableTypes", (item) ->
+ return (item.name == name)
+
+ persist.removeItem "var:#{name}"
+ ]
+
+initializeTaskType = (name) ->
+ Promise.all [
+ persist.addListItem "taskTypes", name
+ persist.setItem "task:#{name}:running", 0
+ persist.setItem "task:#{name}:queued", 0
+ persist.setItem "task:#{name}:failed", 0
+ ]
+
+removeTaskType = (name) ->
+ Promise.all [
+ persist.removeListItem "taskTypes", name
+ persist.removeItem "task:#{name}:running"
+ persist.removeItem "task:#{name}:queued"
+ persist.removeItem "task:#{name}:failed"
+ ]
+
+exports.up = (knex, Promise) ->
+ Promise.all [
+ initializeVariable "cdnRateLimit", "number", 0
+ initializeVariable "announcementText", "string", ""
+ initializeVariable "announcementLinkText", "string", ""
+ initializeVariable "announcementLink", "string", ""
+ initializeVariable "announcementVisible", "boolean", false
+ initializeVariable "maintenanceMode", "boolean", false
+ initializeVariable "maintenanceModeText", "text", ""
+ initializeTaskType "mirror"
+ initializeTaskType "thumbnail"
+ ]
+
+exports.down = (knex, Promise) ->
+ Promise.all [
+ removeVariable "cdnRateLimit"
+ removeVariable "announcementText"
+ removeVariable "announcementLinkText"
+ removeVariable "announcementLink"
+ removeVariable "announcementVisible"
+ removeVariable "maintenanceMode"
+ removeVariable "maintenanceModeText"
+ removeTaskType "mirror"
+ removeTaskType "thumbnail"
+ ]
diff --git a/migrations/20150321220357_abuse.coffee b/migrations/20150321220357_abuse.coffee
new file mode 100644
index 0000000..d805e82
--- /dev/null
+++ b/migrations/20150321220357_abuse.coffee
@@ -0,0 +1,9 @@
+
+exports.up = (knex, Promise) ->
+ knex.schema.table "documents", (table) ->
+ table.boolean "Disabled"
+ .defaultTo 0
+
+exports.down = (knex, Promise) ->
+ knex.schema.table "documents", (table) ->
+ table.dropColumn "Disabled"
diff --git a/migrations/20150321232319_persist-tasks-completed.coffee b/migrations/20150321232319_persist-tasks-completed.coffee
new file mode 100644
index 0000000..adcd249
--- /dev/null
+++ b/migrations/20150321232319_persist-tasks-completed.coffee
@@ -0,0 +1,24 @@
+rfr = require "rfr"
+persist = rfr "lib/persist"
+
+migrateTaskType = (name) ->
+ Promise.all [
+ persist.setItem "task:#{name}:completed", 0
+ ]
+
+rollbackTaskType = (name) ->
+ Promise.all [
+ persist.removeItem "task:#{name}:completed"
+ ]
+
+exports.up = (knex, Promise) ->
+ Promise.all [
+ migrateTaskType "mirror"
+ migrateTaskType "thumbnail"
+ ]
+
+exports.down = (knex, Promise) ->
+ Promise.all [
+ rollbackTaskType "mirror"
+ rollbackTaskType "thumbnail"
+ ]
diff --git a/migrations/20150322001330_persist-more.coffee b/migrations/20150322001330_persist-more.coffee
new file mode 100644
index 0000000..47beac1
--- /dev/null
+++ b/migrations/20150322001330_persist-more.coffee
@@ -0,0 +1,33 @@
+rfr = require "rfr"
+persist = rfr "lib/persist"
+
+initializeVariable = (name, type, initialValue) ->
+ Promise.all [
+ persist.addListItem "variableTypes",
+ name: name
+ type: type
+
+ persist.setItem "var:#{name}", initialValue
+ ]
+
+removeVariable = (name) ->
+ Promise.all [
+ persist.removeListItemByFilter "variableTypes", (item) ->
+ return (item.name == name)
+
+ persist.removeItem "var:#{name}"
+ ]
+
+exports.up = (knex, Promise) ->
+ Promise.all [
+ initializeVariable "donationGoal", "number", 500
+ initializeVariable "donationTotal", "number", 0
+ initializeVariable "showNotice", "boolean", false
+ ]
+
+exports.down = (knex, Promise) ->
+ Promise.all [
+ removeVariable "donationGoal"
+ removeVariable "donationTotal"
+ removeVariable "showNotice"
+ ]
diff --git a/migrations/20150418225818_abuse-reason.coffee b/migrations/20150418225818_abuse-reason.coffee
new file mode 100644
index 0000000..a98731a
--- /dev/null
+++ b/migrations/20150418225818_abuse-reason.coffee
@@ -0,0 +1,9 @@
+
+exports.up = (knex, Promise) ->
+ knex.schema.table "documents", (table) ->
+ table.string "DisabledReason"
+ .nullable()
+
+exports.down = (knex, Promise) ->
+ knex.schema.table "documents", (table) ->
+ table.dropColumn "DisabledReason"
diff --git a/models/blogpost.coffee b/models/blogpost.coffee
new file mode 100644
index 0000000..1c6a5e3
--- /dev/null
+++ b/models/blogpost.coffee
@@ -0,0 +1,6 @@
+Promise = require "bluebird"
+
+module.exports = (shelf) ->
+ shelf.model "BlogPost",
+ tableName: "blog_posts"
+ idAttribute: "Id"
diff --git a/models/document.coffee b/models/document.coffee
new file mode 100644
index 0000000..9ac5584
--- /dev/null
+++ b/models/document.coffee
@@ -0,0 +1,6 @@
+Promise = require "bluebird"
+
+module.exports = (shelf) ->
+ shelf.model "Document",
+ tableName: "documents"
+ idAttribute: "Id"
diff --git a/models/index.coffee b/models/index.coffee
new file mode 100644
index 0000000..299454b
--- /dev/null
+++ b/models/index.coffee
@@ -0,0 +1,13 @@
+Promise = require "bluebird"
+glob = Promise.promisify(require "glob")
+rfr = require "rfr"
+
+# This file automatically loads all models.
+
+module.exports = (shelf) ->
+ Promise.try ->
+ glob "models/**/*.coffee"
+ .then (items) ->
+ for item in items
+ if item != "models/index.coffee"
+ rfr(item)(shelf)
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d797d11
--- /dev/null
+++ b/package.json
@@ -0,0 +1,77 @@
+{
+ "name": "pdfy",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "dependencies": {
+ "Base64": "^0.3.0",
+ "ansi-to-html": "^0.3.0",
+ "bhttp": "^1.0.3",
+ "bip21": "1.0.0",
+ "bluebird": "^2.9.3",
+ "body-parser": "~1.10.2",
+ "buffertools": "^2.1.2",
+ "chokidar": "^1.0.1",
+ "concat-stream": "^1.4.7",
+ "connect-busboy": "0.0.2",
+ "cookie-parser": "~1.3.3",
+ "csurf": "^1.8.0",
+ "debug": "~2.1.1",
+ "errors": "^0.2.0",
+ "express": "~4.11.1",
+ "express-brute": "^0.5.3",
+ "express-domain-middleware": "^0.1.0",
+ "express-promise-router": "0.0.7",
+ "express-session": "^1.11.1",
+ "file-stream-rotator": "joepie91/file-stream-rotator",
+ "glob": "^4.3.5",
+ "gm": "^1.17.0",
+ "ia-headers": "^1.0.0",
+ "jade": "~1.9.1",
+ "knex": "^0.7.6",
+ "lodash": "^3.3.1",
+ "marked": "^0.3.3",
+ "moment": "^2.9.0",
+ "morgan": "~1.5.1",
+ "mysql2": "^0.15.4",
+ "node-persist": "joepie91/node-persist",
+ "nodemailer": "^1.3.4",
+ "pretty-error": "^1.1.1",
+ "qr-image": "^3.1.0",
+ "read": "^1.0.5",
+ "rfr": "^1.2.2",
+ "scrypt-for-humans": "^1.0.1",
+ "serve-favicon": "~2.2.0",
+ "session-file-store": "0.0.3",
+ "slug": "^0.8.0",
+ "stream-length": "^1.0.2",
+ "uuid": "^2.0.1",
+ "xtend": "^4.0.0",
+ "zero-fill": "^2.1.0"
+ },
+ "devDependencies": {
+ "autosize": "^3.0.0",
+ "blueimp-file-upload": "^9.9.3",
+ "coffee-loader": "^0.7.2",
+ "coffee-script": "^1.9.1",
+ "gulp": "^3.8.10",
+ "gulp-cached": "~0.0.3",
+ "gulp-coffee": "~2.0.1",
+ "gulp-concat": "~2.2.0",
+ "gulp-jade": "^0.7.0",
+ "gulp-livereload": "~2.1.0",
+ "gulp-nodemon": "~1.0.4",
+ "gulp-notify": "^1.6.0",
+ "gulp-plumber": "~0.6.3",
+ "gulp-remember": "~0.2.0",
+ "gulp-rename": "~1.2.0",
+ "gulp-sass": "^1.3.3",
+ "gulp-util": "~2.2.17",
+ "gulp-webpack": "^1.3.0",
+ "imports-loader": "^0.6.3",
+ "jquery": "^2.1.3",
+ "pretty-units": "^0.1.0"
+ }
+}
diff --git a/public/css/pure-min.css b/public/css/pure-min.css
new file mode 100644
index 0000000..420907c
--- /dev/null
+++ b/public/css/pure-min.css
@@ -0,0 +1,11 @@
+/*!
+Pure v0.4.2
+Copyright 2014 Yahoo! Inc. All rights reserved.
+Licensed under the BSD License.
+https://github.com/yui/pure/blob/master/LICENSE.md
+*/
+/*!
+normalize.css v1.1.3 | MIT License | git.io/normalize
+Copyright (c) Nicolas Gallagher and Jonathan Neal
+*/
+/*! normalize.css v1.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}[hidden]{display:none!important}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-g-r{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap}.opera-only :-o-prefocus,.pure-g-r{word-spacing:-.43em}.pure-g-r [class *="pure-u"]{font-family:sans-serif}.pure-g-r img{max-width:100%;height:auto}@media (min-width:980px){.pure-visible-phone{display:none}.pure-visible-tablet{display:none}.pure-hidden-desktop{display:none}}@media (max-width:480px){.pure-g-r>.pure-u,.pure-g-r>[class *="pure-u-"]{width:100%}}@media (max-width:767px){.pure-g-r>.pure-u,.pure-g-r>[class *="pure-u-"]{width:100%}.pure-hidden-phone{display:none}.pure-visible-desktop{display:none}}@media (min-width:768px) and (max-width:979px){.pure-hidden-tablet{display:none}.pure-visible-desktop{display:none}}.pure-button{display:inline-block;*display:inline;zoom:1;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;*font-size:90%;*overflow:visible;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);*color:#444;border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;outline:thin dotted \9;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;outline:thin dotted \9;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin dotted #333;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#ee5f5b}.pure-form input:focus:invalid:focus,.pure-form textarea:focus:invalid:focus,.pure-form select:focus:invalid:focus{border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 10em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input{display:block;padding:10px;margin:0;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus{z-index:2}.pure-form .pure-group input:first-child{top:1px;border-radius:4px 4px 0 0}.pure-form .pure-group input:last-child{top:-2px;border-radius:0 0 4px 4px}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu ul{position:absolute;visibility:hidden}.pure-menu.pure-menu-open{visibility:visible;z-index:2;width:100%}.pure-menu ul{left:-10000px;list-style:none;margin:0;padding:0;top:-10000px;z-index:1}.pure-menu>ul{position:relative}.pure-menu-open>ul{left:0;top:0;visibility:visible}.pure-menu-open>ul:focus{outline:0}.pure-menu li{position:relative}.pure-menu a,.pure-menu .pure-menu-heading{display:block;color:inherit;line-height:1.5em;padding:5px 20px;text-decoration:none;white-space:nowrap}.pure-menu.pure-menu-horizontal>.pure-menu-heading{display:inline-block;*display:inline;zoom:1;margin:0;vertical-align:middle}.pure-menu.pure-menu-horizontal>ul{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu li a{padding:5px 20px}.pure-menu-can-have-children>.pure-menu-label:after{content:'\25B8';float:right;font-family:'Lucida Grande','Lucida Sans Unicode','DejaVu Sans',sans-serif;margin-right:-20px;margin-top:-1px}.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-separator{background-color:#dfdfdf;display:block;height:1px;font-size:0;margin:7px 2px;overflow:hidden}.pure-menu-hidden{display:none}.pure-menu-fixed{position:fixed;top:0;left:0;width:100%}.pure-menu-horizontal li{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-horizontal li li{display:block}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label:after{content:"\25BE"}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-horizontal li.pure-menu-separator{height:50%;width:1px;margin:0 7px}.pure-menu-horizontal li li.pure-menu-separator{height:1px;width:auto;margin:7px 2px}.pure-menu.pure-menu-open,.pure-menu.pure-menu-horizontal li .pure-menu-children{background:#fff;border:1px solid #b7b7b7}.pure-menu.pure-menu-horizontal,.pure-menu.pure-menu-horizontal .pure-menu-heading{border:0}.pure-menu a{border:1px solid transparent;border-left:0;border-right:0}.pure-menu a,.pure-menu .pure-menu-can-have-children>li:after{color:#777}.pure-menu .pure-menu-can-have-children>li:hover:after{color:#fff}.pure-menu .pure-menu-open{background:#dedede}.pure-menu li a:hover,.pure-menu li a:focus{background:#eee}.pure-menu li.pure-menu-disabled a:hover,.pure-menu li.pure-menu-disabled a:focus{background:#fff;color:#bfbfbf}.pure-menu .pure-menu-disabled>a{background-image:none;border-color:transparent;cursor:default}.pure-menu .pure-menu-disabled>a,.pure-menu .pure-menu-can-have-children.pure-menu-disabled>a:after{color:#bfbfbf}.pure-menu .pure-menu-heading{color:#565d64;text-transform:uppercase;font-size:90%;margin-top:.5em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#dfdfdf}.pure-menu .pure-menu-selected a{color:#000}.pure-menu.pure-menu-open.pure-menu-fixed{border:0;border-bottom:1px solid #b7b7b7}.pure-paginator{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;list-style:none;margin:0;padding:0}.opera-only :-o-prefocus,.pure-paginator{word-spacing:-.43em}.pure-paginator li{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-paginator .pure-button{border-radius:0;padding:.8em 1.4em;vertical-align:top;height:1.1em}.pure-paginator .pure-button:focus,.pure-paginator .pure-button:active{outline-style:none}.pure-paginator .prev,.pure-paginator .next{color:#C0C1C3;text-shadow:0 -1px 0 rgba(0,0,0,.45)}.pure-paginator .prev{border-radius:2px 0 0 2px}.pure-paginator .next{border-radius:0 2px 2px 0}@media (max-width:480px){.pure-menu-horizontal{width:100%}.pure-menu-children li{display:block;border-bottom:1px solid #000}}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:6px 12px}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child td,.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0}
\ No newline at end of file
diff --git a/public/css/style.css b/public/css/style.css
new file mode 100644
index 0000000..a23c674
--- /dev/null
+++ b/public/css/style.css
@@ -0,0 +1,714 @@
+@-webkit-keyframes pulsate {
+ from {
+ box-shadow: 0 0 0px #000000; }
+
+ 40% {
+ box-shadow: 0 0 15px #005f52; }
+
+ 60% {
+ box-shadow: 0 0 15px #005f52; }
+
+ to {
+ box-shadow: 0 0 0px #000000; } }
+
+@-moz-keyframes pulsate {
+ from {
+ box-shadow: 0 0 0px #000000; }
+
+ 40% {
+ box-shadow: 0 0 15px #005f52; }
+
+ 60% {
+ box-shadow: 0 0 15px #005f52; }
+
+ to {
+ box-shadow: 0 0 0px #000000; } }
+
+@-o-keyframes pulsate {
+ from {
+ box-shadow: 0 0 0px #000000; }
+
+ 40% {
+ box-shadow: 0 0 15px #005f52; }
+
+ 60% {
+ box-shadow: 0 0 15px #005f52; }
+
+ to {
+ box-shadow: 0 0 0px #000000; } }
+
+@-ms-keyframes pulsate {
+ from {
+ box-shadow: 0 0 0px #000000; }
+
+ 40% {
+ box-shadow: 0 0 15px #005f52; }
+
+ 60% {
+ box-shadow: 0 0 15px #005f52; }
+
+ to {
+ box-shadow: 0 0 0px #000000; } }
+
+.pulsate {
+ -webkit-animation-name: pulsate;
+ -webkit-animation-duration: 800ms;
+ -webkit-animation-iteration-count: 3;
+ -moz-animation-name: pulsate;
+ -moz-animation-duration: 800ms;
+ -moz-animation-iteration-count: 3;
+ -o-animation-name: pulsate;
+ -o-animation-duration: 800ms;
+ -o-animation-iteration-count: 3;
+ -ms-animation-name: pulsate;
+ -ms-animation-duration: 800ms;
+ -ms-animation-iteration-count: 3; }
+
+.error-stack {
+ background-color: black;
+ padding: 16px; }
+
+.side-margins {
+ margin-left: 64px;
+ margin-right: 64px; }
+
+.pure-button-small {
+ padding: 6px 11px;
+ font-size: 15px; }
+
+.clearfix:after {
+ content: "";
+ display: table;
+ clear: both; }
+
+.return-button {
+ margin-top: -3px;
+ float: right; }
+
+body {
+ background-color: #2D2D31;
+ color: white;
+ font-family: "PT Sans", sans-serif;
+ padding: 0px;
+ margin: 0px;
+ font-size: 16px; }
+ body.full-screen {
+ overflow: hidden; }
+ body a {
+ color: #94EBD9;
+ text-decoration: none; }
+ body a:hover {
+ /*color: #67C7B3;*/
+ color: #BAF5E9; }
+ body section, body .popup {
+ padding: 16px 19px;
+ background-color: #1f1f1f;
+ border-radius: 6px;
+ margin-top: 24px; }
+ body section h3, body .popup h3 {
+ margin-top: 0px; }
+ body .popup {
+ padding: 8px 12px;
+ margin-top: 0px;
+ display: inline-block; }
+ body .popup label, body .popup input, body .popup button {
+ margin-right: 12px;
+ padding: 0.2em 0.6em !important; }
+ body .popup input {
+ width: 400px; }
+ body .pure-button {
+ background-color: #000000;
+ border: 1px solid #0B7474;
+ color: white; }
+ body .pure-button.inline {
+ padding: 3px 9px;
+ margin: 0px 4px; }
+ body .wrapper {
+ max-width: 960px;
+ margin: 0px auto; }
+ body .header, body .contents, body .subtext {
+ padding: 18px; }
+ body .subtext {
+ background-color: #1f1f1f;
+ padding: 4px 18px; }
+ body .progress-bar {
+ position: relative;
+ border-radius: 8px;
+ margin: 16px 0px;
+ overflow: hidden;
+ background-color: #06282D;
+ padding: 4px 3px;
+ height: 24px; }
+ body .progress-fill {
+ border-radius: 8px;
+ background-color: #0a8071;
+ height: 24px; }
+ body .progress-text {
+ position: absolute;
+ top: 4px;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+ text-align: center;
+ font-size: 18px;
+ text-shadow: 0px 0px 3px #000000;
+ -webkit-text-shadow: 0px 0px 3px #000000;
+ -moz-text-shadow: 0px 0px 3px #000000;
+ -o-text-shadow: 0px 0px 3px #000000;
+ -ms-text-shadow: 0px 0px 3px #000000; }
+ body .progress-container {
+ position: relative;
+ height: 24px;
+ margin: 28px 0px; }
+ body .progress-container label {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ font-size: 19px;
+ font-weight: bold;
+ margin-top: 4px; }
+ body .progress-container .progress-bar {
+ position: absolute;
+ left: 250px;
+ right: 0px;
+ margin: 0px; }
+ body .progress-container .progress-text {
+ text-align: left;
+ margin-left: 16px; }
+ body .header {
+ background-color: black;
+ overflow: hidden;
+ height: 42px; }
+ body .header h1 {
+ margin: 0px;
+ display: inline-block;
+ font-size: 32px; }
+ body .header h1 a {
+ text-decoration: inherit;
+ color: inherit; }
+ body .header .pure-button {
+ background-color: #3C3C3C; }
+ body .header .button-upload, body .header .button-lite {
+ /*display: inline-block;
+ margin-left: 32px;
+ vertical-align: 5px;*/
+ float: right; }
+ body .header .button-lite {
+ border: 1px solid transparent;
+ background: none;
+ margin-right: 4px; }
+ body .header .button-lite:hover {
+ background-color: #3C3C3C;
+ border: 1px solid #0B7474; }
+ body .header .abuse {
+ float: right;
+ margin-right: 64px;
+ margin-top: 10px; }
+ body .contents h2 {
+ margin-top: 0px; }
+ body .contents #upload_activator {
+ margin-bottom: 18px; }
+ body .contents .latest {
+ margin-top: 64px; }
+ body .contents .latest a.document {
+ display: inline-block;
+ width: 150px;
+ height: 218px;
+ background-color: white;
+ text-decoration: none;
+ border: 0px;
+ margin-right: 6px; }
+ body .contents .latest a.document img {
+ width: 150px; }
+ body .contents .latest a.gallery-link {
+ display: block;
+ float: right;
+ padding: 12px 64px; }
+ body .contents .upload-form {
+ font-size: 18px;
+ text-align: center;
+ margin-top: 38px; }
+ body .contents .upload-form .button-browse {
+ font-size: 24px;
+ vertical-align: -1px;
+ margin-right: 24px; }
+ body .contents .upload-form .faded {
+ opacity: 0.4; }
+ body .contents .upload-form .info, body .contents .upload-form .fileinfo, body .contents .upload-form .progress {
+ text-align: left;
+ max-width: 700px;
+ margin: 64px auto 0px auto; }
+ body .contents .upload-form .fileinfo {
+ display: none; }
+ body .contents .upload-form .fileinfo h2 {
+ margin: 0px 0px 16px 0px; }
+ body .contents .upload-form .fileinfo label {
+ margin-left: 7px; }
+ body .contents .upload-form .fileinfo .button-submit {
+ float: right;
+ margin-top: 16px; }
+ body .contents .upload-form .progress {
+ display: none;
+ margin-top: 32px; }
+ body .contents .upload-form .progress .wait {
+ display: none; }
+ body .contents .upload-form #uploadError {
+ background-color: #CD1E32;
+ padding: 20px 28px;
+ text-align: left;
+ width: 700px;
+ margin: 0px auto;
+ display: none;
+ margin-top: 16px; }
+ body .contents .upload-form #uploadError h3 {
+ margin-top: 0px;
+ margin-bottom: 7px; }
+ body .contents .upload-form #uploadError p {
+ margin: 0px; }
+ body .contents .upload-form #upload_element {
+ display: none; }
+ body .contents .upload-form .bar, body .contents .upload-form .bar-inner {
+ height: 12px; }
+ body .contents .upload-form .bar {
+ border: 1px solid #0B7474;
+ border-radius: 4px;
+ overflow: hidden;
+ margin-top: 8px; }
+ body .contents .upload-form .bar-inner {
+ background-color: #014949;
+ width: 0%;
+ border-radius: 3px; }
+ body .viewer-contents .embed_code, body .viewer-contents .link_code {
+ background-color: #1C1C1C;
+ border: 1px solid black;
+ border-radius: 4px;
+ padding: 4px;
+ color: #E1E1E1; }
+ body .viewer-contents textarea.embed_code, body .viewer-contents .link_code {
+ width: 247px;
+ margin-top: 7px; }
+ body .viewer-contents input.embed_code {
+ width: 275px; }
+ body .viewer-contents .viewer-wrapper {
+ position: absolute;
+ top: 78px;
+ /*top: 117px;*/
+ bottom: 0px;
+ left: 0px;
+ right: 320px;
+ overflow: hidden; }
+ body .viewer-contents .viewer-wrapper .viewer {
+ width: 100%;
+ height: 100%;
+ border: 0px; }
+ body .viewer-contents .bottombar {
+ display: none;
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+ height: 48px;
+ padding: 8px; }
+ body .viewer-contents .bottombar .header-wrapper {
+ width: 99%; }
+ body .viewer-contents .bottombar .header-wrapper h2 {
+ display: block;
+ margin: 0px 0px 3px 0px;
+ font-size: 17px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis; }
+ body .viewer-contents .bottombar .tools {
+ font-size: 14px; }
+ body .viewer-contents .bottombar .tools .views {
+ float: left;
+ margin-top: 4px;
+ font-weight: bold; }
+ body .viewer-contents .bottombar .tools .embed {
+ float: right;
+ margin-right: 48px; }
+ body .viewer-contents .bottombar .tools .download {
+ float: right; }
+ body .viewer-contents .sidebar {
+ position: absolute;
+ top: 78px;
+ /*top: 117px;*/
+ bottom: 0px;
+ right: 0px;
+ width: 320px;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ overflow-y: auto; }
+ body .viewer-contents .sidebar .actual-contents {
+ padding: 16px; }
+ body .viewer-contents .sidebar h2 {
+ margin: 6px 0px 0px 0px;
+ overflow: hidden;
+ text-overflow: ellipsis; }
+ body .viewer-contents .sidebar .embed_code {
+ height: 100px; }
+ body .viewer-contents .sidebar .download-box, body .viewer-contents .sidebar .embed-box, body .viewer-contents .sidebar .link-box {
+ padding: 13px 16px;
+ margin-top: 24px;
+ border-radius: 4px; }
+ body .viewer-contents .sidebar .download-box {
+ background-color: #0b4d56; }
+ body .viewer-contents .sidebar .download-box .formats {
+ margin-top: 12px; }
+ body .viewer-contents .sidebar .embed-box, body .viewer-contents .sidebar .link-box {
+ background-color: #3a3e45; }
+ body .viewer-contents .sidebar h3 {
+ margin: 0px 0px 4px 0px; }
+ body .viewer-contents .sidebar p {
+ margin-top: 4px;
+ margin-bottom: 0px; }
+ body .viewer-contents .sidebar .donation-box {
+ padding: 0px 16px 16px 16px;
+ background-color: black;
+ text-align: center; }
+ body .viewer-contents .sidebar .donation-box h3 {
+ font-size: 18px;
+ margin-bottom: 8px; }
+ body .viewer-contents .sidebar .donation-box p.amounts {
+ font-size: 21px;
+ margin-bottom: 12px; }
+ body .viewer-contents .sidebar .donation-box .donation-buttons {
+ margin-top: 16px;
+ font-size: 15px; }
+ body .viewer-contents .sidebar .donation-box .donation-buttons a.pure-button {
+ margin-right: 16px;
+ padding: .4em .9em; }
+ body .viewer-contents .sidebar .toolbar-settings {
+ margin: 8px 3px; }
+ body .viewer-contents .sidebar .toolbar-settings input {
+ position: relative;
+ top: 2px;
+ margin-right: 6px; }
+ body .viewer-contents .sidebar .toolbar-settings label {
+ margin-right: 14px;
+ margin-top: -2px;
+ font-size: 14px; }
+ body .viewer-contents .sidebar .stats {
+ margin-top: 8px;
+ font-size: 13px;
+ padding: 5px 8px;
+ text-align: center; }
+ body.announcement-visible .viewer-contents .viewer-wrapper, body.announcement-visible .viewer-contents .sidebar {
+ top: 117px; }
+ body .gallery {
+ background-color: #242427;
+ border: 1px solid black; }
+ body .gallery .next, body .gallery .previous {
+ display: block;
+ padding: 16px;
+ font-size: 18px; }
+ body .gallery .next {
+ float: right; }
+ body .gallery .previous {
+ float: left; }
+ body .gallery .document {
+ padding: 24px;
+ border-bottom: 1px solid black;
+ display: block;
+ color: white; }
+ body .gallery .document:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden; }
+ body .gallery .document:hover {
+ background-color: #2a2a2e; }
+ body .gallery .document span {
+ display: block; }
+ body .gallery .document span.thumb {
+ width: 83px;
+ height: 120px;
+ float: left;
+ background-color: white;
+ margin-right: 16px; }
+ body .gallery .document span.thumb img {
+ height: 120px; }
+ body .gallery .document .name {
+ font-weight: bold;
+ font-size: 24px; }
+ body .gallery .document .date {
+ font-size: 20px; }
+ body .gallery .document .views {
+ font-size: 18px; }
+ body .donation-page .js-available {
+ display: none; }
+ body .donation-page .bip21-qr {
+ float: right;
+ margin: -25px 12px 12px 12px; }
+ body .donation-page section:after {
+ content: "";
+ display: table;
+ clear: both; }
+ body .donation-page section.instructions {
+ min-height: 200px; }
+ body .donation-page .option {
+ float: left;
+ padding: 14px;
+ border: 1px solid gray;
+ margin: 5px;
+ width: 190px;
+ height: 60px;
+ border-radius: 6px;
+ text-align: center;
+ font-size: 20px;
+ position: relative;
+ background-color: #0D0D0D; }
+ body .donation-page .option.payment-method {
+ height: 75px; }
+ body .donation-page .option.payment-method label {
+ font-size: 14px; }
+ body .donation-page .option.payment-method label.fixed {
+ position: absolute;
+ bottom: 21px;
+ left: 0px;
+ right: 0px;
+ text-align: center; }
+ body .donation-page .option.payment-method label.logo {
+ font-size: 33px;
+ font-weight: bold;
+ color: #efefef; }
+ body .donation-page .option.selected {
+ background-color: #003D35; }
+ body .donation-page .option label, body .donation-page .option input {
+ display: block;
+ margin: 0px auto; }
+ body .donation-page .option label {
+ margin-bottom: 9px; }
+ body .donation-page .option input[type="radio"] {
+ position: absolute;
+ bottom: 12px;
+ left: 50%;
+ margin-left: -6px; }
+ body .donation-page .option .exchange-rate {
+ font-size: 15px; }
+ body .donation-page .option #custom_amount_input {
+ width: 72px;
+ display: inline;
+ margin-left: 5px;
+ background-color: #202020;
+ border: 1px solid #474747;
+ color: white;
+ padding: 4px 4px 3px 4px;
+ font-size: 19px; }
+ body .donation-page .paypal-button {
+ text-align: center;
+ padding: 24px;
+ background-color: #0D0D0D; }
+ body .donation-page .paypal-button:hover {
+ background-color: black; }
+ body .blog-post h2, body .blog-index h2 {
+ font-size: 29px; }
+ body .blog-index .post {
+ margin-bottom: 9px; }
+ body .blog-index .date {
+ font-family: monospace;
+ margin-right: 16px;
+ color: #dbdbdb;
+ font-size: 15px; }
+ body .blog-index .title {
+ margin-left: 8px; }
+ body .blog-post section {
+ padding: 1px 24px; }
+ body .blog-post a.anchor {
+ float: left;
+ margin-left: -16px;
+ margin-top: 4px;
+ font-weight: normal;
+ font-size: 80%; }
+ body .blog-post h3 {
+ font-size: 24px; }
+ body .blog-post h4 {
+ font-size: 19px; }
+ body .admin {
+ position: relative; }
+ body .admin th, body .admin td {
+ padding: 4px 7px; }
+ body .admin th {
+ text-align: left; }
+ body .admin td {
+ border-top: 1px solid gray; }
+ body .admin .save-button {
+ margin-right: 8px; }
+ body .admin form.pure-g [class*="pure-u"] {
+ box-sizing: border-box;
+ padding: 5px; }
+ body .admin form.pure-g label {
+ font-weight: bold;
+ font-size: 18px;
+ margin-bottom: 8px; }
+ body .admin form.pure-g .md-editor {
+ font-family: monospace; }
+ body .admin form.pure-g .md-preview {
+ border: 1px solid #575757;
+ padding: 8px 16px;
+ border-radius: 5px; }
+ body .admin form.pure-g .submit {
+ position: absolute;
+ right: 0px;
+ margin-top: -6px; }
+ body .error h2 {
+ font-size: 22px; }
+ body .pure-button img.icon {
+ position: relative;
+ top: 1px;
+ margin-right: 6px; }
+
+.footer {
+ margin-top: 32px;
+ padding-top: 12px;
+ border-top: 1px solid #DEDEDE;
+ font-size: 14px;
+ color: #DEDEDE; }
+
+#drag_ghost {
+ display: none;
+ position: absolute;
+ z-index: 999;
+ padding: 8px;
+ background-color: #1D3030;
+ border: 1px solid #0B7474;
+ box-shadow: 3px 3px 8px 0px #131314;
+ color: white;
+ font-size: 19px;
+ font-weight: bold;
+ border-radius: 5px; }
+
+@media (max-width: 570px) {
+ .button-lite.hide-x-small {
+ display: none; } }
+
+@media (max-width: 640px) {
+ .bottombar .embed {
+ display: none; } }
+
+@media (max-width: 660px) {
+ .alt-small {
+ display: block; }
+ .alt-large {
+ display: none; } }
+
+@media (min-width: 650px) {
+ .alt-small {
+ display: none; }
+ .alt-large {
+ display: block; } }
+
+@media (max-width: 800px) {
+ .dragdrop-instructions {
+ display: block; } }
+
+@media (max-width: 1000px) {
+ .hide-small {
+ display: none; } }
+
+@media (max-width: 1200px) {
+ body .viewer-contents .sidebar {
+ display: none; }
+ body .viewer-contents .viewer-wrapper {
+ right: 0px;
+ bottom: 64px; }
+ body .viewer-contents .bottombar {
+ display: block; } }
+
+@media (max-height: 940px) {
+ body .header {
+ padding: 10px 18px; }
+ body .viewer-contents .viewer-wrapper, body .viewer-contents .sidebar {
+ top: 62px; }
+ body .viewer-contents .sidebar h2 {
+ margin: 0px;
+ font-size: 20px; }
+ body .viewer-contents .sidebar h3 {
+ font-size: 18px;
+ margin: 0px; }
+ body .viewer-contents .sidebar p {
+ margin: 1px 0px; }
+ body .viewer-contents .sidebar .embed-box, body .viewer-contents .sidebar .link-box, body .viewer-contents .sidebar .download-box {
+ margin: 16px 0px;
+ padding: 8px 16px 10px 16px; }
+ body .viewer-contents .sidebar .donation-box {
+ padding-top: 8px; }
+ body .viewer-contents .sidebar textarea.embed_code {
+ height: 32px; }
+ body .viewer-contents .sidebar .stats {
+ margin-top: 4px;
+ padding: 0px 8px; }
+ body.announcement-visible .viewer-contents .viewer-wrapper, body.announcement-visible .viewer-contents .sidebar {
+ top: 101px; } }
+
+.clear {
+ clear: both; }
+
+.notice {
+ background-color: #086458;
+ color: white;
+ padding: 8px 14px;
+ margin-bottom: 32px;
+ border-radius: 4px; }
+ .notice a {
+ color: white; }
+ .notice p {
+ margin: 8px 0px; }
+
+.announce {
+ background-color: #0A8071;
+ color: white;
+ text-align: center;
+ padding: 12px 16px;
+ font-size: 18px; }
+ .announce a {
+ color: white; }
+
+.full-screen .announce {
+ height: 21px;
+ overflow: hidden;
+ font-size: 17px;
+ padding: 9px 16px; }
+
+::-webkit-scrollbar {
+ background-color: white; }
+
+::-webkit-scrollbar-track {
+ background-color: #5A5A61; }
+
+::-webkit-scrollbar-thumb {
+ background-color: #171717; }
+
+::-webkit-scrollbar-track:vertical {
+ border-left: 1px solid #323235; }
+
+::-webkit-scrollbar-thumb-vertical {
+ border-left: 1px solid #323235; }
+
+::-webkit-scrollbar-track:horizontal {
+ border-top: 1px solid #323235; }
+
+::-webkit-scrollbar-thumb-horizontal {
+ border-top: 1px solid #323235; }
+
+::-webkit-scrollbar-button {
+ background-color: #1C1C1C;
+ color: white;
+ background-position: 0px -1px; }
+
+::-webkit-scrollbar-button:vertical:increment {
+ background-image: url(/static/pdfjs/images/arrow-down.png);
+ border-top: 1px solid black; }
+
+::-webkit-scrollbar-button:vertical:decrement {
+ background-image: url(/static/pdfjs/images/arrow-up.png);
+ border-bottom: 1px solid black; }
+
+::-webkit-scrollbar-button:horizontal:increment {
+ background-image: url(/static/pdfjs/images/arrow-right.png);
+ border-left: 1px solid black; }
+
+::-webkit-scrollbar-button:horizontal:decrement {
+ background-image: url(/static/pdfjs/images/arrow-left.png);
+ border-right: 1px solid black; }
diff --git a/public/css/style.scss b/public/css/style.scss
new file mode 100644
index 0000000..19423f0
--- /dev/null
+++ b/public/css/style.scss
@@ -0,0 +1,1274 @@
+@-webkit-keyframes pulsate
+{
+ from { box-shadow: 0 0 0px #000000; }
+ 40% { box-shadow: 0 0 15px #005f52; }
+ 60% { box-shadow: 0 0 15px #005f52; }
+ to { box-shadow: 0 0 0px #000000; }
+}
+
+@-moz-keyframes pulsate
+{
+ from { box-shadow: 0 0 0px #000000; }
+ 40% { box-shadow: 0 0 15px #005f52; }
+ 60% { box-shadow: 0 0 15px #005f52; }
+ to { box-shadow: 0 0 0px #000000; }
+}
+
+@-o-keyframes pulsate
+{
+ from { box-shadow: 0 0 0px #000000; }
+ 40% { box-shadow: 0 0 15px #005f52; }
+ 60% { box-shadow: 0 0 15px #005f52; }
+ to { box-shadow: 0 0 0px #000000; }
+}
+
+@-ms-keyframes pulsate
+{
+ from { box-shadow: 0 0 0px #000000; }
+ 40% { box-shadow: 0 0 15px #005f52; }
+ 60% { box-shadow: 0 0 15px #005f52; }
+ to { box-shadow: 0 0 0px #000000; }
+}
+
+.pulsate
+{
+ -webkit-animation-name: pulsate;
+ -webkit-animation-duration: 800ms;
+ -webkit-animation-iteration-count: 3;
+ -moz-animation-name: pulsate;
+ -moz-animation-duration: 800ms;
+ -moz-animation-iteration-count: 3;
+ -o-animation-name: pulsate;
+ -o-animation-duration: 800ms;
+ -o-animation-iteration-count: 3;
+ -ms-animation-name: pulsate;
+ -ms-animation-duration: 800ms;
+ -ms-animation-iteration-count: 3;
+}
+
+.error-stack
+{
+ background-color: black;
+ padding: 16px;
+}
+
+.side-margins
+{
+ margin-left: 64px;
+ margin-right: 64px;
+}
+
+.pure-button-small
+{
+ padding: 6px 11px;
+ font-size: 15px;
+}
+
+.clearfix:after
+{
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.return-button
+{
+ margin-top: -3px;
+ float: right;
+}
+
+body
+{
+ background-color: #2D2D31;
+ color: white;
+ font-family: "PT Sans", sans-serif;
+ padding: 0px;
+ margin: 0px;
+ font-size: 16px;
+
+ &.full-screen
+ {
+ overflow: hidden;
+ }
+
+ a
+ {
+ color: #94EBD9;
+ text-decoration: none;
+
+ &:hover
+ {
+ /*color: #67C7B3;*/
+ color: #BAF5E9;
+ }
+ }
+
+ section, .popup // For static content pages, primarily...
+ {
+ padding: 16px 19px;
+ background-color: rgb(31, 31, 31);
+ border-radius: 6px;
+ margin-top: 24px;
+
+ h3
+ {
+ margin-top: 0px;
+ }
+ }
+
+ .popup
+ {
+ padding: 8px 12px;
+ margin-top: 0px;
+ display: inline-block;
+
+ label, input, button
+ {
+ margin-right: 12px;
+ padding: 0.2em 0.6em !important;
+ }
+
+ input
+ {
+ width: 400px;
+ }
+ }
+
+ .pure-button
+ {
+ background-color: #000000;
+ border: 1px solid #0B7474;
+ color: white;
+
+ &.inline
+ {
+ padding: 3px 9px;
+ margin: 0px 4px;
+ }
+ }
+
+ .wrapper
+ {
+ max-width: 960px;
+ margin: 0px auto;
+ }
+
+ .header, .contents, .subtext
+ {
+ padding: 18px;
+ }
+
+ .subtext
+ {
+ background-color: rgb(31, 31, 31);
+ padding: 4px 18px;
+ }
+
+ .progress-bar
+ {
+ position: relative;
+ border-radius: 8px;
+ margin: 16px 0px;
+ overflow: hidden;
+ background-color: #06282D;
+ padding: 4px 3px;
+ height: 24px;
+ }
+
+ .progress-fill
+ {
+ border-radius: 8px;
+ background-color: rgb(10, 128, 113);
+ height: 24px;
+ }
+
+ .progress-text
+ {
+ position: absolute;
+ top: 4px;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+ text-align: center;
+ font-size: 18px;
+ text-shadow: 0px 0px 3px #000000;
+ -webkit-text-shadow: 0px 0px 3px #000000;
+ -moz-text-shadow: 0px 0px 3px #000000;
+ -o-text-shadow: 0px 0px 3px #000000;
+ -ms-text-shadow: 0px 0px 3px #000000;
+ }
+
+ .progress-container
+ {
+ position: relative;
+ height: 24px;
+ margin: 28px 0px;
+
+ label
+ {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ font-size: 19px;
+ font-weight: bold;
+ margin-top: 4px;
+ }
+
+ .progress-bar
+ {
+ position: absolute;
+ left: 250px;
+ right: 0px;
+ margin: 0px;
+ }
+
+ .progress-text
+ {
+ text-align: left;
+ margin-left: 16px;
+ }
+
+ }
+
+ .header
+ {
+ background-color: black;
+ overflow: hidden;
+ height: 42px;
+
+ h1
+ {
+ margin: 0px;
+ display: inline-block;
+ font-size: 32px;
+
+ a
+ {
+ text-decoration: inherit;
+ color: inherit;
+ }
+ }
+
+ .pure-button
+ {
+ background-color: #3C3C3C;
+ }
+
+ .button-upload, .button-lite
+ {
+ /*display: inline-block;
+ margin-left: 32px;
+ vertical-align: 5px;*/
+ float: right;
+ }
+
+ .button-lite
+ {
+ border: 1px solid transparent;
+ background: none;
+ margin-right: 4px;
+
+ &:hover
+ {
+ background-color: #3C3C3C;
+ border: 1px solid #0B7474;
+ }
+ }
+
+ .abuse
+ {
+ float: right;
+ margin-right: 64px;
+ margin-top: 10px;
+ }
+ }
+
+ .contents
+ {
+ h2
+ {
+ margin-top: 0px;
+ }
+
+ #upload_activator
+ {
+ margin-bottom: 18px;
+ }
+
+ .latest
+ {
+ margin-top: 64px;
+
+ a.document
+ {
+ display: inline-block;
+ width: 150px;
+ height: 218px;
+ background-color: white;
+ text-decoration: none;
+ border: 0px;
+ margin-right: 6px;
+
+ img
+ {
+ width: 150px;
+ }
+ }
+
+ a.gallery-link
+ {
+ display: block;
+ float: right;
+ padding: 12px 64px;
+ }
+ }
+
+ .upload-form
+ {
+ font-size: 18px;
+ text-align: center;
+ margin-top: 38px;
+
+ .button-browse
+ {
+ font-size: 24px;
+ vertical-align: -1px;
+ margin-right: 24px;
+ }
+
+ .faded
+ {
+ opacity: 0.4;
+ }
+
+ .info, .fileinfo, .progress
+ {
+ text-align: left;
+ max-width: 700px;
+ margin: 64px auto 0px auto;
+ }
+
+ .fileinfo
+ {
+ display: none;
+
+ h2
+ {
+ margin: 0px 0px 16px 0px;
+ }
+
+ label
+ {
+ margin-left: 7px;
+ }
+
+ .button-submit
+ {
+ float: right;
+ margin-top: 16px;
+ }
+ }
+
+ .progress
+ {
+ display: none;
+ margin-top: 32px;
+
+ .wait
+ {
+ display: none;
+ }
+ }
+
+ #uploadError
+ {
+ background-color: #CD1E32;
+ padding: 20px 28px;
+ text-align: left;
+ width: 700px;
+ margin: 0px auto;
+ display: none;
+ margin-top: 16px;
+
+ h3
+ {
+ margin-top: 0px;
+ margin-bottom: 7px;
+ }
+
+ p
+ {
+ margin: 0px;
+ }
+ }
+
+ #upload_element
+ {
+ display: none;
+ }
+
+ .bar, .bar-inner
+ {
+ height: 12px;
+ }
+
+ .bar
+ {
+ border: 1px solid #0B7474;
+ border-radius: 4px;
+ overflow: hidden;
+ margin-top: 8px;
+ }
+
+ .bar-inner
+ {
+ background-color: #014949;
+ width: 0%;
+ border-radius: 3px;
+ }
+ }
+ }
+
+ .viewer-contents
+ {
+ .embed_code, .link_code
+ {
+ background-color: #1C1C1C;
+ border: 1px solid black;
+ border-radius: 4px;
+ padding: 4px;
+ color: #E1E1E1;
+ }
+
+ textarea.embed_code, .link_code
+ {
+ width: 247px;
+ margin-top: 7px;
+ }
+
+ input.embed_code
+ {
+ width: 275px;
+ }
+
+ .viewer-wrapper
+ {
+ position: absolute;
+ top: 78px;
+ /*top: 117px;*/
+ bottom: 0px;
+ left: 0px;
+ right: 320px;
+ overflow: hidden;
+
+ .viewer
+ {
+ width: 100%;
+ height: 100%;
+ border: 0px;
+ }
+ }
+
+ .bottombar
+ {
+ display: none;
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+ height: 48px;
+ padding: 8px;
+
+ .header-wrapper
+ {
+ width: 99%;
+
+ h2
+ {
+ display: block;
+ margin: 0px 0px 3px 0px;
+ font-size: 17px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+
+ .tools
+ {
+ font-size: 14px;
+
+ .views
+ {
+ float: left;
+ margin-top: 4px;
+ font-weight: bold;
+ }
+
+ .embed
+ {
+ float: right;
+ margin-right: 48px;
+ }
+
+ .download
+ {
+ float: right;
+ }
+ }
+ }
+
+ .sidebar
+ {
+ position: absolute;
+ top: 78px;
+ /*top: 117px;*/
+ bottom: 0px;
+ right: 0px;
+ width: 320px;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ overflow-y: auto;
+
+ .actual-contents
+ {
+ padding: 16px;
+ }
+
+ h2
+ {
+ margin: 6px 0px 0px 0px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .embed_code
+ {
+ height: 100px;
+ }
+
+ .download-box, .embed-box, .link-box
+ {
+ padding: 13px 16px;
+ margin-top: 24px;
+ border-radius: 4px;
+ }
+
+ .download-box
+ {
+ background-color: rgb(11, 77, 86);
+
+ .formats
+ {
+ margin-top: 12px;
+ }
+ }
+
+ .embed-box, .link-box
+ {
+ background-color: rgb(58, 62, 69);
+ }
+
+ h3
+ {
+ margin: 0px 0px 4px 0px;
+ }
+
+ p
+ {
+ margin-top: 4px;
+ margin-bottom: 0px;
+ }
+
+ .donation-box
+ {
+ padding: 0px 16px 16px 16px;
+ background-color: black;
+ text-align: center;
+
+ h3
+ {
+ font-size: 18px;
+ margin-bottom: 8px;
+ }
+
+ p.amounts
+ {
+ font-size: 21px;
+ margin-bottom: 12px;
+ }
+
+ .donation-buttons
+ {
+ margin-top: 16px;
+ font-size: 15px;
+
+ a.pure-button
+ {
+ margin-right: 16px;
+ padding: .4em .9em;
+ }
+ }
+ }
+
+ .toolbar-settings
+ {
+ margin: 8px 3px;
+
+ input
+ {
+ // Strange hacks to make it align nicely with the labels...
+ position: relative;
+ top: 2px;
+ margin-right: 6px;
+ }
+
+ label
+ {
+ margin-right: 14px;
+ margin-top: -2px;
+ font-size: 14px;
+ }
+ }
+
+ .stats
+ {
+ margin-top: 8px;
+ font-size: 13px;
+ padding: 5px 8px;
+ text-align: center;
+ }
+ }
+ }
+
+ &.announcement-visible .viewer-contents
+ {
+ .viewer-wrapper, .sidebar
+ {
+ top: 117px;
+ }
+ }
+
+ .gallery
+ {
+ background-color: #242427;
+ border: 1px solid black;
+
+ .next, .previous
+ {
+ display: block;
+ padding: 16px;
+ font-size: 18px;
+ }
+
+ .next
+ {
+ float: right;
+ }
+
+ .previous
+ {
+ float: left;
+ }
+
+ .document
+ {
+ padding: 24px;
+ border-bottom: 1px solid black;
+ display: block;
+ color: white;
+
+ &:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+ }
+
+ &:hover
+ {
+ background-color: #2a2a2e;
+ }
+
+ span
+ {
+ display: block;
+ }
+
+ span.thumb
+ {
+ width: 83px;
+ height: 120px;
+ float: left;
+ background-color: white;
+ margin-right: 16px;
+
+ img
+ {
+ height: 120px;
+ }
+ }
+
+ .name
+ {
+ font-weight: bold;
+ font-size: 24px;
+ }
+
+ .date
+ {
+ font-size: 20px;
+ }
+
+ .views
+ {
+ font-size: 18px;
+ }
+ }
+ }
+
+ .donation-page
+ {
+ .js-available
+ {
+ display: none;
+ }
+
+ .bip21-qr
+ {
+ float: right;
+ margin: -25px 12px 12px 12px;
+ }
+
+ section:after
+ {
+ content: "";
+ display: table;
+ clear: both;
+ }
+
+ section.instructions
+ {
+ min-height: 200px;
+ }
+
+ .option
+ {
+ float: left;
+ padding: 14px;
+ border: 1px solid gray;
+ margin: 5px;
+ width: 190px;
+ height: 60px;
+ border-radius: 6px;
+ text-align: center;
+ font-size: 20px;
+ position: relative;
+ background-color: #0D0D0D;
+
+ &.payment-method
+ {
+ height: 75px;
+
+ label
+ {
+ font-size: 14px;
+
+ &.fixed
+ {
+ position: absolute;
+ bottom: 21px;
+ left: 0px;
+ right: 0px;
+ text-align: center;
+ }
+
+ &.logo
+ {
+ font-size: 33px;
+ font-weight: bold;
+ color: #efefef;
+ }
+ }
+ }
+
+ &.selected
+ {
+ background-color: #003D35;
+ }
+
+ label, input
+ {
+ display: block;
+ margin: 0px auto;
+ }
+
+ label
+ {
+ margin-bottom: 9px;
+ }
+
+ input[type="radio"]
+ {
+ position: absolute;
+ bottom: 12px;
+ left: 50%;
+ margin-left: -6px;
+ }
+
+ .exchange-rate
+ {
+ font-size: 15px;
+ }
+
+ #custom_amount_input
+ {
+ width: 72px;
+ display: inline;
+ margin-left: 5px;
+ background-color: rgb(32, 32, 32);
+ border: 1px solid rgb(71, 71, 71);
+ color: white;
+ padding: 4px 4px 3px 4px;
+ font-size: 19px;
+ }
+ }
+
+ .paypal-button
+ {
+ text-align: center;
+ padding: 24px;
+ background-color: #0D0D0D;
+
+ &:hover
+ {
+ background-color: black;
+ }
+ }
+ }
+
+ .blog-post, .blog-index
+ {
+ h2
+ {
+ font-size: 29px;
+ }
+ }
+
+ .blog-index
+ {
+ .post
+ {
+ margin-bottom: 9px;
+ }
+
+ .date
+ {
+ font-family: monospace;
+ margin-right: 16px;
+ color: rgb(219, 219, 219);
+ font-size: 15px;
+ }
+
+ .title
+ {
+ margin-left: 8px;
+ }
+ }
+
+ .blog-post
+ {
+ section
+ {
+ padding: 1px 24px;
+ }
+
+ a.anchor
+ {
+ float: left;
+ margin-left: -16px;
+ margin-top: 4px;
+ font-weight: normal;
+ font-size: 80%;
+ }
+
+ h3
+ {
+ font-size: 24px;
+ }
+
+ h4
+ {
+ font-size: 19px;
+ }
+ }
+
+ .admin
+ {
+ position: relative;
+
+ th, td
+ {
+ padding: 4px 7px;
+ }
+
+ th
+ {
+ text-align: left;
+ }
+
+ td
+ {
+ border-top: 1px solid gray;
+ }
+
+ .save-button
+ {
+ margin-right: 8px;
+ }
+
+ form.pure-g
+ {
+ [class *= "pure-u"]
+ {
+ box-sizing: border-box;
+ padding: 5px;
+ }
+
+ label
+ {
+ font-weight: bold;
+ font-size: 18px;
+ margin-bottom: 8px;
+ }
+
+ .md-editor
+ {
+ font-family: monospace;
+ }
+
+ .md-preview
+ {
+ border: 1px solid #575757;
+ padding: 8px 16px;
+ border-radius: 5px;
+ }
+
+ .submit
+ {
+ position: absolute;
+ right: 0px;
+ margin-top: -6px;
+ }
+ }
+ }
+
+ .error
+ {
+ h2
+ {
+ font-size: 22px;
+ }
+ }
+
+ .pure-button img.icon
+ {
+ position: relative;
+ top: 1px;
+ margin-right: 6px;
+ }
+}
+
+.footer
+{
+ margin-top: 32px;
+ padding-top: 12px;
+ border-top: 1px solid #DEDEDE;
+ font-size: 14px;
+ color: #DEDEDE;
+}
+
+#drag_ghost
+{
+ display: none;
+ position: absolute;
+ z-index: 999;
+ padding: 8px;
+ background-color: #1D3030;
+ border: 1px solid #0B7474;
+ box-shadow: 3px 3px 8px 0px #131314;
+ color: white;
+ font-size: 19px;
+ font-weight: bold;
+ border-radius: 5px;
+}
+
+@media (max-width: 570px)
+{
+ .button-lite.hide-x-small
+ {
+ display: none;
+ }
+}
+
+@media (max-width: 640px)
+{
+ .bottombar .embed
+ {
+ display: none;
+ }
+}
+
+@media (max-width: 660px)
+{
+ .alt-small
+ {
+ display: block;
+ }
+
+ .alt-large
+ {
+ display: none;
+ }
+}
+
+@media (min-width: 650px)
+{
+ .alt-small
+ {
+ display: none;
+ }
+
+ .alt-large
+ {
+ display: block;
+ }
+}
+
+@media (max-width: 800px)
+{
+ .dragdrop-instructions
+ {
+ display: block;
+ }
+}
+
+@media (max-width: 1000px)
+{
+ .hide-small
+ {
+ display: none;
+ }
+}
+
+@media (max-width: 1200px)
+{
+ body
+ {
+ .viewer-contents
+ {
+ .sidebar
+ {
+ display: none;
+ }
+
+ .viewer-wrapper
+ {
+ right: 0px;
+ bottom: 64px;
+ }
+
+ .bottombar
+ {
+ display: block;
+ }
+ }
+ }
+}
+
+@media (max-height: 940px)
+{
+ body
+ {
+ .header
+ {
+ padding: 10px 18px;
+ }
+
+ .viewer-contents
+ {
+ .viewer-wrapper, .sidebar
+ {
+ top: 62px;
+ }
+
+ .sidebar
+ {
+ h2
+ {
+ margin: 0px;
+ font-size: 20px;
+ }
+
+ h3
+ {
+ font-size: 18px;
+ margin: 0px;
+ }
+
+ p
+ {
+ margin: 1px 0px;
+ }
+
+ .embed-box, .link-box, .download-box
+ {
+ margin: 16px 0px;
+ padding: 8px 16px 10px 16px;
+ }
+
+ .donation-box
+ {
+ padding-top: 8px;
+ }
+
+ textarea.embed_code
+ {
+ height: 32px;
+ }
+
+ .stats
+ {
+ margin-top: 4px;
+ padding: 0px 8px;
+ }
+ }
+ }
+
+ &.announcement-visible .viewer-contents
+ {
+ .viewer-wrapper, .sidebar
+ {
+ top: 101px;
+ }
+ }
+ }
+}
+
+.clear
+{
+ clear: both;
+}
+
+.notice
+{
+ background-color: #086458;
+ color: white;
+ padding: 8px 14px;
+ margin-bottom: 32px;
+ border-radius: 4px;
+
+ a
+ {
+ color: white;
+ }
+
+ p
+ {
+ margin: 8px 0px;
+ }
+}
+
+.announce
+{
+ background-color: #0A8071;
+ color: white;
+ text-align: center;
+ padding: 12px 16px;
+ font-size: 18px;
+
+ a
+ {
+ color: white;
+ }
+}
+
+.full-screen
+{
+ .announce
+ {
+ height: 21px;
+ overflow: hidden;
+ font-size: 17px;
+ padding: 9px 16px;
+ }
+}
+
+// Scrollbar styles
+
+::-webkit-scrollbar
+{
+ background-color: white;
+}
+
+::-webkit-scrollbar-track
+{
+ background-color: #5A5A61;
+}
+
+::-webkit-scrollbar-thumb
+{
+ background-color: #171717;
+}
+
+::-webkit-scrollbar-track:vertical
+{
+ border-left: 1px solid #323235;
+}
+
+::-webkit-scrollbar-thumb-vertical
+{
+ border-left: 1px solid #323235;
+}
+
+::-webkit-scrollbar-track:horizontal
+{
+ border-top: 1px solid #323235;
+}
+
+::-webkit-scrollbar-thumb-horizontal
+{
+ border-top: 1px solid #323235;
+}
+
+::-webkit-scrollbar-button
+{
+ background-color: #1C1C1C;
+ color: white;
+ background-position: 0px -1px;
+}
+
+::-webkit-scrollbar-button:vertical:increment
+{
+ background-image: url(/static/pdfjs/images/arrow-down.png);
+ border-top: 1px solid black;
+}
+
+::-webkit-scrollbar-button:vertical:decrement
+{
+ background-image: url(/static/pdfjs/images/arrow-up.png);
+ border-bottom: 1px solid black;
+}
+
+::-webkit-scrollbar-button:horizontal:increment
+{
+ background-image: url(/static/pdfjs/images/arrow-right.png);
+ border-left: 1px solid black;
+}
+
+::-webkit-scrollbar-button:horizontal:decrement
+{
+ background-image: url(/static/pdfjs/images/arrow-left.png);
+ border-right: 1px solid black;
+}
diff --git a/public/images/ia.png b/public/images/ia.png
new file mode 100644
index 0000000..9b3cebc
Binary files /dev/null and b/public/images/ia.png differ
diff --git a/public/images/logos/bitcoin.png b/public/images/logos/bitcoin.png
new file mode 100644
index 0000000..604fbdf
Binary files /dev/null and b/public/images/logos/bitcoin.png differ
diff --git a/public/images/logos/flattr.png b/public/images/logos/flattr.png
new file mode 100644
index 0000000..1b8af4e
Binary files /dev/null and b/public/images/logos/flattr.png differ
diff --git a/public/images/logos/gratipay.png b/public/images/logos/gratipay.png
new file mode 100644
index 0000000..c70ad04
Binary files /dev/null and b/public/images/logos/gratipay.png differ
diff --git a/public/images/logos/paypal.png b/public/images/logos/paypal.png
new file mode 100644
index 0000000..5106121
Binary files /dev/null and b/public/images/logos/paypal.png differ
diff --git a/public/images/logos/sepa.png b/public/images/logos/sepa.png
new file mode 100644
index 0000000..70a8f91
Binary files /dev/null and b/public/images/logos/sepa.png differ
diff --git a/public/images/no-thumbnail.png b/public/images/no-thumbnail.png
new file mode 100644
index 0000000..563018a
Binary files /dev/null and b/public/images/no-thumbnail.png differ
diff --git a/public/js/bundle.js b/public/js/bundle.js
new file mode 100644
index 0000000..b2416a7
--- /dev/null
+++ b/public/js/bundle.js
@@ -0,0 +1,13418 @@
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+
+
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var $, autosize, marked, scrollFloat;
+
+ __webpack_require__(1);
+
+ __webpack_require__(2);
+
+ __webpack_require__(3);
+
+ __webpack_require__(4);
+
+ $ = __webpack_require__(6);
+
+ autosize = __webpack_require__(8);
+
+ marked = __webpack_require__(7);
+
+ scrollFloat = __webpack_require__(5);
+
+ $(function() {
+ var updatePreview;
+ $(".checkAll").on("change", function(event) {
+ var newValue;
+ newValue = $(this).prop("checked");
+ return $(this).closest("table").find("input[type='checkbox']").filter(function() {
+ return !$(this).hasClass("checkAll");
+ }).prop("checked", newValue);
+ });
+ scrollFloat($(".floating"));
+ autosize($(".md-editor"));
+ updatePreview = function() {
+ return $(".md-preview").html(marked($(this).val()));
+ };
+ return $(".md-editor").on("change input propertychange", updatePreview).each(updatePreview);
+ });
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var $, data_object, filePicked, prettyUnits, reinitializeUploader, triggerError, updateUploadProgress, uploadDone;
+
+ $ = __webpack_require__(6);
+
+ prettyUnits = __webpack_require__(9);
+
+ __webpack_require__(10);
+
+ data_object = null;
+
+ uploadDone = function(response) {
+ var errorHeader, errorMessage;
+ switch (response.status) {
+ case 415:
+ errorHeader = "Oops! That's not a PDF file.";
+ errorMessage = "The file you tried to upload is not a valid PDF file. Currently, only PDF files are accepted.";
+ break;
+ case 413:
+ errorHeader = "Oops! That file is too big.";
+ errorMessage = "The file you tried to upload is too big. Currently, you can only upload PDF files up to 150MB in size.";
+ break;
+ case 200:
+ break;
+ default:
+ errorHeader = "Oops! Something went wrong.";
+ errorMessage = "An unknown error occurred. Please reload the page and try again. If the error keeps occurring, send us an e-mail!";
+ }
+ if (errorMessage != null) {
+ triggerError(errorHeader, errorMessage);
+ return reinitializeUploader();
+ } else {
+ if (response.responseJSON.redirect != null) {
+ return window.location = response.responseJSON.redirect;
+ } else {
+
+ }
+ }
+ };
+
+ triggerError = function(header, message) {
+ var errorBox;
+ $(".upload-form .privacySettings, .upload-form .progress, .upload-form .button-submit").hide();
+ $(".upload").removeClass("faded");
+ errorBox = $("#uploadError").show();
+ errorBox.find("h3").html(header);
+ errorBox.find(".message").html(message);
+ return data_object = null;
+ };
+
+ reinitializeUploader = function() {
+ return $("#upload_element").replaceWith($("#upload_element").clone(true));
+ };
+
+ filePicked = function(data) {
+ var fileinfo, filesize, filesize_text;
+ $(".upload-form .privacySettings, .upload-form .button-submit").show();
+ $("#uploadError").hide();
+ $(".upload-form .fileinfo").removeClass("faded");
+ fileinfo = $(".fileinfo");
+ filesize = data.files[0].size;
+ if (filesize > (150 * 1024 * 1024)) {
+ reinitializeUploader();
+ triggerError("Oops! That file is too big.", "The file you tried to upload is too big. Currently, you can only upload PDF files up to 150MB in size.");
+ return;
+ }
+ filesize_text = prettyUnits(filesize) + "B";
+ fileinfo.find(".filename").text(data.files[0].name);
+ fileinfo.find(".filesize").text(filesize_text);
+ $(".info").hide();
+ fileinfo.show();
+ return $(".upload").addClass("faded");
+ };
+
+ updateUploadProgress = function(event) {
+ var done_text, percentage, progress, total_text;
+ if (event.lengthComputable) {
+ percentage = event.loaded / event.total * 100;
+ done_text = prettyUnits(event.loaded) + "B";
+ total_text = prettyUnits(event.total) + "B";
+ progress = $(".progress");
+ progress.find(".done").text(done_text);
+ progress.find(".total").text(total_text);
+ progress.find(".percentage").text(Math.ceil(percentage * 100) / 100);
+ progress.find(".bar-inner").css({
+ width: percentage + "%"
+ });
+ if (event.loaded >= event.total) {
+ progress.find(".numbers").hide();
+ return progress.find(".wait").show();
+ }
+ }
+ };
+
+ $(function() {
+ if ($().fileupload != null) {
+ $("#upload_form").fileupload({
+ fileInput: null,
+ type: "POST",
+ url: "/upload",
+ paramName: "file",
+ autoUpload: false,
+ maxNumberOfFiles: 1,
+ formData: function(form) {
+ form = $("#upload_form");
+ return form.serializeArray();
+ },
+ progressall: function(e, data) {
+ return updateUploadProgress({
+ lengthComputable: true,
+ loaded: data.loaded,
+ total: data.total
+ });
+ },
+ add: function(e, data) {
+ data_object = data;
+ return filePicked(data);
+ },
+ always: function(e, data) {
+ return uploadDone(data.jqXHR);
+ }
+ });
+ }
+ $("#upload_activator").on("click", function(event) {
+ return $("#upload_element").click();
+ });
+ $("#upload_element").on("change", function(event) {
+ return filePicked(this);
+ });
+ return $("#upload_form").on("submit", function(event) {
+ var formData;
+ event.stopPropagation();
+ event.preventDefault();
+ $(".fileinfo").addClass("faded");
+ $(".progress").show();
+ if (data_object === null) {
+ formData = new FormData(this);
+ return $.ajax({
+ method: "POST",
+ url: "/upload",
+ data: formData,
+ cache: false,
+ contentType: false,
+ processData: false,
+ xhr: function() {
+ var customHandler;
+ customHandler = $.ajaxSettings.xhr();
+ if (customHandler.upload != null) {
+ customHandler.upload.addEventListener("progress", updateUploadProgress, false);
+ }
+ return customHandler;
+ },
+ complete: function(result) {
+ return uploadDone(result);
+ }
+ });
+ } else {
+ return data_object.submit();
+ }
+ });
+ });
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var $, castBoolean, updateEmbedCode;
+
+ $ = __webpack_require__(6);
+
+ castBoolean = function(value) {
+ if (value === true) {
+ return 1;
+ } else {
+ return 0;
+ }
+ };
+
+ updateEmbedCode = function() {
+ var embedCode, showDonationLink, showToolbar;
+ showToolbar = $("#show_toolbar").prop("checked");
+ showDonationLink = $("#show_donation").prop("checked");
+ embedCode = embed_template.replace("{SPARSE}", castBoolean(!showToolbar)).replace("{DONATION}", castBoolean(showDonationLink));
+ return $(".embed_code").val(embedCode);
+ };
+
+ $(function() {
+ $(".autoselect").on("click", function(event) {
+ return $(this).focus().select();
+ });
+ $("#show_toolbar, #show_donation").on("change", function(event) {
+ return updateEmbedCode();
+ });
+ if (typeof embed_template !== "undefined" && embed_template !== null) {
+ return updateEmbedCode();
+ }
+ });
+
+
+/***/ },
+/* 3 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var $;
+
+ $ = __webpack_require__(6);
+
+ __webpack_require__(11);
+
+ $(function() {
+ var paymentMethodHandlers, paypalHandler, pulsateRemove, selectedAmount, selectedHasPrice, selectedMethod, setCustomValue, showInstructions;
+ selectedMethod = void 0;
+ selectedAmount = void 0;
+ selectedHasPrice = void 0;
+ pulsateRemove = void 0;
+ paypalHandler = function(block) {
+ return block.find("input.amount").val(Math.round(selectedAmount * 100) / 100);
+ };
+ paymentMethodHandlers = {
+ paypal: paypalHandler,
+ "paypal-weekly": paypalHandler,
+ "paypal-monthly": paypalHandler,
+ bitcoin: function(block) {
+ block.find(".loading-message").show();
+ block.find(".loaded-content").hide();
+ return $.get("/donate/convert/btc?amount=" + selectedAmount, function(btcAmount) {
+ $.get("/donate/bip21?amount=" + btcAmount, function(uri) {
+ $(".bip21").attr("href", uri);
+ block.find(".loading-message").hide();
+ return block.find(".loaded-content").show();
+ });
+ $(".bip21-qr").attr("src", "/donate/bip21/qr?amount=" + btcAmount);
+ return $(".btc-amount").text(Math.round(btcAmount * 100000000) / 100000000);
+ });
+ }
+ };
+ setCustomValue = function(value) {
+ value = parseFloat(value);
+ if (isNaN(value)) {
+ value = 0;
+ }
+ return selectedAmount = value;
+ };
+ $("#custom_amount_input").on("keyup", function(event) {
+ setCustomValue($(this).val());
+ return showInstructions();
+ }).on("change", function(event) {
+ setCustomValue($(this).val());
+ return showInstructions();
+ }).on("click", function(event) {});
+ $("#amount_custom").on("click", function(event) {
+ return setCustomValue($("#custom_amount_input").val());
+ });
+ $(".donation-page section.types .option input[type='radio']").on("click", function(event) {
+ var type;
+ $(".donation-page section.instructions div").hide();
+ $(".donation-page section.instructions .placeholder").show();
+ type = $(this).closest(".option").data("type");
+ $(".donation-page section.methods .option").hide();
+ return $(".donation-page section.methods .option[data-type='" + type + "']").show();
+ });
+ $(".donation-page section.methods .option input[type='radio']").on("click", function(event) {
+ var instructionSection, method, optionElement, setPrice;
+ optionElement = $(this).closest(".option");
+ selectedMethod = method = optionElement.data("name");
+ selectedHasPrice = setPrice = !!(optionElement.data("set-price"));
+ if (setPrice) {
+ $(".donation-page section.amount").slideDown(400);
+ instructionSection = $(".donation-page section.instructions");
+ instructionSection.children("h3.set-amount").show();
+ instructionSection.children("h3.no-set-amount").hide();
+ } else {
+ $(".donation-page section.amount").slideUp(400);
+ instructionSection = $(".donation-page section.instructions");
+ instructionSection.children("h3.set-amount").hide();
+ instructionSection.children("h3.no-set-amount").show();
+ }
+ return showInstructions();
+ });
+ $(".donation-page section.amount .option input[type='radio']").on("click", function(event) {
+ if (!($(this).attr("id") === "amount_custom")) {
+ selectedAmount = $(this).val();
+ }
+ return showInstructions();
+ });
+ showInstructions = function() {
+ $(".donation-page section.instructions .method").hide();
+ if ((selectedMethod != null) && ((selectedAmount != null) || !selectedHasPrice)) {
+ $(".donation-page section.instructions .placeholder").hide();
+ $(".donation-page section.instructions .method-" + selectedMethod).show();
+ $(".donation-page section.instructions").addClass("pulsate");
+ if (pulsateRemove != null) {
+ clearTimeout(pulsateRemove);
+ }
+ pulsateRemove = setTimeout((function() {
+ return $(".donation-page section.instructions").removeClass("pulsate");
+ }), 3000);
+ if (selectedMethod in paymentMethodHandlers) {
+ return paymentMethodHandlers[selectedMethod]($(".donation-page section.instructions .method-" + selectedMethod));
+ }
+ } else {
+ return $(".donation-page section.instructions .placeholder").show();
+ }
+ };
+ $(".donation-page .option input[type='radio']").on("click", function(event) {
+ $(this).closest("section").find(".option").removeClass("selected");
+ $(this).closest(".option").addClass("selected");
+ $(this).closest(".option").find("input[type='number']").focus();
+ return event.stopPropagation();
+ });
+ $(".donation-page .option").on("click", function(event) {
+ return $(this).find("input[type='radio']").click();
+ });
+ $(".donation-page section.types .option[data-type='once']").click();
+ $(".donation-page #amount_10").click();
+ $(".donation-page section.instructions .method, .donation-page section.instructions h3.no-set-amount").hide();
+ $(".js-unavailable").hide();
+ $(".js-available").show();
+ return $(".decodable").each(function() {
+ return $(this).html(atob($(this).html()));
+ });
+ });
+
+
+/***/ },
+/* 4 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var $;
+
+ $ = __webpack_require__(6);
+
+ $(function() {
+ $(".popup").hide();
+ return $(".form-popup").each(function() {
+ var elem, target;
+ elem = $(this);
+ target = ".popup-" + (elem.data('popup'));
+ elem.on("click", function() {
+ return $(target).show();
+ });
+ return $(".popup .close").on("click", function() {
+ return $(this).closest(".popup").hide();
+ });
+ });
+ });
+
+
+/***/ },
+/* 5 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var $;
+
+ $ = __webpack_require__(6);
+
+ module.exports = function(jqueryObj) {
+ return jqueryObj.each(function() {
+ var basePos, currentlyFloating, elem, makeFloating, makeNotFloating, minTop, originalLeft, originalPositioning, originalTop, threshold, updateMetrics;
+ elem = $(this);
+ minTop = threshold = basePos = void 0;
+ currentlyFloating = false;
+ originalPositioning = elem.css("position");
+ originalLeft = elem.css("left");
+ originalTop = elem.css("top");
+ originalLeft = elem.css("right");
+ updateMetrics = function() {
+ var needRestore, ref;
+ needRestore = currentlyFloating;
+ if (needRestore) {
+ makeNotFloating();
+ }
+ basePos = elem.offset();
+ minTop = (ref = elem.data("min-top")) != null ? ref : 0;
+ threshold = basePos.top - minTop;
+ if (needRestore) {
+ return makeFloating();
+ }
+ };
+ makeFloating = function() {
+ currentlyFloating = true;
+ return elem.css({
+ position: "fixed",
+ top: minTop + "px",
+ right: "auto",
+ left: basePos.left + "px"
+ });
+ };
+ makeNotFloating = function() {
+ currentlyFloating = false;
+ return elem.attr("style", "");
+ };
+ updateMetrics();
+ $(window).on("resize", updateMetrics);
+ return $(document).on("scroll", function(event) {
+ if ($(document).scrollTop() >= threshold) {
+ return makeFloating();
+ } else {
+ return makeNotFloating();
+ }
+ });
+ });
+ };
+
+
+/***/ },
+/* 6 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
+ * jQuery JavaScript Library v2.1.3
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2014-12-18T15:11Z
+ */
+
+ (function( global, factory ) {
+
+ if ( typeof module === "object" && typeof module.exports === "object" ) {
+ // For CommonJS and CommonJS-like environments where a proper `window`
+ // is present, execute the factory and get jQuery.
+ // For environments that do not have a `window` with a `document`
+ // (such as Node.js), expose a factory as module.exports.
+ // This accentuates the need for the creation of a real `window`.
+ // e.g. var jQuery = require("jquery")(window);
+ // See ticket #14549 for more info.
+ module.exports = global.document ?
+ factory( global, true ) :
+ function( w ) {
+ if ( !w.document ) {
+ throw new Error( "jQuery requires a window with a document" );
+ }
+ return factory( w );
+ };
+ } else {
+ factory( global );
+ }
+
+ // Pass this if window is not defined yet
+ }(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+ // Support: Firefox 18+
+ // Can't be in strict mode, several libs including ASP.NET trace
+ // the stack via arguments.caller.callee and Firefox dies if
+ // you try to trace through "use strict" call chains. (#13335)
+ //
+
+ var arr = [];
+
+ var slice = arr.slice;
+
+ var concat = arr.concat;
+
+ var push = arr.push;
+
+ var indexOf = arr.indexOf;
+
+ var class2type = {};
+
+ var toString = class2type.toString;
+
+ var hasOwn = class2type.hasOwnProperty;
+
+ var support = {};
+
+
+
+ var
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
+
+ version = "2.1.3",
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ // Need init if jQuery is called (just allow error to be thrown if not included)
+ return new jQuery.fn.init( selector, context );
+ },
+
+ // Support: Android<4.1
+ // Make sure we trim BOM and NBSP
+ rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return letter.toUpperCase();
+ };
+
+ jQuery.fn = jQuery.prototype = {
+ // The current version of jQuery being used
+ jquery: version,
+
+ constructor: jQuery,
+
+ // Start with an empty selector
+ selector: "",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ toArray: function() {
+ return slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num != null ?
+
+ // Return just the one element from the set
+ ( num < 0 ? this[ num + this.length ] : this[ num ] ) :
+
+ // Return all the elements in a clean array
+ slice.call( this );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+ ret.context = this.context;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ) );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ eq: function( i ) {
+ var len = this.length,
+ j = +i + ( i < 0 ? len : 0 );
+ return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: arr.sort,
+ splice: arr.splice
+ };
+
+ jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+
+ // Skip the boolean and the target
+ target = arguments[ i ] || {};
+ i++;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // Extend jQuery itself if only one argument is passed
+ if ( i === length ) {
+ target = this;
+ i--;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+ };
+
+ jQuery.extend({
+ // Unique for each copy of jQuery on the page
+ expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
+
+ // Assume jQuery is ready without the ready module
+ isReady: true,
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ noop: function() {},
+
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray,
+
+ isWindow: function( obj ) {
+ return obj != null && obj === obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ // parseFloat NaNs numeric-cast false positives (null|true|false|"")
+ // ...but misinterprets leading-number strings, particularly hex literals ("0x...")
+ // subtraction forces infinities to NaN
+ // adding 1 corrects loss of precision from parseFloat (#15100)
+ return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0;
+ },
+
+ isPlainObject: function( obj ) {
+ // Not plain objects:
+ // - Any object or value whose internal [[Class]] property is not "[object Object]"
+ // - DOM nodes
+ // - window
+ if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ if ( obj.constructor &&
+ !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
+ return false;
+ }
+
+ // If the function hasn't returned already, we're confident that
+ // |obj| is a plain object, created by {} or constructed with new Object
+ return true;
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ type: function( obj ) {
+ if ( obj == null ) {
+ return obj + "";
+ }
+ // Support: Android<4.0, iOS<6 (functionish RegExp)
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ toString.call(obj) ] || "object" :
+ typeof obj;
+ },
+
+ // Evaluates a script in a global context
+ globalEval: function( code ) {
+ var script,
+ indirect = eval;
+
+ code = jQuery.trim( code );
+
+ if ( code ) {
+ // If the code includes a valid, prologue position
+ // strict mode pragma, execute code by injecting a
+ // script tag into the document.
+ if ( code.indexOf("use strict") === 1 ) {
+ script = document.createElement("script");
+ script.text = code;
+ document.head.appendChild( script ).parentNode.removeChild( script );
+ } else {
+ // Otherwise, avoid the DOM node creation, insertion
+ // and removal by using an indirect global eval
+ indirect( code );
+ }
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Support: IE9-11+
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+ },
+
+ // args is for internal usage only
+ each: function( obj, callback, args ) {
+ var value,
+ i = 0,
+ length = obj.length,
+ isArray = isArraylike( obj );
+
+ if ( args ) {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // Support: Android<4.1
+ trim: function( text ) {
+ return text == null ?
+ "" :
+ ( text + "" ).replace( rtrim, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var ret = results || [];
+
+ if ( arr != null ) {
+ if ( isArraylike( Object(arr) ) ) {
+ jQuery.merge( ret,
+ typeof arr === "string" ?
+ [ arr ] : arr
+ );
+ } else {
+ push.call( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ return arr == null ? -1 : indexOf.call( arr, elem, i );
+ },
+
+ merge: function( first, second ) {
+ var len = +second.length,
+ j = 0,
+ i = first.length;
+
+ for ( ; j < len; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, invert ) {
+ var callbackInverse,
+ matches = [],
+ i = 0,
+ length = elems.length,
+ callbackExpect = !invert;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ callbackInverse = !callback( elems[ i ], i );
+ if ( callbackInverse !== callbackExpect ) {
+ matches.push( elems[ i ] );
+ }
+ }
+
+ return matches;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value,
+ i = 0,
+ length = elems.length,
+ isArray = isArraylike( elems ),
+ ret = [];
+
+ // Go through the array, translating each of the items to their new values
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( i in elems ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var tmp, args, proxy;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ now: Date.now,
+
+ // jQuery.support is not used in Core but other projects attach their
+ // properties to it so it needs to exist.
+ support: support
+ });
+
+ // Populate the class2type map
+ jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+ });
+
+ function isArraylike( obj ) {
+ var length = obj.length,
+ type = jQuery.type( obj );
+
+ if ( type === "function" || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ if ( obj.nodeType === 1 && length ) {
+ return true;
+ }
+
+ return type === "array" || length === 0 ||
+ typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+ }
+ var Sizzle =
+ /*!
+ * Sizzle CSS Selector Engine v2.2.0-pre
+ * http://sizzlejs.com/
+ *
+ * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2014-12-16
+ */
+ (function( window ) {
+
+ var i,
+ support,
+ Expr,
+ getText,
+ isXML,
+ tokenize,
+ compile,
+ select,
+ outermostContext,
+ sortInput,
+ hasDuplicate,
+
+ // Local document vars
+ setDocument,
+ document,
+ docElem,
+ documentIsHTML,
+ rbuggyQSA,
+ rbuggyMatches,
+ matches,
+ contains,
+
+ // Instance-specific data
+ expando = "sizzle" + 1 * new Date(),
+ preferredDoc = window.document,
+ dirruns = 0,
+ done = 0,
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ },
+
+ // General-purpose constants
+ MAX_NEGATIVE = 1 << 31,
+
+ // Instance methods
+ hasOwn = ({}).hasOwnProperty,
+ arr = [],
+ pop = arr.pop,
+ push_native = arr.push,
+ push = arr.push,
+ slice = arr.slice,
+ // Use a stripped-down indexOf as it's faster than native
+ // http://jsperf.com/thor-indexof-vs-for/5
+ indexOf = function( list, elem ) {
+ var i = 0,
+ len = list.length;
+ for ( ; i < len; i++ ) {
+ if ( list[i] === elem ) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
+
+ // Regular expressions
+
+ // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+ // http://www.w3.org/TR/css3-syntax/#characters
+ characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
+
+ // Loosely modeled on CSS identifier characters
+ // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
+ // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ identifier = characterEncoding.replace( "w", "w#" ),
+
+ // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
+ attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace +
+ // Operator (capture 2)
+ "*([*^$|!~]?=)" + whitespace +
+ // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
+ "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
+ "*\\]",
+
+ pseudos = ":(" + characterEncoding + ")(?:\\((" +
+ // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
+ // 1. quoted (capture 3; capture 4 or capture 5)
+ "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
+ // 2. simple (capture 6)
+ "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
+ // 3. anything else (capture 2)
+ ".*" +
+ ")\\)|)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rwhitespace = new RegExp( whitespace + "+", "g" ),
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
+
+ rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
+
+ rpseudo = new RegExp( pseudos ),
+ ridentifier = new RegExp( "^" + identifier + "$" ),
+
+ matchExpr = {
+ "ID": new RegExp( "^#(" + characterEncoding + ")" ),
+ "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+ "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
+ "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+ "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ "bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+ // For use in libraries implementing .is()
+ // We use this for POS matching in `select`
+ "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
+ whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+ },
+
+ rinputs = /^(?:input|select|textarea|button)$/i,
+ rheader = /^h\d$/i,
+
+ rnative = /^[^{]+\{\s*\[native \w/,
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+ rsibling = /[+~]/,
+ rescape = /'|\\/g,
+
+ // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+ runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
+ funescape = function( _, escaped, escapedWhitespace ) {
+ var high = "0x" + escaped - 0x10000;
+ // NaN means non-codepoint
+ // Support: Firefox<24
+ // Workaround erroneous numeric interpretation of +"0x"
+ return high !== high || escapedWhitespace ?
+ escaped :
+ high < 0 ?
+ // BMP codepoint
+ String.fromCharCode( high + 0x10000 ) :
+ // Supplemental Plane codepoint (surrogate pair)
+ String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+ },
+
+ // Used for iframes
+ // See setDocument()
+ // Removing the function wrapper causes a "Permission Denied"
+ // error in IE
+ unloadHandler = function() {
+ setDocument();
+ };
+
+ // Optimize for push.apply( _, NodeList )
+ try {
+ push.apply(
+ (arr = slice.call( preferredDoc.childNodes )),
+ preferredDoc.childNodes
+ );
+ // Support: Android<4.0
+ // Detect silently failing push.apply
+ arr[ preferredDoc.childNodes.length ].nodeType;
+ } catch ( e ) {
+ push = { apply: arr.length ?
+
+ // Leverage slice if possible
+ function( target, els ) {
+ push_native.apply( target, slice.call(els) );
+ } :
+
+ // Support: IE<9
+ // Otherwise append directly
+ function( target, els ) {
+ var j = target.length,
+ i = 0;
+ // Can't trust NodeList.length
+ while ( (target[j++] = els[i++]) ) {}
+ target.length = j - 1;
+ }
+ };
+ }
+
+ function Sizzle( selector, context, results, seed ) {
+ var match, elem, m, nodeType,
+ // QSA vars
+ i, groups, old, nid, newContext, newSelector;
+
+ if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+ setDocument( context );
+ }
+
+ context = context || document;
+ results = results || [];
+ nodeType = context.nodeType;
+
+ if ( typeof selector !== "string" || !selector ||
+ nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
+
+ return results;
+ }
+
+ if ( !seed && documentIsHTML ) {
+
+ // Try to shortcut find operations when possible (e.g., not under DocumentFragment)
+ if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
+ // Speed-up: Sizzle("#ID")
+ if ( (m = match[1]) ) {
+ if ( nodeType === 9 ) {
+ elem = context.getElementById( m );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document (jQuery #6963)
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE, Opera, and Webkit return items
+ // by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+ } else {
+ // Context is not a document
+ if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+ contains( context, elem ) && elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ }
+
+ // Speed-up: Sizzle("TAG")
+ } else if ( match[2] ) {
+ push.apply( results, context.getElementsByTagName( selector ) );
+ return results;
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( (m = match[3]) && support.getElementsByClassName ) {
+ push.apply( results, context.getElementsByClassName( m ) );
+ return results;
+ }
+ }
+
+ // QSA path
+ if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+ nid = old = expando;
+ newContext = context;
+ newSelector = nodeType !== 1 && selector;
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ groups = tokenize( selector );
+
+ if ( (old = context.getAttribute("id")) ) {
+ nid = old.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", nid );
+ }
+ nid = "[id='" + nid + "'] ";
+
+ i = groups.length;
+ while ( i-- ) {
+ groups[i] = nid + toSelector( groups[i] );
+ }
+ newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
+ newSelector = groups.join(",");
+ }
+
+ if ( newSelector ) {
+ try {
+ push.apply( results,
+ newContext.querySelectorAll( newSelector )
+ );
+ return results;
+ } catch(qsaError) {
+ } finally {
+ if ( !old ) {
+ context.removeAttribute("id");
+ }
+ }
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed );
+ }
+
+ /**
+ * Create key-value caches of limited size
+ * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
+ * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ * deleting the oldest entry
+ */
+ function createCache() {
+ var keys = [];
+
+ function cache( key, value ) {
+ // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+ if ( keys.push( key + " " ) > Expr.cacheLength ) {
+ // Only keep the most recent entries
+ delete cache[ keys.shift() ];
+ }
+ return (cache[ key + " " ] = value);
+ }
+ return cache;
+ }
+
+ /**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
+ */
+ function markFunction( fn ) {
+ fn[ expando ] = true;
+ return fn;
+ }
+
+ /**
+ * Support testing using an element
+ * @param {Function} fn Passed the created div and expects a boolean result
+ */
+ function assert( fn ) {
+ var div = document.createElement("div");
+
+ try {
+ return !!fn( div );
+ } catch (e) {
+ return false;
+ } finally {
+ // Remove from its parent by default
+ if ( div.parentNode ) {
+ div.parentNode.removeChild( div );
+ }
+ // release memory in IE
+ div = null;
+ }
+ }
+
+ /**
+ * Adds the same handler for all of the specified attrs
+ * @param {String} attrs Pipe-separated list of attributes
+ * @param {Function} handler The method that will be applied
+ */
+ function addHandle( attrs, handler ) {
+ var arr = attrs.split("|"),
+ i = attrs.length;
+
+ while ( i-- ) {
+ Expr.attrHandle[ arr[i] ] = handler;
+ }
+ }
+
+ /**
+ * Checks document order of two siblings
+ * @param {Element} a
+ * @param {Element} b
+ * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+ */
+ function siblingCheck( a, b ) {
+ var cur = b && a,
+ diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+ ( ~b.sourceIndex || MAX_NEGATIVE ) -
+ ( ~a.sourceIndex || MAX_NEGATIVE );
+
+ // Use IE sourceIndex if available on both nodes
+ if ( diff ) {
+ return diff;
+ }
+
+ // Check if b follows a
+ if ( cur ) {
+ while ( (cur = cur.nextSibling) ) {
+ if ( cur === b ) {
+ return -1;
+ }
+ }
+ }
+
+ return a ? 1 : -1;
+ }
+
+ /**
+ * Returns a function to use in pseudos for input types
+ * @param {String} type
+ */
+ function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+ }
+
+ /**
+ * Returns a function to use in pseudos for buttons
+ * @param {String} type
+ */
+ function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && elem.type === type;
+ };
+ }
+
+ /**
+ * Returns a function to use in pseudos for positionals
+ * @param {Function} fn
+ */
+ function createPositionalPseudo( fn ) {
+ return markFunction(function( argument ) {
+ argument = +argument;
+ return markFunction(function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ (j = matchIndexes[i]) ] ) {
+ seed[j] = !(matches[j] = seed[j]);
+ }
+ }
+ });
+ });
+ }
+
+ /**
+ * Checks a node for validity as a Sizzle context
+ * @param {Element|Object=} context
+ * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+ */
+ function testContext( context ) {
+ return context && typeof context.getElementsByTagName !== "undefined" && context;
+ }
+
+ // Expose support vars for convenience
+ support = Sizzle.support = {};
+
+ /**
+ * Detects XML nodes
+ * @param {Element|Object} elem An element or a document
+ * @returns {Boolean} True iff elem is a non-HTML XML node
+ */
+ isXML = Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+ };
+
+ /**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [doc] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+ setDocument = Sizzle.setDocument = function( node ) {
+ var hasCompare, parent,
+ doc = node ? node.ownerDocument || node : preferredDoc;
+
+ // If no document and documentElement is available, return
+ if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
+ return document;
+ }
+
+ // Set our document
+ document = doc;
+ docElem = doc.documentElement;
+ parent = doc.defaultView;
+
+ // Support: IE>8
+ // If iframe document is assigned to "document" variable and if iframe has been reloaded,
+ // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
+ // IE6-8 do not support the defaultView property so parent will be undefined
+ if ( parent && parent !== parent.top ) {
+ // IE11 does not have attachEvent, so all must suffer
+ if ( parent.addEventListener ) {
+ parent.addEventListener( "unload", unloadHandler, false );
+ } else if ( parent.attachEvent ) {
+ parent.attachEvent( "onunload", unloadHandler );
+ }
+ }
+
+ /* Support tests
+ ---------------------------------------------------------------------- */
+ documentIsHTML = !isXML( doc );
+
+ /* Attributes
+ ---------------------------------------------------------------------- */
+
+ // Support: IE<8
+ // Verify that getAttribute really returns attributes and not properties
+ // (excepting IE8 booleans)
+ support.attributes = assert(function( div ) {
+ div.className = "i";
+ return !div.getAttribute("className");
+ });
+
+ /* getElement(s)By*
+ ---------------------------------------------------------------------- */
+
+ // Check if getElementsByTagName("*") returns only elements
+ support.getElementsByTagName = assert(function( div ) {
+ div.appendChild( doc.createComment("") );
+ return !div.getElementsByTagName("*").length;
+ });
+
+ // Support: IE<9
+ support.getElementsByClassName = rnative.test( doc.getElementsByClassName );
+
+ // Support: IE<10
+ // Check if getElementById returns elements by name
+ // The broken getElementById methods don't pick up programatically-set names,
+ // so use a roundabout getElementsByName test
+ support.getById = assert(function( div ) {
+ docElem.appendChild( div ).id = expando;
+ return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
+ });
+
+ // ID find and filter
+ if ( support.getById ) {
+ Expr.find["ID"] = function( id, context ) {
+ if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+ var m = context.getElementById( id );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [ m ] : [];
+ }
+ };
+ Expr.filter["ID"] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ return elem.getAttribute("id") === attrId;
+ };
+ };
+ } else {
+ // Support: IE6/7
+ // getElementById is not reliable as a find shortcut
+ delete Expr.find["ID"];
+
+ Expr.filter["ID"] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+ return node && node.value === attrId;
+ };
+ };
+ }
+
+ // Tag
+ Expr.find["TAG"] = support.getElementsByTagName ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( tag );
+
+ // DocumentFragment nodes don't have gEBTN
+ } else if ( support.qsa ) {
+ return context.querySelectorAll( tag );
+ }
+ } :
+
+ function( tag, context ) {
+ var elem,
+ tmp = [],
+ i = 0,
+ // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
+ results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ };
+
+ // Class
+ Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
+ if ( documentIsHTML ) {
+ return context.getElementsByClassName( className );
+ }
+ };
+
+ /* QSA/matchesSelector
+ ---------------------------------------------------------------------- */
+
+ // QSA and matchesSelector support
+
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ rbuggyMatches = [];
+
+ // qSa(:focus) reports false when true (Chrome 21)
+ // We allow this because of a bug in IE8/9 that throws an error
+ // whenever `document.activeElement` is accessed on an iframe
+ // So, we allow :focus to pass through QSA all the time to avoid the IE error
+ // See http://bugs.jquery.com/ticket/13378
+ rbuggyQSA = [];
+
+ if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert(function( div ) {
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explicitly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // http://bugs.jquery.com/ticket/12359
+ docElem.appendChild( div ).innerHTML = "" +
+ "";
+
+ // Support: IE8, Opera 11-12.16
+ // Nothing should be selected when empty strings follow ^= or $= or *=
+ // The test attribute must be unknown in Opera but "safe" for WinRT
+ // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
+ if ( div.querySelectorAll("[msallowcapture^='']").length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+ }
+
+ // Support: IE8
+ // Boolean attributes and "value" are not treated correctly
+ if ( !div.querySelectorAll("[selected]").length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+ }
+
+ // Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+
+ if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
+ rbuggyQSA.push("~=");
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here and will not see later tests
+ if ( !div.querySelectorAll(":checked").length ) {
+ rbuggyQSA.push(":checked");
+ }
+
+ // Support: Safari 8+, iOS 8+
+ // https://bugs.webkit.org/show_bug.cgi?id=136851
+ // In-page `selector#id sibing-combinator selector` fails
+ if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) {
+ rbuggyQSA.push(".#.+[+~]");
+ }
+ });
+
+ assert(function( div ) {
+ // Support: Windows 8 Native Apps
+ // The type and name attributes are restricted during .innerHTML assignment
+ var input = doc.createElement("input");
+ input.setAttribute( "type", "hidden" );
+ div.appendChild( input ).setAttribute( "name", "D" );
+
+ // Support: IE8
+ // Enforce case-sensitivity of name attribute
+ if ( div.querySelectorAll("[name=d]").length ) {
+ rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here and will not see later tests
+ if ( !div.querySelectorAll(":enabled").length ) {
+ rbuggyQSA.push( ":enabled", ":disabled" );
+ }
+
+ // Opera 10-11 does not throw on post-comma invalid pseudos
+ div.querySelectorAll("*,:x");
+ rbuggyQSA.push(",.*:");
+ });
+ }
+
+ if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
+ docElem.webkitMatchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector) )) ) {
+
+ assert(function( div ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ support.disconnectedMatch = matches.call( div, "div" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( div, "[s!='']:x" );
+ rbuggyMatches.push( "!=", pseudos );
+ });
+ }
+
+ rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+ rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
+
+ /* Contains
+ ---------------------------------------------------------------------- */
+ hasCompare = rnative.test( docElem.compareDocumentPosition );
+
+ // Element contains another
+ // Purposefully does not implement inclusive descendent
+ // As in, an element does not contain itself
+ contains = hasCompare || rnative.test( docElem.contains ) ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && (
+ adown.contains ?
+ adown.contains( bup ) :
+ a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+ ));
+ } :
+ function( a, b ) {
+ if ( b ) {
+ while ( (b = b.parentNode) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ /* Sorting
+ ---------------------------------------------------------------------- */
+
+ // Document order sorting
+ sortOrder = hasCompare ?
+ function( a, b ) {
+
+ // Flag for duplicate removal
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ // Sort on method existence if only one input has compareDocumentPosition
+ var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+ if ( compare ) {
+ return compare;
+ }
+
+ // Calculate position if both inputs belong to the same document
+ compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
+ a.compareDocumentPosition( b ) :
+
+ // Otherwise we know they are disconnected
+ 1;
+
+ // Disconnected nodes
+ if ( compare & 1 ||
+ (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
+
+ // Choose the first element that is related to our preferred document
+ if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
+ return -1;
+ }
+ if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
+ return 1;
+ }
+
+ // Maintain original order
+ return sortInput ?
+ ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+ 0;
+ }
+
+ return compare & 4 ? -1 : 1;
+ } :
+ function( a, b ) {
+ // Exit early if the nodes are identical
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ var cur,
+ i = 0,
+ aup = a.parentNode,
+ bup = b.parentNode,
+ ap = [ a ],
+ bp = [ b ];
+
+ // Parentless nodes are either documents or disconnected
+ if ( !aup || !bup ) {
+ return a === doc ? -1 :
+ b === doc ? 1 :
+ aup ? -1 :
+ bup ? 1 :
+ sortInput ?
+ ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+ 0;
+
+ // If the nodes are siblings, we can do a quick check
+ } else if ( aup === bup ) {
+ return siblingCheck( a, b );
+ }
+
+ // Otherwise we need full lists of their ancestors for comparison
+ cur = a;
+ while ( (cur = cur.parentNode) ) {
+ ap.unshift( cur );
+ }
+ cur = b;
+ while ( (cur = cur.parentNode) ) {
+ bp.unshift( cur );
+ }
+
+ // Walk down the tree looking for a discrepancy
+ while ( ap[i] === bp[i] ) {
+ i++;
+ }
+
+ return i ?
+ // Do a sibling check if the nodes have a common ancestor
+ siblingCheck( ap[i], bp[i] ) :
+
+ // Otherwise nodes in our document sort first
+ ap[i] === preferredDoc ? -1 :
+ bp[i] === preferredDoc ? 1 :
+ 0;
+ };
+
+ return doc;
+ };
+
+ Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+ };
+
+ Sizzle.matchesSelector = function( elem, expr ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
+ }
+
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace( rattributeQuotes, "='$1']" );
+
+ if ( support.matchesSelector && documentIsHTML &&
+ ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+ ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
+
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || support.disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch (e) {}
+ }
+
+ return Sizzle( expr, document, null, [ elem ] ).length > 0;
+ };
+
+ Sizzle.contains = function( context, elem ) {
+ // Set document vars if needed
+ if ( ( context.ownerDocument || context ) !== document ) {
+ setDocument( context );
+ }
+ return contains( context, elem );
+ };
+
+ Sizzle.attr = function( elem, name ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
+ }
+
+ var fn = Expr.attrHandle[ name.toLowerCase() ],
+ // Don't get fooled by Object.prototype properties (jQuery #13807)
+ val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+ fn( elem, name, !documentIsHTML ) :
+ undefined;
+
+ return val !== undefined ?
+ val :
+ support.attributes || !documentIsHTML ?
+ elem.getAttribute( name ) :
+ (val = elem.getAttributeNode(name)) && val.specified ?
+ val.value :
+ null;
+ };
+
+ Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+ };
+
+ /**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+ Sizzle.uniqueSort = function( results ) {
+ var elem,
+ duplicates = [],
+ j = 0,
+ i = 0;
+
+ // Unless we *know* we can detect duplicates, assume their presence
+ hasDuplicate = !support.detectDuplicates;
+ sortInput = !support.sortStable && results.slice( 0 );
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem === results[ i ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ results.splice( duplicates[ j ], 1 );
+ }
+ }
+
+ // Clear input after sorting to release objects
+ // See https://github.com/jquery/sizzle/pull/225
+ sortInput = null;
+
+ return results;
+ };
+
+ /**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+ getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( !nodeType ) {
+ // If no nodeType, this is expected to be an array
+ while ( (node = elem[i++]) ) {
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (jQuery #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ // Do not include comment or processing instruction nodes
+
+ return ret;
+ };
+
+ Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ attrHandle: {},
+
+ find: {},
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[1] = match[1].replace( runescape, funescape );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
+
+ if ( match[2] === "~=" ) {
+ match[3] = " " + match[3] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 what (child|of-type)
+ 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 4 xn-component of xn+y argument ([+-]?\d*n|)
+ 5 sign of xn-component
+ 6 x of xn-component
+ 7 sign of y-component
+ 8 y of y-component
+ */
+ match[1] = match[1].toLowerCase();
+
+ if ( match[1].slice( 0, 3 ) === "nth" ) {
+ // nth-* requires argument
+ if ( !match[3] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
+ match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[3] ) {
+ Sizzle.error( match[0] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var excess,
+ unquoted = !match[6] && match[2];
+
+ if ( matchExpr["CHILD"].test( match[0] ) ) {
+ return null;
+ }
+
+ // Accept quoted arguments as-is
+ if ( match[3] ) {
+ match[2] = match[4] || match[5] || "";
+
+ // Strip excess characters from unquoted arguments
+ } else if ( unquoted && rpseudo.test( unquoted ) &&
+ // Get excess from tokenize (recursively)
+ (excess = tokenize( unquoted, true )) &&
+ // advance to the next closing parenthesis
+ (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+ // excess is a negative index
+ match[0] = match[0].slice( 0, excess );
+ match[2] = unquoted.slice( 0, excess );
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+
+ "TAG": function( nodeNameSelector ) {
+ var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+ return nodeNameSelector === "*" ?
+ function() { return true; } :
+ function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ className + " " ];
+
+ return pattern ||
+ (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
+ classCache( className, function( elem ) {
+ return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
+ });
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.slice( -check.length ) === check :
+ operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+ false;
+ };
+ },
+
+ "CHILD": function( type, what, argument, first, last ) {
+ var simple = type.slice( 0, 3 ) !== "nth",
+ forward = type.slice( -4 ) !== "last",
+ ofType = what === "of-type";
+
+ return first === 1 && last === 0 ?
+
+ // Shortcut for :nth-*(n)
+ function( elem ) {
+ return !!elem.parentNode;
+ } :
+
+ function( elem, context, xml ) {
+ var cache, outerCache, node, diff, nodeIndex, start,
+ dir = simple !== forward ? "nextSibling" : "previousSibling",
+ parent = elem.parentNode,
+ name = ofType && elem.nodeName.toLowerCase(),
+ useCache = !xml && !ofType;
+
+ if ( parent ) {
+
+ // :(first|last|only)-(child|of-type)
+ if ( simple ) {
+ while ( dir ) {
+ node = elem;
+ while ( (node = node[ dir ]) ) {
+ if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
+ return false;
+ }
+ }
+ // Reverse direction for :only-* (if we haven't yet done so)
+ start = dir = type === "only" && !start && "nextSibling";
+ }
+ return true;
+ }
+
+ start = [ forward ? parent.firstChild : parent.lastChild ];
+
+ // non-xml :nth-child(...) stores cache data on `parent`
+ if ( forward && useCache ) {
+ // Seek `elem` from a previously-cached index
+ outerCache = parent[ expando ] || (parent[ expando ] = {});
+ cache = outerCache[ type ] || [];
+ nodeIndex = cache[0] === dirruns && cache[1];
+ diff = cache[0] === dirruns && cache[2];
+ node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+ while ( (node = ++nodeIndex && node && node[ dir ] ||
+
+ // Fallback to seeking `elem` from the start
+ (diff = nodeIndex = 0) || start.pop()) ) {
+
+ // When found, cache indexes on `parent` and break
+ if ( node.nodeType === 1 && ++diff && node === elem ) {
+ outerCache[ type ] = [ dirruns, nodeIndex, diff ];
+ break;
+ }
+ }
+
+ // Use previously-cached element index if available
+ } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
+ diff = cache[1];
+
+ // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
+ } else {
+ // Use the same loop as above to seek `elem` from the start
+ while ( (node = ++nodeIndex && node && node[ dir ] ||
+ (diff = nodeIndex = 0) || start.pop()) ) {
+
+ if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
+ // Cache the index of each encountered element
+ if ( useCache ) {
+ (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
+ }
+
+ if ( node === elem ) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset, then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument ) {
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction(function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf( seed, matched[i] );
+ seed[ idx ] = !( matches[ idx ] = matched[i] );
+ }
+ }) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+ // Potentially complex pseudos
+ "not": markFunction(function( selector ) {
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction(function( seed, matches, context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( (elem = unmatched[i]) ) {
+ seed[i] = !(matches[i] = elem);
+ }
+ }
+ }) :
+ function( elem, context, xml ) {
+ input[0] = elem;
+ matcher( input, null, xml, results );
+ // Don't keep the element (issue #299)
+ input[0] = null;
+ return !results.pop();
+ };
+ }),
+
+ "has": markFunction(function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ }),
+
+ "contains": markFunction(function( text ) {
+ text = text.replace( runescape, funescape );
+ return function( elem ) {
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+ };
+ }),
+
+ // "Whether an element is represented by a :lang() selector
+ // is based solely on the element's language value
+ // being equal to the identifier C,
+ // or beginning with the identifier C immediately followed by "-".
+ // The matching of C against the element's language value is performed case-insensitively.
+ // The identifier C does not have to be a valid language name."
+ // http://www.w3.org/TR/selectors/#lang-pseudo
+ "lang": markFunction( function( lang ) {
+ // lang value must be a valid identifier
+ if ( !ridentifier.test(lang || "") ) {
+ Sizzle.error( "unsupported lang: " + lang );
+ }
+ lang = lang.replace( runescape, funescape ).toLowerCase();
+ return function( elem ) {
+ var elemLang;
+ do {
+ if ( (elemLang = documentIsHTML ?
+ elem.lang :
+ elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
+
+ elemLang = elemLang.toLowerCase();
+ return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+ }
+ } while ( (elem = elem.parentNode) && elem.nodeType === 1 );
+ return false;
+ };
+ }),
+
+ // Miscellaneous
+ "target": function( elem ) {
+ var hash = window.location && window.location.hash;
+ return hash && hash.slice( 1 ) === elem.id;
+ },
+
+ "root": function( elem ) {
+ return elem === docElem;
+ },
+
+ "focus": function( elem ) {
+ return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+ },
+
+ // Boolean properties
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
+
+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
+
+ "checked": function( elem ) {
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+ },
+
+ "selected": function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ // Contents
+ "empty": function( elem ) {
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+ // but not by others (comment: 8; processing instruction: 7; etc.)
+ // nodeType < 6 works because attributes (2) do not appear as children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ if ( elem.nodeType < 6 ) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos["empty"]( elem );
+ },
+
+ // Element/input types
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "text": function( elem ) {
+ var attr;
+ return elem.nodeName.toLowerCase() === "input" &&
+ elem.type === "text" &&
+
+ // Support: IE<8
+ // New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
+ ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
+ },
+
+ // Position-in-collection
+ "first": createPositionalPseudo(function() {
+ return [ 0 ];
+ }),
+
+ "last": createPositionalPseudo(function( matchIndexes, length ) {
+ return [ length - 1 ];
+ }),
+
+ "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ }),
+
+ "even": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 0;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "odd": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 1;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ })
+ }
+ };
+
+ Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+ // Add button/input type pseudos
+ for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+ Expr.pseudos[ i ] = createInputPseudo( i );
+ }
+ for ( i in { submit: true, reset: true } ) {
+ Expr.pseudos[ i ] = createButtonPseudo( i );
+ }
+
+ // Easy API for creating new setFilters
+ function setFilters() {}
+ setFilters.prototype = Expr.filters = Expr.pseudos;
+ Expr.setFilters = new setFilters();
+
+ tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
+ var matched, match, tokens, type,
+ soFar, groups, preFilters,
+ cached = tokenCache[ selector + " " ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || (match = rcomma.exec( soFar )) ) {
+ if ( match ) {
+ // Don't consume trailing commas as valid
+ soFar = soFar.slice( match[0].length ) || soFar;
+ }
+ groups.push( (tokens = []) );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( (match = rcombinators.exec( soFar )) ) {
+ matched = match.shift();
+ tokens.push({
+ value: matched,
+ // Cast descendant combinators to space
+ type: match[0].replace( rtrim, " " )
+ });
+ soFar = soFar.slice( matched.length );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+ (match = preFilters[ type ]( match ))) ) {
+ matched = match.shift();
+ tokens.push({
+ value: matched,
+ type: type,
+ matches: match
+ });
+ soFar = soFar.slice( matched.length );
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+ };
+
+ function toSelector( tokens ) {
+ var i = 0,
+ len = tokens.length,
+ selector = "";
+ for ( ; i < len; i++ ) {
+ selector += tokens[i].value;
+ }
+ return selector;
+ }
+
+ function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ checkNonElements = base && dir === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ var oldCache, outerCache,
+ newCache = [ dirruns, doneName ];
+
+ // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+ if ( xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ if ( matcher( elem, context, xml ) ) {
+ return true;
+ }
+ }
+ }
+ } else {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ outerCache = elem[ expando ] || (elem[ expando ] = {});
+ if ( (oldCache = outerCache[ dir ]) &&
+ oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
+
+ // Assign to newCache so results back-propagate to previous elements
+ return (newCache[ 2 ] = oldCache[ 2 ]);
+ } else {
+ // Reuse newcache so results back-propagate to previous elements
+ outerCache[ dir ] = newCache;
+
+ // A match means we're done; a fail means we have to keep checking
+ if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ };
+ }
+
+ function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[i]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[0];
+ }
+
+ function multipleContexts( selector, contexts, results ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[i], results );
+ }
+ return results;
+ }
+
+ function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( (elem = unmatched[i]) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+ }
+
+ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction(function( seed, results, context, xml ) {
+ var temp, i, elem,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ temp = condense( matcherOut, postMap );
+ postFilter( temp, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = temp.length;
+ while ( i-- ) {
+ if ( (elem = temp[i]) ) {
+ matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+ }
+ }
+ }
+
+ if ( seed ) {
+ if ( postFinder || preFilter ) {
+ if ( postFinder ) {
+ // Get the final matcherOut by condensing this intermediate into postFinder contexts
+ temp = [];
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) ) {
+ // Restore matcherIn since elem is not yet a final match
+ temp.push( (matcherIn[i] = elem) );
+ }
+ }
+ postFinder( null, (matcherOut = []), temp, xml );
+ }
+
+ // Move matched elements from seed to results to keep them synchronized
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) &&
+ (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
+
+ seed[temp] = !(results[temp] = elem);
+ }
+ }
+ }
+
+ // Add elements to results, through postFinder if defined
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ });
+ }
+
+ function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[0].type ],
+ implicitRelative = leadingRelative || Expr.relative[" "],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ (checkContext = context).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+ // Avoid hanging onto element (issue #299)
+ checkContext = null;
+ return ret;
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+ matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
+ } else {
+ matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[j].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && toSelector(
+ // If the preceding token was a descendant combinator, insert an implicit any-element `*`
+ tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
+ ).replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+ j < len && toSelector( tokens )
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+ }
+
+ function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, outermost ) {
+ var elem, j, matcher,
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ setMatched = [],
+ contextBackup = outermostContext,
+ // We must always have either seed elements or outermost context
+ elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
+ // Use integer dirruns iff this is the outermost matcher
+ dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
+ len = elems.length;
+
+ if ( outermost ) {
+ outermostContext = context !== document && context;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
+ // Support: IE<9, Safari
+ // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id
+ for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
+ if ( byElement && elem ) {
+ j = 0;
+ while ( (matcher = elementMatchers[j++]) ) {
+ if ( matcher( elem, context, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+ // They will have gone through all possible matchers
+ if ( (elem = !matcher && elem) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // Apply set filters to unmatched elements
+ matchedCount += i;
+ if ( bySet && i !== matchedCount ) {
+ j = 0;
+ while ( (matcher = setMatchers[j++]) ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !(unmatched[i] || setMatched[i]) ) {
+ setMatched[i] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+ }
+
+ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ selector + " " ];
+
+ if ( !cached ) {
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !match ) {
+ match = tokenize( selector );
+ }
+ i = match.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( match[i] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+
+ // Save selector and tokenization
+ cached.selector = selector;
+ }
+ return cached;
+ };
+
+ /**
+ * A low-level selection function that works with Sizzle's compiled
+ * selector functions
+ * @param {String|Function} selector A selector or a pre-compiled
+ * selector function built with Sizzle.compile
+ * @param {Element} context
+ * @param {Array} [results]
+ * @param {Array} [seed] A set of elements to match against
+ */
+ select = Sizzle.select = function( selector, context, results, seed ) {
+ var i, tokens, token, type, find,
+ compiled = typeof selector === "function" && selector,
+ match = !seed && tokenize( (selector = compiled.selector || selector) );
+
+ results = results || [];
+
+ // Try to minimize operations if there is no seed and only one group
+ if ( match.length === 1 ) {
+
+ // Take a shortcut and set the context if the root selector is an ID
+ tokens = match[0] = match[0].slice( 0 );
+ if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+ support.getById && context.nodeType === 9 && documentIsHTML &&
+ Expr.relative[ tokens[1].type ] ) {
+
+ context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
+ if ( !context ) {
+ return results;
+
+ // Precompiled matchers will still verify ancestry, so step up a level
+ } else if ( compiled ) {
+ context = context.parentNode;
+ }
+
+ selector = selector.slice( tokens.shift().value.length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
+ while ( i-- ) {
+ token = tokens[i];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ (type = token.type) ] ) {
+ break;
+ }
+ if ( (find = Expr.find[ type ]) ) {
+ // Search, expanding context for leading sibling combinators
+ if ( (seed = find(
+ token.matches[0].replace( runescape, funescape ),
+ rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
+ )) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && toSelector( tokens );
+ if ( !selector ) {
+ push.apply( results, seed );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function if one is not provided
+ // Provide `match` to avoid retokenization if we modified the selector above
+ ( compiled || compile( selector, match ) )(
+ seed,
+ context,
+ !documentIsHTML,
+ results,
+ rsibling.test( selector ) && testContext( context.parentNode ) || context
+ );
+ return results;
+ };
+
+ // One-time assignments
+
+ // Sort stability
+ support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
+
+ // Support: Chrome 14-35+
+ // Always assume duplicates if they aren't passed to the comparison function
+ support.detectDuplicates = !!hasDuplicate;
+
+ // Initialize against the default document
+ setDocument();
+
+ // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+ // Detached nodes confoundingly follow *each other*
+ support.sortDetached = assert(function( div1 ) {
+ // Should return 1, but returns 4 (following)
+ return div1.compareDocumentPosition( document.createElement("div") ) & 1;
+ });
+
+ // Support: IE<8
+ // Prevent attribute/property "interpolation"
+ // http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+ if ( !assert(function( div ) {
+ div.innerHTML = "";
+ return div.firstChild.getAttribute("href") === "#" ;
+ }) ) {
+ addHandle( "type|href|height|width", function( elem, name, isXML ) {
+ if ( !isXML ) {
+ return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+ }
+ });
+ }
+
+ // Support: IE<9
+ // Use defaultValue in place of getAttribute("value")
+ if ( !support.attributes || !assert(function( div ) {
+ div.innerHTML = "";
+ div.firstChild.setAttribute( "value", "" );
+ return div.firstChild.getAttribute( "value" ) === "";
+ }) ) {
+ addHandle( "value", function( elem, name, isXML ) {
+ if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+ return elem.defaultValue;
+ }
+ });
+ }
+
+ // Support: IE<9
+ // Use getAttributeNode to fetch booleans when getAttribute lies
+ if ( !assert(function( div ) {
+ return div.getAttribute("disabled") == null;
+ }) ) {
+ addHandle( booleans, function( elem, name, isXML ) {
+ var val;
+ if ( !isXML ) {
+ return elem[ name ] === true ? name.toLowerCase() :
+ (val = elem.getAttributeNode( name )) && val.specified ?
+ val.value :
+ null;
+ }
+ });
+ }
+
+ return Sizzle;
+
+ })( window );
+
+
+
+ jQuery.find = Sizzle;
+ jQuery.expr = Sizzle.selectors;
+ jQuery.expr[":"] = jQuery.expr.pseudos;
+ jQuery.unique = Sizzle.uniqueSort;
+ jQuery.text = Sizzle.getText;
+ jQuery.isXMLDoc = Sizzle.isXML;
+ jQuery.contains = Sizzle.contains;
+
+
+
+ var rneedsContext = jQuery.expr.match.needsContext;
+
+ var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);
+
+
+
+ var risSimple = /^.[^:#\[\.,]*$/;
+
+ // Implement the identical functionality for filter and not
+ function winnow( elements, qualifier, not ) {
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep( elements, function( elem, i ) {
+ /* jshint -W018 */
+ return !!qualifier.call( elem, i, elem ) !== not;
+ });
+
+ }
+
+ if ( qualifier.nodeType ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( elem === qualifier ) !== not;
+ });
+
+ }
+
+ if ( typeof qualifier === "string" ) {
+ if ( risSimple.test( qualifier ) ) {
+ return jQuery.filter( qualifier, elements, not );
+ }
+
+ qualifier = jQuery.filter( qualifier, elements );
+ }
+
+ return jQuery.grep( elements, function( elem ) {
+ return ( indexOf.call( qualifier, elem ) >= 0 ) !== not;
+ });
+ }
+
+ jQuery.filter = function( expr, elems, not ) {
+ var elem = elems[ 0 ];
+
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 && elem.nodeType === 1 ?
+ jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
+ jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+ return elem.nodeType === 1;
+ }));
+ };
+
+ jQuery.fn.extend({
+ find: function( selector ) {
+ var i,
+ len = this.length,
+ ret = [],
+ self = this;
+
+ if ( typeof selector !== "string" ) {
+ return this.pushStack( jQuery( selector ).filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ }) );
+ }
+
+ for ( i = 0; i < len; i++ ) {
+ jQuery.find( selector, self[ i ], ret );
+ }
+
+ // Needed because $( selector, context ) becomes $( context ).find( selector )
+ ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
+ ret.selector = this.selector ? this.selector + " " + selector : selector;
+ return ret;
+ },
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], false) );
+ },
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], true) );
+ },
+ is: function( selector ) {
+ return !!winnow(
+ this,
+
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ typeof selector === "string" && rneedsContext.test( selector ) ?
+ jQuery( selector ) :
+ selector || [],
+ false
+ ).length;
+ }
+ });
+
+
+ // Initialize a jQuery object
+
+
+ // A central reference to the root jQuery(document)
+ var rootjQuery,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over to avoid XSS via location.hash (#9521)
+ // Strict HTML recognition (#11290: must start with <)
+ rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
+
+ init = jQuery.fn.init = function( selector, context ) {
+ var match, elem;
+
+ // HANDLE: $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+
+ // Option to run scripts is true for back-compat
+ // Intentionally let the error be thrown if parseHTML is not present
+ jQuery.merge( this, jQuery.parseHTML(
+ match[1],
+ context && context.nodeType ? context.ownerDocument || context : document,
+ true
+ ) );
+
+ // HANDLE: $(html, props)
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ for ( match in context ) {
+ // Properties of context are called as methods if possible
+ if ( jQuery.isFunction( this[ match ] ) ) {
+ this[ match ]( context[ match ] );
+
+ // ...and otherwise set as attributes
+ } else {
+ this.attr( match, context[ match ] );
+ }
+ }
+ }
+
+ return this;
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Support: Blackberry 4.6
+ // gEBID returns nodes no longer in the document (#6963)
+ if ( elem && elem.parentNode ) {
+ // Inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(DOMElement)
+ } else if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return typeof rootjQuery.ready !== "undefined" ?
+ rootjQuery.ready( selector ) :
+ // Execute immediately if ready is not present
+ selector( jQuery );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ };
+
+ // Give the init function the jQuery prototype for later instantiation
+ init.prototype = jQuery.fn;
+
+ // Initialize central reference
+ rootjQuery = jQuery( document );
+
+
+ var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+ // Methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+ jQuery.extend({
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ truncate = until !== undefined;
+
+ while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {
+ if ( elem.nodeType === 1 ) {
+ if ( truncate && jQuery( elem ).is( until ) ) {
+ break;
+ }
+ matched.push( elem );
+ }
+ }
+ return matched;
+ },
+
+ sibling: function( n, elem ) {
+ var matched = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ matched.push( n );
+ }
+ }
+
+ return matched;
+ }
+ });
+
+ jQuery.fn.extend({
+ has: function( target ) {
+ var targets = jQuery( target, this ),
+ l = targets.length;
+
+ return this.filter(function() {
+ var i = 0;
+ for ( ; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ matched = [],
+ pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( ; i < l; i++ ) {
+ for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
+ // Always skip document fragments
+ if ( cur.nodeType < 11 && (pos ?
+ pos.index(cur) > -1 :
+
+ // Don't pass non-elements to Sizzle
+ cur.nodeType === 1 &&
+ jQuery.find.matchesSelector(cur, selectors)) ) {
+
+ matched.push( cur );
+ break;
+ }
+ }
+ }
+
+ return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
+ },
+
+ // Determine the position of an element within the set
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+ }
+
+ // Index in selector
+ if ( typeof elem === "string" ) {
+ return indexOf.call( jQuery( elem ), this[ 0 ] );
+ }
+
+ // Locate the position of the desired element
+ return indexOf.call( this,
+
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[ 0 ] : elem
+ );
+ },
+
+ add: function( selector, context ) {
+ return this.pushStack(
+ jQuery.unique(
+ jQuery.merge( this.get(), jQuery( selector, context ) )
+ )
+ );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter(selector)
+ );
+ }
+ });
+
+ function sibling( cur, dir ) {
+ while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}
+ return cur;
+ }
+
+ jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return elem.contentDocument || jQuery.merge( [], elem.childNodes );
+ }
+ }, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var matched = jQuery.map( this, fn, until );
+
+ if ( name.slice( -5 ) !== "Until" ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ matched = jQuery.filter( selector, matched );
+ }
+
+ if ( this.length > 1 ) {
+ // Remove duplicates
+ if ( !guaranteedUnique[ name ] ) {
+ jQuery.unique( matched );
+ }
+
+ // Reverse order for parents* and prev-derivatives
+ if ( rparentsprev.test( name ) ) {
+ matched.reverse();
+ }
+ }
+
+ return this.pushStack( matched );
+ };
+ });
+ var rnotwhite = (/\S+/g);
+
+
+
+ // String to Object options format cache
+ var optionsCache = {};
+
+ // Convert String-formatted options into Object-formatted ones and store in cache
+ function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+ }
+
+ /*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+ jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
+
+ var // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ var type = jQuery.type( arg );
+ if ( type === "function" ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Check if a given callback is in the list.
+ // If no argument is given, return whether or not list has callbacks attached.
+ has: function( fn ) {
+ return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ firingLength = 0;
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( list && ( !fired || stack ) ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+ };
+
+
+ jQuery.extend({
+
+ Deferred: function( func ) {
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( tuples, function( i, tuple ) {
+ var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ](function() {
+ var returned = fn && fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
+ }
+ });
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ]
+ deferred[ tuple[0] ] = function() {
+ deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
+ return this;
+ };
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+ if ( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // Add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
+
+ // If we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ }
+ });
+
+
+ // The deferred used on DOM ready
+ var readyList;
+
+ jQuery.fn.ready = function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
+
+ return this;
+ };
+
+ jQuery.extend({
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.triggerHandler ) {
+ jQuery( document ).triggerHandler( "ready" );
+ jQuery( document ).off( "ready" );
+ }
+ }
+ });
+
+ /**
+ * The ready event handler and self cleanup method
+ */
+ function completed() {
+ document.removeEventListener( "DOMContentLoaded", completed, false );
+ window.removeEventListener( "load", completed, false );
+ jQuery.ready();
+ }
+
+ jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
+
+ readyList = jQuery.Deferred();
+
+ // Catch cases where $(document).ready() is called after the browser event has already occurred.
+ // We once tried to use readyState "interactive" here, but it caused issues like the one
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready );
+
+ } else {
+
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", completed, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", completed, false );
+ }
+ }
+ return readyList.promise( obj );
+ };
+
+ // Kick off the DOM ready check even if the user does not
+ jQuery.ready.promise();
+
+
+
+
+ // Multifunctional method to get and set values of a collection
+ // The value/s can optionally be executed if it's a function
+ var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+ var i = 0,
+ len = elems.length,
+ bulk = key == null;
+
+ // Sets many values
+ if ( jQuery.type( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
+ }
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
+
+ if ( !jQuery.isFunction( value ) ) {
+ raw = true;
+ }
+
+ if ( bulk ) {
+ // Bulk operations run against the entire set
+ if ( raw ) {
+ fn.call( elems, value );
+ fn = null;
+
+ // ...except when executing function values
+ } else {
+ bulk = fn;
+ fn = function( elem, key, value ) {
+ return bulk.call( jQuery( elem ), value );
+ };
+ }
+ }
+
+ if ( fn ) {
+ for ( ; i < len; i++ ) {
+ fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
+ }
+ }
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ len ? fn( elems[0], key ) : emptyGet;
+ };
+
+
+ /**
+ * Determines whether an object can have data
+ */
+ jQuery.acceptData = function( owner ) {
+ // Accepts only:
+ // - Node
+ // - Node.ELEMENT_NODE
+ // - Node.DOCUMENT_NODE
+ // - Object
+ // - Any
+ /* jshint -W018 */
+ return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
+ };
+
+
+ function Data() {
+ // Support: Android<4,
+ // Old WebKit does not have Object.preventExtensions/freeze method,
+ // return new empty object instead with no [[set]] accessor
+ Object.defineProperty( this.cache = {}, 0, {
+ get: function() {
+ return {};
+ }
+ });
+
+ this.expando = jQuery.expando + Data.uid++;
+ }
+
+ Data.uid = 1;
+ Data.accepts = jQuery.acceptData;
+
+ Data.prototype = {
+ key: function( owner ) {
+ // We can accept data for non-element nodes in modern browsers,
+ // but we should not, see #8335.
+ // Always return the key for a frozen object.
+ if ( !Data.accepts( owner ) ) {
+ return 0;
+ }
+
+ var descriptor = {},
+ // Check if the owner object already has a cache key
+ unlock = owner[ this.expando ];
+
+ // If not, create one
+ if ( !unlock ) {
+ unlock = Data.uid++;
+
+ // Secure it in a non-enumerable, non-writable property
+ try {
+ descriptor[ this.expando ] = { value: unlock };
+ Object.defineProperties( owner, descriptor );
+
+ // Support: Android<4
+ // Fallback to a less secure definition
+ } catch ( e ) {
+ descriptor[ this.expando ] = unlock;
+ jQuery.extend( owner, descriptor );
+ }
+ }
+
+ // Ensure the cache object
+ if ( !this.cache[ unlock ] ) {
+ this.cache[ unlock ] = {};
+ }
+
+ return unlock;
+ },
+ set: function( owner, data, value ) {
+ var prop,
+ // There may be an unlock assigned to this node,
+ // if there is no entry for this "owner", create one inline
+ // and set the unlock as though an owner entry had always existed
+ unlock = this.key( owner ),
+ cache = this.cache[ unlock ];
+
+ // Handle: [ owner, key, value ] args
+ if ( typeof data === "string" ) {
+ cache[ data ] = value;
+
+ // Handle: [ owner, { properties } ] args
+ } else {
+ // Fresh assignments by object are shallow copied
+ if ( jQuery.isEmptyObject( cache ) ) {
+ jQuery.extend( this.cache[ unlock ], data );
+ // Otherwise, copy the properties one-by-one to the cache object
+ } else {
+ for ( prop in data ) {
+ cache[ prop ] = data[ prop ];
+ }
+ }
+ }
+ return cache;
+ },
+ get: function( owner, key ) {
+ // Either a valid cache is found, or will be created.
+ // New caches will be created and the unlock returned,
+ // allowing direct access to the newly created
+ // empty data object. A valid owner object must be provided.
+ var cache = this.cache[ this.key( owner ) ];
+
+ return key === undefined ?
+ cache : cache[ key ];
+ },
+ access: function( owner, key, value ) {
+ var stored;
+ // In cases where either:
+ //
+ // 1. No key was specified
+ // 2. A string key was specified, but no value provided
+ //
+ // Take the "read" path and allow the get method to determine
+ // which value to return, respectively either:
+ //
+ // 1. The entire cache object
+ // 2. The data stored at the key
+ //
+ if ( key === undefined ||
+ ((key && typeof key === "string") && value === undefined) ) {
+
+ stored = this.get( owner, key );
+
+ return stored !== undefined ?
+ stored : this.get( owner, jQuery.camelCase(key) );
+ }
+
+ // [*]When the key is not a string, or both a key and value
+ // are specified, set or extend (existing objects) with either:
+ //
+ // 1. An object of properties
+ // 2. A key and value
+ //
+ this.set( owner, key, value );
+
+ // Since the "set" path can have two possible entry points
+ // return the expected data based on which path was taken[*]
+ return value !== undefined ? value : key;
+ },
+ remove: function( owner, key ) {
+ var i, name, camel,
+ unlock = this.key( owner ),
+ cache = this.cache[ unlock ];
+
+ if ( key === undefined ) {
+ this.cache[ unlock ] = {};
+
+ } else {
+ // Support array or space separated string of keys
+ if ( jQuery.isArray( key ) ) {
+ // If "name" is an array of keys...
+ // When data is initially created, via ("key", "val") signature,
+ // keys will be converted to camelCase.
+ // Since there is no way to tell _how_ a key was added, remove
+ // both plain key and camelCase key. #12786
+ // This will only penalize the array argument path.
+ name = key.concat( key.map( jQuery.camelCase ) );
+ } else {
+ camel = jQuery.camelCase( key );
+ // Try the string as a key before any manipulation
+ if ( key in cache ) {
+ name = [ key, camel ];
+ } else {
+ // If a key with the spaces exists, use it.
+ // Otherwise, create an array by matching non-whitespace
+ name = camel;
+ name = name in cache ?
+ [ name ] : ( name.match( rnotwhite ) || [] );
+ }
+ }
+
+ i = name.length;
+ while ( i-- ) {
+ delete cache[ name[ i ] ];
+ }
+ }
+ },
+ hasData: function( owner ) {
+ return !jQuery.isEmptyObject(
+ this.cache[ owner[ this.expando ] ] || {}
+ );
+ },
+ discard: function( owner ) {
+ if ( owner[ this.expando ] ) {
+ delete this.cache[ owner[ this.expando ] ];
+ }
+ }
+ };
+ var data_priv = new Data();
+
+ var data_user = new Data();
+
+
+
+ // Implementation Summary
+ //
+ // 1. Enforce API surface and semantic compatibility with 1.9.x branch
+ // 2. Improve the module's maintainability by reducing the storage
+ // paths to a single mechanism.
+ // 3. Use the same single mechanism to support "private" and "user" data.
+ // 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+ // 5. Avoid exposing implementation details on user objects (eg. expando properties)
+ // 6. Provide a clear path for implementation upgrade to WeakMap in 2014
+
+ var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+ function dataAttr( elem, key, data ) {
+ var name;
+
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+ name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ data_user.set( elem, key, data );
+ } else {
+ data = undefined;
+ }
+ }
+ return data;
+ }
+
+ jQuery.extend({
+ hasData: function( elem ) {
+ return data_user.hasData( elem ) || data_priv.hasData( elem );
+ },
+
+ data: function( elem, name, data ) {
+ return data_user.access( elem, name, data );
+ },
+
+ removeData: function( elem, name ) {
+ data_user.remove( elem, name );
+ },
+
+ // TODO: Now that all calls to _data and _removeData have been replaced
+ // with direct calls to data_priv methods, these can be deprecated.
+ _data: function( elem, name, data ) {
+ return data_priv.access( elem, name, data );
+ },
+
+ _removeData: function( elem, name ) {
+ data_priv.remove( elem, name );
+ }
+ });
+
+ jQuery.fn.extend({
+ data: function( key, value ) {
+ var i, name, data,
+ elem = this[ 0 ],
+ attrs = elem && elem.attributes;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = data_user.get( elem );
+
+ if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
+ i = attrs.length;
+ while ( i-- ) {
+
+ // Support: IE11+
+ // The attrs elements can be null (#14894)
+ if ( attrs[ i ] ) {
+ name = attrs[ i ].name;
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.slice(5) );
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ }
+ data_priv.set( elem, "hasDataAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ data_user.set( this, key );
+ });
+ }
+
+ return access( this, function( value ) {
+ var data,
+ camelKey = jQuery.camelCase( key );
+
+ // The calling jQuery object (element matches) is not empty
+ // (and therefore has an element appears at this[ 0 ]) and the
+ // `value` parameter was not undefined. An empty jQuery object
+ // will result in `undefined` for elem = this[ 0 ] which will
+ // throw an exception if an attempt to read a data cache is made.
+ if ( elem && value === undefined ) {
+ // Attempt to get data from the cache
+ // with the key as-is
+ data = data_user.get( elem, key );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // Attempt to get data from the cache
+ // with the key camelized
+ data = data_user.get( elem, camelKey );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // Attempt to "discover" the data in
+ // HTML5 custom data-* attrs
+ data = dataAttr( elem, camelKey, undefined );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // We tried really hard, but the data doesn't exist.
+ return;
+ }
+
+ // Set the data...
+ this.each(function() {
+ // First, attempt to store a copy or reference of any
+ // data that might've been store with a camelCased key.
+ var data = data_user.get( this, camelKey );
+
+ // For HTML5 data-* attribute interop, we have to
+ // store property names with dashes in a camelCase form.
+ // This might not apply to all properties...*
+ data_user.set( this, camelKey, value );
+
+ // *... In the case of properties that might _actually_
+ // have dashes, we need to also store a copy of that
+ // unchanged property.
+ if ( key.indexOf("-") !== -1 && data !== undefined ) {
+ data_user.set( this, key, value );
+ }
+ });
+ }, null, value, arguments.length > 1, null, true );
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ data_user.remove( this, key );
+ });
+ }
+ });
+
+
+ jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = data_priv.get( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray( data ) ) {
+ queue = data_priv.access( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // Clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // Not public - generate a queueHooks object, or return the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return data_priv.get( elem, key ) || data_priv.access( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ data_priv.remove( elem, [ type + "queue", key ] );
+ })
+ });
+ }
+ });
+
+ jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // Ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while ( i-- ) {
+ tmp = data_priv.get( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+ });
+ var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source;
+
+ var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+
+ var isHidden = function( elem, el ) {
+ // isHidden might be called from jQuery#filter function;
+ // in that case, element will be second argument
+ elem = el || elem;
+ return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+ };
+
+ var rcheckableType = (/^(?:checkbox|radio)$/i);
+
+
+
+ (function() {
+ var fragment = document.createDocumentFragment(),
+ div = fragment.appendChild( document.createElement( "div" ) ),
+ input = document.createElement( "input" );
+
+ // Support: Safari<=5.1
+ // Check state lost if the name is set (#11217)
+ // Support: Windows Web Apps (WWA)
+ // `name` and `type` must use .setAttribute for WWA (#14901)
+ input.setAttribute( "type", "radio" );
+ input.setAttribute( "checked", "checked" );
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+
+ // Support: Safari<=5.1, Android<4.2
+ // Older WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Support: IE<=11+
+ // Make sure textarea (and checkbox) defaultValue is properly cloned
+ div.innerHTML = "";
+ support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+ })();
+ var strundefined = typeof undefined;
+
+
+
+ support.focusinBubbles = "onfocusin" in window;
+
+
+ var
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
+
+ function returnTrue() {
+ return true;
+ }
+
+ function returnFalse() {
+ return false;
+ }
+
+ function safeActiveElement() {
+ try {
+ return document.activeElement;
+ } catch ( err ) { }
+ }
+
+ /*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+ jQuery.event = {
+
+ global: {},
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var handleObjIn, eventHandle, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = data_priv.get( elem );
+
+ // Don't attach events to noData or text/comment nodes (but allow plain objects)
+ if ( !elemData ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ if ( !(events = elemData.events) ) {
+ events = elemData.events = {};
+ }
+ if ( !(eventHandle = elemData.handle) ) {
+ eventHandle = elemData.handle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?
+ jQuery.event.dispatch.apply( elem, arguments ) : undefined;
+ };
+ }
+
+ // Handle multiple events separated by a space
+ types = ( types || "" ).match( rnotwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // There *must* be a type, no attaching namespace-only handlers
+ if ( !type ) {
+ continue;
+ }
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: origType,
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ if ( !(handlers = events[ type ]) ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ },
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var j, origCount, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = data_priv.hasData( elem ) && data_priv.get( elem );
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = ( types || "" ).match( rnotwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+ handlers = events[ type ] || [];
+ tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
+
+ // Remove matching events
+ origCount = j = handlers.length;
+ while ( j-- ) {
+ handleObj = handlers[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !tmp || tmp.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ handlers.splice( j, 1 );
+
+ if ( handleObj.selector ) {
+ handlers.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( origCount && !handlers.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+ data_priv.remove( elem, "events" );
+ }
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+
+ var i, cur, tmp, bubbleType, ontype, handle, special,
+ eventPath = [ elem || document ],
+ type = hasOwn.call( event, "type" ) ? event.type : event,
+ namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
+
+ cur = tmp = elem = elem || document;
+
+ // Don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf(".") >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+ ontype = type.indexOf(":") < 0 && "on" + type;
+
+ // Caller can pass in a jQuery.Event object, Object, or just an event type string
+ event = event[ jQuery.expando ] ?
+ event :
+ new jQuery.Event( type, typeof event === "object" && event );
+
+ // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+ event.isTrigger = onlyHandlers ? 2 : 3;
+ event.namespace = namespaces.join(".");
+ event.namespace_re = event.namespace ?
+ new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
+ null;
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data == null ?
+ [ event ] :
+ jQuery.makeArray( data, [ event ] );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ if ( !rfocusMorph.test( bubbleType + type ) ) {
+ cur = cur.parentNode;
+ }
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push( cur );
+ tmp = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( tmp === (elem.ownerDocument || document) ) {
+ eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+ }
+ }
+
+ // Fire handlers on the event path
+ i = 0;
+ while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
+
+ event.type = i > 1 ?
+ bubbleType :
+ special.bindType || type;
+
+ // jQuery handler
+ handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+
+ // Native handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
+ event.result = handle.apply( cur, data );
+ if ( event.result === false ) {
+ event.preventDefault();
+ }
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
+ jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ tmp = elem[ ontype ];
+
+ if ( tmp ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( tmp ) {
+ elem[ ontype ] = tmp;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event );
+
+ var i, j, ret, matched, handleObj,
+ handlerQueue = [],
+ args = slice.call( arguments ),
+ handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [],
+ special = jQuery.event.special[ event.type ] || {};
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers
+ handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+ // Run delegates first; they may want to stop propagation beneath us
+ i = 0;
+ while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
+ event.currentTarget = matched.elem;
+
+ j = 0;
+ while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
+
+ // Triggered event must either 1) have no namespace, or 2) have namespace(s)
+ // a subset or equal to those in the bound event (both can have no namespace).
+ if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.handleObj = handleObj;
+ event.data = handleObj.data;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ if ( (event.result = ret) === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ handlers: function( event, handlers ) {
+ var i, matches, sel, handleObj,
+ handlerQueue = [],
+ delegateCount = handlers.delegateCount,
+ cur = event.target;
+
+ // Find delegate handlers
+ // Black-hole SVG