scala-news-reader
rss/atom news reader in scala
git clone https://9o.is/git/scala-news-reader.git
commit 4cdc146388e82f7173701194c834c18e6c574f54 Author: Jul <jul@9o.is> Date: Mon, 24 Jun 2013 20:46:04 -0400 update for safety. init. Diffstat:
| A | .gitignore | | | 28 | ++++++++++++++++++++++++++++ |
| A | README.md | | | 53 | +++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | project/Build.scala | | | 25 | +++++++++++++++++++++++++ |
| A | project/Build.scala.save | | | 26 | ++++++++++++++++++++++++++ |
| A | project/BuildSettings.scala | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | project/plugins.sbt | | | 9 | +++++++++ |
| A | project/sbt-launch.jar | | | 0 | |
| A | sbt | | | 1 | + |
| A | sbt-debug | | | 1 | + |
| A | src/main/javascript/customs/fileupload.js | | | 56 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/javascript/customs/other.js | | | 12 | ++++++++++++ |
| A | src/main/javascript/libs/bootstrap-2.2.2.min.js | | | 7 | +++++++ |
| A | src/main/javascript/libs/jquery-1.8.3.min.js | | | 3 | +++ |
| A | src/main/javascript/libs/jquery.bsAlerts.min.js | | | 8 | ++++++++ |
| A | src/main/javascript/libs/jquery.bsFormAlerts.min.js | | | 8 | ++++++++ |
| A | src/main/javascript/libs/jquery.fileupload.js | | | 1316 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/javascript/libs/jquery.iframe-transport.js | | | 206 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/javascript/libs/jquery.timeago.js | | | 193 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/javascript/libs/jquery.ui.widget.js | | | 531 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/javascript/libs/liftAjax.js | | | 212 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/javascript/libs/mediaelement-and-player.min.js | | | 172 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/javascript/script.jsm | | | 22 | ++++++++++++++++++++++ |
| A | src/main/less/bootstrap/accordion.less | | | 34 | ++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/alerts.less | | | 79 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/bootstrap.less | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/breadcrumbs.less | | | 24 | ++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/button-groups.less | | | 227 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/buttons.less | | | 230 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/carousel.less | | | 131 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/close.less | | | 32 | ++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/code.less | | | 62 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/component-animations.less | | | 22 | ++++++++++++++++++++++ |
| A | src/main/less/bootstrap/dropdowns.less | | | 233 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/forms.less | | | 687 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/grid.less | | | 21 | +++++++++++++++++++++ |
| A | src/main/less/bootstrap/hero-unit.less | | | 25 | +++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/labels-badges.less | | | 82 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/layouts.less | | | 17 | +++++++++++++++++ |
| A | src/main/less/bootstrap/media.less | | | 55 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/mixins.less | | | 686 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/modals.less | | | 95 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/navbar.less | | | 493 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/navs.less | | | 391 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/pager.less | | | 42 | ++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/pagination.less | | | 121 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/popovers.less | | | 129 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/progress-bars.less | | | 122 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/reset.less | | | 217 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/responsive-1200px-min.less | | | 28 | ++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/responsive-767px-max.less | | | 193 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/responsive-768px-979px.less | | | 19 | +++++++++++++++++++ |
| A | src/main/less/bootstrap/responsive-navbar.less | | | 185 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/responsive-utilities.less | | | 43 | +++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/responsive.less | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/scaffolding.less | | | 52 | ++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/sprites.less | | | 193 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/tables.less | | | 237 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/thumbnails.less | | | 52 | ++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/tooltip.less | | | 70 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/type.less | | | 235 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/utilities.less | | | 30 | ++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/variables.less | | | 301 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/bootstrap/wells.less | | | 29 | +++++++++++++++++++++++++++++ |
| A | src/main/less/custom.less | | | 122 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/mediaelementplayer.less | | | 865 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/styles.less | | | 240 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/less/variables.less | | | 301 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/resources/default.logback.xml | | | 13 | +++++++++++++ |
| A | src/main/resources/production.default.logback.xml | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/resources/props/default.props | | | 14 | ++++++++++++++ |
| A | src/main/resources/props/production.default.props | | | 3 | +++ |
| A | src/main/scala/bootstrap/liftweb/Boot.scala | | | 92 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/api/ImageUpload.scala | | | 110 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/comet/UserSignUp.scala | | | 124 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/config/ErrorHandler.scala | | | 65 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/config/MongoConfig.scala | | | 51 | +++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/config/S3Config.scala | | | 13 | +++++++++++++ |
| A | src/main/scala/com/joereader/config/Site.scala | | | 101 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/config/SmtpMailer.scala | | | 71 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/lib/DependencyFactory.scala | | | 56 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/lib/Event.scala | | | 47 | +++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/lib/Helper.scala | | | 69 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/lib/VideoInfo.scala | | | 61 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/lib/aws/AmazonS3.scala | | | 145 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/lib/aws/S3.scala | | | 33 | +++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/lib/rss/Rss.scala | | | 211 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/model/BetaUser.scala | | | 27 | +++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/model/Blog.scala | | | 114 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/model/Category.scala | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/model/User.scala | | | 201 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/snippet/BlogSnips.scala | | | 150 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/snippet/LiftExtras.scala | | | 22 | ++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/snippet/SnipHelpers.scala | | | 71 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/snippet/UserSnips.scala | | | 251 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/snippet/signup/SignUp.scala | | | 28 | ++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/snippet/signup/SignUpBlog.scala | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/snippet/signup/SignUpCategories.scala | | | 85 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/snippet/signup/SignUpEmail.scala | | | 51 | +++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/snippet/signup/SignUpPassword.scala | | | 43 | +++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/snippet/signup/SignUpUser.scala | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/snippet/signup/SignUpVerification.scala | | | 162 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/scala/com/joereader/snippet/signup/SignUpWriter.scala | | | 34 | ++++++++++++++++++++++++++++++++++ |
| A | src/main/webapp/404.html | | | 3 | +++ |
| A | src/main/webapp/WEB-INF/web.xml | | | 21 | +++++++++++++++++++++ |
| A | src/main/webapp/about.html | | | 18 | ++++++++++++++++++ |
| A | src/main/webapp/blog.html | | | 4 | ++++ |
| A | src/main/webapp/error.html | | | 4 | ++++ |
| A | src/main/webapp/help/recovery.html | | | 27 | +++++++++++++++++++++++++++ |
| A | src/main/webapp/img/controls.png | | | 0 | |
| A | src/main/webapp/img/glyphicons-halflings-white.png | | | 0 | |
| A | src/main/webapp/img/glyphicons-halflings.png | | | 0 | |
| A | src/main/webapp/img/media/background.png | | | 0 | |
| A | src/main/webapp/img/media/controls.png | | | 0 | |
| A | src/main/webapp/img/media/controls.svg | | | 2 | ++ |
| A | src/main/webapp/img/media/echo-hereweare.webm | | | 0 | |
| A | src/main/webapp/img/media/flashmediaelement.swf | | | 0 | |
| A | src/main/webapp/img/media/mediaelement.srt | | | 62 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/webapp/img/media/silverlightmediaelement.xap | | | 0 | |
| A | src/main/webapp/img/spinner.gif | | | 0 | |
| A | src/main/webapp/index.html | | | 38 | ++++++++++++++++++++++++++++++++++++++ |
| A | src/main/webapp/login.html | | | 34 | ++++++++++++++++++++++++++++++++++ |
| A | src/main/webapp/reader.html | | | 3 | +++ |
| A | src/main/webapp/register.html | | | 4 | ++++ |
| A | src/main/webapp/robots.txt | | | 5 | +++++ |
| A | src/main/webapp/settings/account.html | | | 5 | +++++ |
| A | src/main/webapp/settings/password.html | | | 34 | ++++++++++++++++++++++++++++++++++ |
| A | src/main/webapp/settings/profile.html | | | 3 | +++ |
| A | src/main/webapp/signup/blog.html | | | 17 | +++++++++++++++++ |
| A | src/main/webapp/signup/categories.html | | | 29 | +++++++++++++++++++++++++++++ |
| A | src/main/webapp/signup/password.html | | | 20 | ++++++++++++++++++++ |
| A | src/main/webapp/signup/user.html | | | 17 | +++++++++++++++++ |
| A | src/main/webapp/signup/verify.html | | | 41 | +++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/webapp/signup/writer.html | | | 20 | ++++++++++++++++++++ |
| A | src/main/webapp/templates-hidden/base-wrap.html | | | 50 | ++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main/webapp/templates-hidden/default.html | | | 17 | +++++++++++++++++ |
| A | src/main/webapp/templates-hidden/parts/blog-profile.html | | | 17 | +++++++++++++++++ |
| A | src/main/webapp/templates-hidden/parts/footer.html | | | 4 | ++++ |
| A | src/main/webapp/templates-hidden/parts/user-profile.html | | | 16 | ++++++++++++++++ |
| A | src/main/webapp/templates-hidden/settings-wrap.html | | | 12 | ++++++++++++ |
| A | src/main/webapp/templates-hidden/wizard-all.html | | | 33 | +++++++++++++++++++++++++++++++++ |
| A | src/main/webapp/user.html | | | 4 | ++++ |
141 files changed, 13812 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore @@ -0,0 +1,28 @@ +# Lines starting with '#' are considered comments. +.DS_Store +.cache/ +Thumbs.db + +# backups and nohup +*~ +*.out +*.log + +# sbt +target/ +project/project +project/target + +#eclipse +.settings +.project +.classpath + +.cache +.scala_dependencies +.target/ +.save + +# intelliJ +.idea/ +.idea_modules/ diff --git a/README.md b/README.md @@ -0,0 +1,53 @@ +Twitter Bootstrap +----------------- + +Bootstrap v2.2.2 is included. + +MongoDB +------- + +This app uses MongoDB. Therefore, you will need to either have it installed locally, or use one of +the cloud providers and configure it in your props file. See config.MongoConfig for more info. + +Building +-------- + +This app requires sbt 0.12. To build for the first time, run: + + bash$ sbt + > ~;container:start; container:reload / + +That will start the app and automatically reload it whenever sources are modified. It will be running +on http://localhost:8080 + +Less +---- + +The [less-sbt](https://github.com/softprops/less-sbt) plugin is used to compile the bootstrap less files as well as your own. + +* `src/main/less/styles.less` acts a manifest file that lists all of the other less files that are to be included in the build. +* `src/main/less/variables.less` is for setting colors, fonts, etc. +* `src/main/less/custom.less` is where you can add custom styles for your app. +* `src/main/less/bootstrap` are the original files and should not be changed. + +You can also add other less files to the build by adding them to styles.less. + +**Note:** In order to trigger sbt to re-compile the less files, you need to save styles.less. + +JavaScript +---------- + +The [sbt-closure](https://github.com/eltimn/sbt-closure) plugin is used to compile JavaScript files. + +* `src/main/javascript/script.jsm` is the JavaScript manifest file that lists all of the JavaScript files that are to be included in the build. + +**Note:** In order to trigger sbt to re-compile the JavaScript files, you need to save script.jsm. + +User Model +---------- + +This app implements the [Mongoauth Lift Module](https://github.com/eltimn/lift-mongoauth). +The registration and login implementation is based on +[research done by Google](http://sites.google.com/site/oauthgoog/UXFedLogin) a few years ago +and is similar to Amazon.com and Buy.com. It's different than what most people seem to expect, +but it can easily be changed to suit your needs since most of the code is part of your project. diff --git a/project/Build.scala b/project/Build.scala @@ -0,0 +1,25 @@ +import sbt._ +import sbt.Keys._ + +object LiftProjectBuild extends Build { + + import BuildSettings._ + + lazy val root = Project("joe-reader", file(".")) + .settings(liftAppSettings: _*) + .settings(libraryDependencies ++= + Seq( + "net.liftweb" %% "lift-webkit" % Ver.lift % "compile", + "net.liftweb" %% "lift-mongodb-record" % Ver.lift % "compile", + "net.liftmodules" %% "mongoauth" % (Ver.lift+"-0.4") % "compile", + "net.liftmodules" %% ("extras_"+Ver.lift_edition) % "0.1" % "compile", + "org.eclipse.jetty" % "jetty-webapp" % Ver.jetty % "container", + "ch.qos.logback" % "logback-classic" % "1.0.6" % "compile", + "org.scalatest" %% "scalatest" % "1.9.1" % "test", + "net.databinder.dispatch" %% "dispatch-core" % "0.10.0", + "org.jsoup" % "jsoup" % "1.7.2", + "rome" % "rome" % "1.0", + "com.typesafe.akka" %% "akka-actor" % "2.1.4" + ) + ) +} diff --git a/project/Build.scala.save b/project/Build.scala.save @@ -0,0 +1,26 @@ +import sbt._ +import sbt.Keys._ + +object LiftProjectBuild extends Build { + + import BuildSettings._ + + lazy val root = Project("joe-reader", file(".")) + .settings(liftAppSettings: _*) + .settings(libraryDependencies ++= + Seq( + "net.liftweb" %% "lift-webkit" % Ver.lift % "compile", + "net.liftweb" %% "lift-mongodb-record" % Ver.lift % "compile", + "net.liftmodules" %% "mongoauth" % (Ver.lift+"-0.4") % "compile", + "net.liftmodules" %% ("extras_"+Ver.lift_edition) % "0.1" % "compile", + "org.eclipse.jetty" % "jetty-webapp" % Ver.jetty % "container", + "ch.qos.logback" % "logback-classic" % "1.0.6" % "compile", + "org.scalatest" %% "scalatest" % "1.9.1" % "test", + "net.databinder.dispatch" %% "dispatch-core" % "0.10.0", + "org.jsoup" % "jsoup" % "1.7.2", + "rome" % "rome" % "1.0", + + ) + ) +} +"com.typesafe.akka" %% "akka-actor" % "2.2-SNAPSHOT" diff --git a/project/BuildSettings.scala b/project/BuildSettings.scala @@ -0,0 +1,63 @@ +import sbt._ +import sbt.Keys._ + +import com.github.siasia.WebPlugin.{container, webSettings} +import com.github.siasia.PluginKeys._ +import sbtbuildinfo.Plugin._ +import less.Plugin._ +import sbtbuildinfo.Plugin._ +import sbtclosure.SbtClosurePlugin._ + +object BuildSettings { + object Ver { + val lift = "2.5-RC4" + val lift_edition = "2.5" + val jetty = "8.1.8.v20121106" + } + + val buildTime = SettingKey[String]("build-time") + + val basicSettings = Defaults.defaultSettings ++ Seq( + name := "joe-reader", + version := "0.1-SNAPSHOT", + organization := "Joe Reader", + scalaVersion := "2.10.0", + resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/", + scalacOptions <<= scalaVersion map { sv: String => + if (sv.startsWith("2.10.")) + Seq("-deprecation", "-unchecked", "-feature", "-language:postfixOps", "-language:implicitConversions") + else + Seq("-deprecation", "-unchecked") + } + ) + + val liftAppSettings = basicSettings ++ + webSettings ++ + buildInfoSettings ++ + lessSettings ++ + closureSettings ++ + seq( + buildTime := System.currentTimeMillis.toString, + + // build-info + buildInfoKeys ++= Seq[BuildInfoKey](buildTime), + buildInfoPackage := "com.joereader", + sourceGenerators in Compile <+= buildInfo, + + // less + (LessKeys.filter in (Compile, LessKeys.less)) := "*styles.less", + (LessKeys.mini in (Compile, LessKeys.less)) := true, + + // closure + (ClosureKeys.prettyPrint in (Compile, ClosureKeys.closure)) := false, + + // add managed resources, where less and closure publish to, to the webapp + (webappResources in Compile) <+= (resourceManaged in Compile) + ) + + lazy val noPublishing = seq( + publish := (), + publishLocal := () + ) +} + diff --git a/project/plugins.sbt b/project/plugins.sbt @@ -0,0 +1,9 @@ +addSbtPlugin("org.scala-sbt" % "sbt-closure" % "0.1.3") + +addSbtPlugin("me.lessis" % "less-sbt" % "0.1.10") + +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.2.2") + +addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.4.0") + +libraryDependencies <+= sbtVersion(v => "com.github.siasia" %% "xsbt-web-plugin" % ("0.12.0-0.2.11.1")) diff --git a/project/sbt-launch.jar b/project/sbt-launch.jar Binary files differ. diff --git a/sbt b/sbt @@ -0,0 +1 @@ +java -Xms512M -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=384M -jar `dirname $0`/project/sbt-launch.jar "$@" diff --git a/sbt-debug b/sbt-debug @@ -0,0 +1 @@ +java -Xms512M -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=384M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar project/sbt-launch.jar "$@" diff --git a/src/main/javascript/customs/fileupload.js b/src/main/javascript/customs/fileupload.js @@ -0,0 +1,55 @@ +$(function () { + $('#bg-fileupload').fileupload({ + dataType: 'json', + dropZone: null, + add: function (e,data) { + $('#bg-progress-bar').css('width', '0%'); + $('#bg-progress').show(); + data.submit(); + }, + progressall: function (e, data) { + var progress = parseInt(data.loaded / data.total * 100, 10) + '%'; + $('#bg-progress-bar').css('width', progress); + }, + done: function (e, data) { + $.each(data.files, function (index, file) { + $('<p/>').text(file.name).appendTo(document.body); + }); + $('#bg-progress').fadeOut(); + }, + fail: function (e, data) { + $('#bg-progress').hide(); + + $('div[data-alerts="alerts"]').append('<div class="alert alert-error fade in">'+ + '<button type="button" class="close" data-dismiss="alert">×</button>'+ + data.errorThrown+'</div>'); + } + }); + + $('#pic-fileupload').fileupload({ + dataType: 'json', + dropZone: null, + add: function (e,data) { + $('#pic-progress-bar').css('width', '0%'); + $('#pic-progress').show(); + data.submit(); + }, + progressall: function (e, data) { + var progress = parseInt(data.loaded / data.total * 100, 10) + '%'; + $('#pic-progress-bar').css('width', progress); + }, + done: function (e, data) { + $.each(data.files, function (index, file) { + $('<p/>').text(file.name).appendTo(document.body); + }); + $('#pic-progress').fadeOut(); + }, + fail: function (e, data) { + $('#pic-progress').hide(); + + $('div[data-alerts="alerts"]').append('<div class="alert alert-error fade in">'+ + '<button type="button" class="close" data-dismiss="alert">×</button>'+ + data.errorThrown+'</div>'); + } + }); + }); +\ No newline at end of file diff --git a/src/main/javascript/customs/other.js b/src/main/javascript/customs/other.js @@ -0,0 +1,12 @@ +$(function () { + + $('#intro-player').mediaelementplayer({ + pluginPath: '/img/media/', + features: ['progress','fullscreen'] + }); + + /* <time datetime="2008-01-28T20:24:17Z">January 1, 2008 20:24:17 GMT</time> */ + jQuery("time.timeago").timeago(); + jQuery.timeago.settings.allowFuture = true; +}); + diff --git a/src/main/javascript/libs/bootstrap-2.2.2.min.js b/src/main/javascript/libs/bootstrap-2.2.2.min.js @@ -0,0 +1,6 @@ +/*! +* Bootstrap.js by @fat & @mdo +* Copyright 2012 Twitter, Inc. +* http://www.apache.org/licenses/LICENSE-2.0.txt +*/ +!function($){"use strict";$(function(){$.support.transition=function(){var transitionEnd=function(){var name,el=document.createElement("bootstrap"),transEndEventNames={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(name in transEndEventNames)if(void 0!==el.style[name])return transEndEventNames[name]}();return transitionEnd&&{end:transitionEnd}}()})}(window.jQuery),!function($){"use strict";var dismiss='[data-dismiss="alert"]',Alert=function(el){$(el).on("click",dismiss,this.close)};Alert.prototype.close=function(e){function removeElement(){$parent.trigger("closed").remove()}var $parent,$this=$(this),selector=$this.attr("data-target");selector||(selector=$this.attr("href"),selector=selector&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),e&&e.preventDefault(),$parent.length||($parent=$this.hasClass("alert")?$this:$this.parent()),$parent.trigger(e=$.Event("close")),e.isDefaultPrevented()||($parent.removeClass("in"),$.support.transition&&$parent.hasClass("fade")?$parent.on($.support.transition.end,removeElement):removeElement())};var old=$.fn.alert;$.fn.alert=function(option){return this.each(function(){var $this=$(this),data=$this.data("alert");data||$this.data("alert",data=new Alert(this)),"string"==typeof option&&data[option].call($this)})},$.fn.alert.Constructor=Alert,$.fn.alert.noConflict=function(){return $.fn.alert=old,this},$(document).on("click.alert.data-api",dismiss,Alert.prototype.close)}(window.jQuery),!function($){"use strict";var Button=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.button.defaults,options)};Button.prototype.setState=function(state){var d="disabled",$el=this.$element,data=$el.data(),val=$el.is("input")?"val":"html";state+="Text",data.resetText||$el.data("resetText",$el[val]()),$el[val](data[state]||this.options[state]),setTimeout(function(){"loadingText"==state?$el.addClass(d).attr(d,d):$el.removeClass(d).removeAttr(d)},0)},Button.prototype.toggle=function(){var $parent=this.$element.closest('[data-toggle="buttons-radio"]');$parent&&$parent.find(".active").removeClass("active"),this.$element.toggleClass("active")};var old=$.fn.button;$.fn.button=function(option){return this.each(function(){var $this=$(this),data=$this.data("button"),options="object"==typeof option&&option;data||$this.data("button",data=new Button(this,options)),"toggle"==option?data.toggle():option&&data.setState(option)})},$.fn.button.defaults={loadingText:"loading..."},$.fn.button.Constructor=Button,$.fn.button.noConflict=function(){return $.fn.button=old,this},$(document).on("click.button.data-api","[data-toggle^=button]",function(e){var $btn=$(e.target);$btn.hasClass("btn")||($btn=$btn.closest(".btn")),$btn.button("toggle")})}(window.jQuery),!function($){"use strict";var Carousel=function(element,options){this.$element=$(element),this.options=options,"hover"==this.options.pause&&this.$element.on("mouseenter",$.proxy(this.pause,this)).on("mouseleave",$.proxy(this.cycle,this))};Carousel.prototype={cycle:function(e){return e||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval($.proxy(this.next,this),this.options.interval)),this},to:function(pos){var $active=this.$element.find(".item.active"),children=$active.parent().children(),activePos=children.index($active),that=this;if(!(pos>children.length-1||0>pos))return this.sliding?this.$element.one("slid",function(){that.to(pos)}):activePos==pos?this.pause().cycle():this.slide(pos>activePos?"next":"prev",$(children[pos]))},pause:function(e){return e||(this.paused=!0),this.$element.find(".next, .prev").length&&$.support.transition.end&&(this.$element.trigger($.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){return this.sliding?void 0:this.slide("next")},prev:function(){return this.sliding?void 0:this.slide("prev")},slide:function(type,next){var e,$active=this.$element.find(".item.active"),$next=next||$active[type](),isCycling=this.interval,direction="next"==type?"left":"right",fallback="next"==type?"first":"last",that=this;if(this.sliding=!0,isCycling&&this.pause(),$next=$next.length?$next:this.$element.find(".item")[fallback](),e=$.Event("slide",{relatedTarget:$next[0]}),!$next.hasClass("active")){if($.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(e),e.isDefaultPrevented())return;$next.addClass(type),$next[0].offsetWidth,$active.addClass(direction),$next.addClass(direction),this.$element.one($.support.transition.end,function(){$next.removeClass([type,direction].join(" ")).addClass("active"),$active.removeClass(["active",direction].join(" ")),that.sliding=!1,setTimeout(function(){that.$element.trigger("slid")},0)})}else{if(this.$element.trigger(e),e.isDefaultPrevented())return;$active.removeClass("active"),$next.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return isCycling&&this.cycle(),this}}};var old=$.fn.carousel;$.fn.carousel=function(option){return this.each(function(){var $this=$(this),data=$this.data("carousel"),options=$.extend({},$.fn.carousel.defaults,"object"==typeof option&&option),action="string"==typeof option?option:options.slide;data||$this.data("carousel",data=new Carousel(this,options)),"number"==typeof option?data.to(option):action?data[action]():options.interval&&data.cycle()})},$.fn.carousel.defaults={interval:5e3,pause:"hover"},$.fn.carousel.Constructor=Carousel,$.fn.carousel.noConflict=function(){return $.fn.carousel=old,this},$(document).on("click.carousel.data-api","[data-slide]",function(e){var href,$this=$(this),$target=$($this.attr("data-target")||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,"")),options=$.extend({},$target.data(),$this.data());$target.carousel(options),e.preventDefault()})}(window.jQuery),!function($){"use strict";var Collapse=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.collapse.defaults,options),this.options.parent&&(this.$parent=$(this.options.parent)),this.options.toggle&&this.toggle()};Collapse.prototype={constructor:Collapse,dimension:function(){var hasWidth=this.$element.hasClass("width");return hasWidth?"width":"height"},show:function(){var dimension,scroll,actives,hasData;if(!this.transitioning){if(dimension=this.dimension(),scroll=$.camelCase(["scroll",dimension].join("-")),actives=this.$parent&&this.$parent.find("> .accordion-group > .in"),actives&&actives.length){if(hasData=actives.data("collapse"),hasData&&hasData.transitioning)return;actives.collapse("hide"),hasData||actives.data("collapse",null)}this.$element[dimension](0),this.transition("addClass",$.Event("show"),"shown"),$.support.transition&&this.$element[dimension](this.$element[0][scroll])}},hide:function(){var dimension;this.transitioning||(dimension=this.dimension(),this.reset(this.$element[dimension]()),this.transition("removeClass",$.Event("hide"),"hidden"),this.$element[dimension](0))},reset:function(size){var dimension=this.dimension();return this.$element.removeClass("collapse")[dimension](size||"auto")[0].offsetWidth,this.$element[null!==size?"addClass":"removeClass"]("collapse"),this},transition:function(method,startEvent,completeEvent){var that=this,complete=function(){"show"==startEvent.type&&that.reset(),that.transitioning=0,that.$element.trigger(completeEvent)};this.$element.trigger(startEvent),startEvent.isDefaultPrevented()||(this.transitioning=1,this.$element[method]("in"),$.support.transition&&this.$element.hasClass("collapse")?this.$element.one($.support.transition.end,complete):complete())},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var old=$.fn.collapse;$.fn.collapse=function(option){return this.each(function(){var $this=$(this),data=$this.data("collapse"),options="object"==typeof option&&option;data||$this.data("collapse",data=new Collapse(this,options)),"string"==typeof option&&data[option]()})},$.fn.collapse.defaults={toggle:!0},$.fn.collapse.Constructor=Collapse,$.fn.collapse.noConflict=function(){return $.fn.collapse=old,this},$(document).on("click.collapse.data-api","[data-toggle=collapse]",function(e){var href,$this=$(this),target=$this.attr("data-target")||e.preventDefault()||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,""),option=$(target).data("collapse")?"toggle":$this.data();$this[$(target).hasClass("in")?"addClass":"removeClass"]("collapsed"),$(target).collapse(option)})}(window.jQuery),!function($){"use strict";function clearMenus(){$(toggle).each(function(){getParent($(this)).removeClass("open")})}function getParent($this){var $parent,selector=$this.attr("data-target");return selector||(selector=$this.attr("href"),selector=selector&&/#/.test(selector)&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),$parent.length||($parent=$this.parent()),$parent}var toggle="[data-toggle=dropdown]",Dropdown=function(element){var $el=$(element).on("click.dropdown.data-api",this.toggle);$("html").on("click.dropdown.data-api",function(){$el.parent().removeClass("open")})};Dropdown.prototype={constructor:Dropdown,toggle:function(){var $parent,isActive,$this=$(this);if(!$this.is(".disabled, :disabled"))return $parent=getParent($this),isActive=$parent.hasClass("open"),clearMenus(),isActive||$parent.toggleClass("open"),$this.focus(),!1},keydown:function(e){var $this,$items,$parent,isActive,index;if(/(38|40|27)/.test(e.keyCode)&&($this=$(this),e.preventDefault(),e.stopPropagation(),!$this.is(".disabled, :disabled"))){if($parent=getParent($this),isActive=$parent.hasClass("open"),!isActive||isActive&&27==e.keyCode)return $this.click();$items=$("[role=menu] li:not(.divider):visible a",$parent),$items.length&&(index=$items.index($items.filter(":focus")),38==e.keyCode&&index>0&&index--,40==e.keyCode&&$items.length-1>index&&index++,~index||(index=0),$items.eq(index).focus())}}};var old=$.fn.dropdown;$.fn.dropdown=function(option){return this.each(function(){var $this=$(this),data=$this.data("dropdown");data||$this.data("dropdown",data=new Dropdown(this)),"string"==typeof option&&data[option].call($this)})},$.fn.dropdown.Constructor=Dropdown,$.fn.dropdown.noConflict=function(){return $.fn.dropdown=old,this},$(document).on("click.dropdown.data-api touchstart.dropdown.data-api",clearMenus).on("click.dropdown touchstart.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("touchstart.dropdown.data-api",".dropdown-menu",function(e){e.stopPropagation()}).on("click.dropdown.data-api touchstart.dropdown.data-api",toggle,Dropdown.prototype.toggle).on("keydown.dropdown.data-api touchstart.dropdown.data-api",toggle+", [role=menu]",Dropdown.prototype.keydown)}(window.jQuery),!function($){"use strict";var Modal=function(element,options){this.options=options,this.$element=$(element).delegate('[data-dismiss="modal"]',"click.dismiss.modal",$.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};Modal.prototype={constructor:Modal,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var that=this,e=$.Event("show");this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.backdrop(function(){var transition=$.support.transition&&that.$element.hasClass("fade");that.$element.parent().length||that.$element.appendTo(document.body),that.$element.show(),transition&&that.$element[0].offsetWidth,that.$element.addClass("in").attr("aria-hidden",!1),that.enforceFocus(),transition?that.$element.one($.support.transition.end,function(){that.$element.focus().trigger("shown")}):that.$element.focus().trigger("shown")}))},hide:function(e){e&&e.preventDefault(),e=$.Event("hide"),this.$element.trigger(e),this.isShown&&!e.isDefaultPrevented()&&(this.isShown=!1,this.escape(),$(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),$.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal())},enforceFocus:function(){var that=this;$(document).on("focusin.modal",function(e){that.$element[0]===e.target||that.$element.has(e.target).length||that.$element.focus()})},escape:function(){var that=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(e){27==e.which&&that.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var that=this,timeout=setTimeout(function(){that.$element.off($.support.transition.end),that.hideModal()},500);this.$element.one($.support.transition.end,function(){clearTimeout(timeout),that.hideModal()})},hideModal:function(){this.$element.hide().trigger("hidden"),this.backdrop()},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(callback){var animate=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var doAnimate=$.support.transition&&animate;this.$backdrop=$('<div class="modal-backdrop '+animate+'" />').appendTo(document.body),this.$backdrop.click("static"==this.options.backdrop?$.proxy(this.$element[0].focus,this.$element[0]):$.proxy(this.hide,this)),doAnimate&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),doAnimate?this.$backdrop.one($.support.transition.end,callback):callback()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),$.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one($.support.transition.end,$.proxy(this.removeBackdrop,this)):this.removeBackdrop()):callback&&callback()}};var old=$.fn.modal;$.fn.modal=function(option){return this.each(function(){var $this=$(this),data=$this.data("modal"),options=$.extend({},$.fn.modal.defaults,$this.data(),"object"==typeof option&&option);data||$this.data("modal",data=new Modal(this,options)),"string"==typeof option?data[option]():options.show&&data.show()})},$.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},$.fn.modal.Constructor=Modal,$.fn.modal.noConflict=function(){return $.fn.modal=old,this},$(document).on("click.modal.data-api",'[data-toggle="modal"]',function(e){var $this=$(this),href=$this.attr("href"),$target=$($this.attr("data-target")||href&&href.replace(/.*(?=#[^\s]+$)/,"")),option=$target.data("modal")?"toggle":$.extend({remote:!/#/.test(href)&&href},$target.data(),$this.data());e.preventDefault(),$target.modal(option).one("hide",function(){$this.focus()})})}(window.jQuery),!function($){"use strict";var Tooltip=function(element,options){this.init("tooltip",element,options)};Tooltip.prototype={constructor:Tooltip,init:function(type,element,options){var eventIn,eventOut;this.type=type,this.$element=$(element),this.options=this.getOptions(options),this.enabled=!0,"click"==this.options.trigger?this.$element.on("click."+this.type,this.options.selector,$.proxy(this.toggle,this)):"manual"!=this.options.trigger&&(eventIn="hover"==this.options.trigger?"mouseenter":"focus",eventOut="hover"==this.options.trigger?"mouseleave":"blur",this.$element.on(eventIn+"."+this.type,this.options.selector,$.proxy(this.enter,this)),this.$element.on(eventOut+"."+this.type,this.options.selector,$.proxy(this.leave,this))),this.options.selector?this._options=$.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(options){return options=$.extend({},$.fn[this.type].defaults,options,this.$element.data()),options.delay&&"number"==typeof options.delay&&(options.delay={show:options.delay,hide:options.delay}),options},enter:function(e){var self=$(e.currentTarget)[this.type](this._options).data(this.type);return self.options.delay&&self.options.delay.show?(clearTimeout(this.timeout),self.hoverState="in",this.timeout=setTimeout(function(){"in"==self.hoverState&&self.show()},self.options.delay.show),void 0):self.show()},leave:function(e){var self=$(e.currentTarget)[this.type](this._options).data(this.type);return this.timeout&&clearTimeout(this.timeout),self.options.delay&&self.options.delay.hide?(self.hoverState="out",this.timeout=setTimeout(function(){"out"==self.hoverState&&self.hide()},self.options.delay.hide),void 0):self.hide()},show:function(){var $tip,inside,pos,actualWidth,actualHeight,placement,tp;if(this.hasContent()&&this.enabled){switch($tip=this.tip(),this.setContent(),this.options.animation&&$tip.addClass("fade"),placement="function"==typeof this.options.placement?this.options.placement.call(this,$tip[0],this.$element[0]):this.options.placement,inside=/in/.test(placement),$tip.detach().css({top:0,left:0,display:"block"}).insertAfter(this.$element),pos=this.getPosition(inside),actualWidth=$tip[0].offsetWidth,actualHeight=$tip[0].offsetHeight,inside?placement.split(" ")[1]:placement){case"bottom":tp={top:pos.top+pos.height,left:pos.left+pos.width/2-actualWidth/2};break;case"top":tp={top:pos.top-actualHeight,left:pos.left+pos.width/2-actualWidth/2};break;case"left":tp={top:pos.top+pos.height/2-actualHeight/2,left:pos.left-actualWidth};break;case"right":tp={top:pos.top+pos.height/2-actualHeight/2,left:pos.left+pos.width}}$tip.offset(tp).addClass(placement).addClass("in")}},setContent:function(){var $tip=this.tip(),title=this.getTitle();$tip.find(".tooltip-inner")[this.options.html?"html":"text"](title),$tip.removeClass("fade in top bottom left right")},hide:function(){function removeWithAnimation(){var timeout=setTimeout(function(){$tip.off($.support.transition.end).detach()},500);$tip.one($.support.transition.end,function(){clearTimeout(timeout),$tip.detach()})}var $tip=this.tip();return $tip.removeClass("in"),$.support.transition&&this.$tip.hasClass("fade")?removeWithAnimation():$tip.detach(),this},fixTitle:function(){var $e=this.$element;($e.attr("title")||"string"!=typeof $e.attr("data-original-title"))&&$e.attr("data-original-title",$e.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(inside){return $.extend({},inside?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var title,$e=this.$element,o=this.options;return title=$e.attr("data-original-title")||("function"==typeof o.title?o.title.call($e[0]):o.title)},tip:function(){return this.$tip=this.$tip||$(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(e){var self=$(e.currentTarget)[this.type](this._options).data(this.type);self[self.tip().hasClass("in")?"hide":"show"]()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var old=$.fn.tooltip;$.fn.tooltip=function(option){return this.each(function(){var $this=$(this),data=$this.data("tooltip"),options="object"==typeof option&&option;data||$this.data("tooltip",data=new Tooltip(this,options)),"string"==typeof option&&data[option]()})},$.fn.tooltip.Constructor=Tooltip,$.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover",title:"",delay:0,html:!1},$.fn.tooltip.noConflict=function(){return $.fn.tooltip=old,this}}(window.jQuery),!function($){"use strict";var Popover=function(element,options){this.init("popover",element,options)};Popover.prototype=$.extend({},$.fn.tooltip.Constructor.prototype,{constructor:Popover,setContent:function(){var $tip=this.tip(),title=this.getTitle(),content=this.getContent();$tip.find(".popover-title")[this.options.html?"html":"text"](title),$tip.find(".popover-content")[this.options.html?"html":"text"](content),$tip.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var content,$e=this.$element,o=this.options;return content=$e.attr("data-content")||("function"==typeof o.content?o.content.call($e[0]):o.content)},tip:function(){return this.$tip||(this.$tip=$(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var old=$.fn.popover;$.fn.popover=function(option){return this.each(function(){var $this=$(this),data=$this.data("popover"),options="object"==typeof option&&option;data||$this.data("popover",data=new Popover(this,options)),"string"==typeof option&&data[option]()})},$.fn.popover.Constructor=Popover,$.fn.popover.defaults=$.extend({},$.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"></div></div></div>'}),$.fn.popover.noConflict=function(){return $.fn.popover=old,this}}(window.jQuery),!function($){"use strict";function ScrollSpy(element,options){var href,process=$.proxy(this.process,this),$element=$(element).is("body")?$(window):$(element);this.options=$.extend({},$.fn.scrollspy.defaults,options),this.$scrollElement=$element.on("scroll.scroll-spy.data-api",process),this.selector=(this.options.target||(href=$(element).attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=$("body"),this.refresh(),this.process()}ScrollSpy.prototype={constructor:ScrollSpy,refresh:function(){var $targets,self=this;this.offsets=$([]),this.targets=$([]),$targets=this.$body.find(this.selector).map(function(){var $el=$(this),href=$el.data("target")||$el.attr("href"),$href=/^#\w/.test(href)&&$(href);return $href&&$href.length&&[[$href.position().top+self.$scrollElement.scrollTop(),href]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){self.offsets.push(this[0]),self.targets.push(this[1])})},process:function(){var i,scrollTop=this.$scrollElement.scrollTop()+this.options.offset,scrollHeight=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,maxScroll=scrollHeight-this.$scrollElement.height(),offsets=this.offsets,targets=this.targets,activeTarget=this.activeTarget;if(scrollTop>=maxScroll)return activeTarget!=(i=targets.last()[0])&&this.activate(i);for(i=offsets.length;i--;)activeTarget!=targets[i]&&scrollTop>=offsets[i]&&(!offsets[i+1]||offsets[i+1]>=scrollTop)&&this.activate(targets[i])},activate:function(target){var active,selector;this.activeTarget=target,$(this.selector).parent(".active").removeClass("active"),selector=this.selector+'[data-target="'+target+'"],'+this.selector+'[href="'+target+'"]',active=$(selector).parent("li").addClass("active"),active.parent(".dropdown-menu").length&&(active=active.closest("li.dropdown").addClass("active")),active.trigger("activate")}};var old=$.fn.scrollspy;$.fn.scrollspy=function(option){return this.each(function(){var $this=$(this),data=$this.data("scrollspy"),options="object"==typeof option&&option;data||$this.data("scrollspy",data=new ScrollSpy(this,options)),"string"==typeof option&&data[option]()})},$.fn.scrollspy.Constructor=ScrollSpy,$.fn.scrollspy.defaults={offset:10},$.fn.scrollspy.noConflict=function(){return $.fn.scrollspy=old,this},$(window).on("load",function(){$('[data-spy="scroll"]').each(function(){var $spy=$(this);$spy.scrollspy($spy.data())})})}(window.jQuery),!function($){"use strict";var Tab=function(element){this.element=$(element)};Tab.prototype={constructor:Tab,show:function(){var previous,$target,e,$this=this.element,$ul=$this.closest("ul:not(.dropdown-menu)"),selector=$this.attr("data-target");selector||(selector=$this.attr("href"),selector=selector&&selector.replace(/.*(?=#[^\s]*$)/,"")),$this.parent("li").hasClass("active")||(previous=$ul.find(".active:last a")[0],e=$.Event("show",{relatedTarget:previous}),$this.trigger(e),e.isDefaultPrevented()||($target=$(selector),this.activate($this.parent("li"),$ul),this.activate($target,$target.parent(),function(){$this.trigger({type:"shown",relatedTarget:previous})})))},activate:function(element,container,callback){function next(){$active.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),element.addClass("active"),transition?(element[0].offsetWidth,element.addClass("in")):element.removeClass("fade"),element.parent(".dropdown-menu")&&element.closest("li.dropdown").addClass("active"),callback&&callback()}var $active=container.find("> .active"),transition=callback&&$.support.transition&&$active.hasClass("fade");transition?$active.one($.support.transition.end,next):next(),$active.removeClass("in")}};var old=$.fn.tab;$.fn.tab=function(option){return this.each(function(){var $this=$(this),data=$this.data("tab");data||$this.data("tab",data=new Tab(this)),"string"==typeof option&&data[option]()})},$.fn.tab.Constructor=Tab,$.fn.tab.noConflict=function(){return $.fn.tab=old,this},$(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(e){e.preventDefault(),$(this).tab("show")})}(window.jQuery),!function($){"use strict";var Typeahead=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.typeahead.defaults,options),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=$(this.options.menu),this.shown=!1,this.listen()};Typeahead.prototype={constructor:Typeahead,select:function(){var val=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(val)).change(),this.hide()},updater:function(item){return item},show:function(){var pos=$.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:pos.top+pos.height,left:pos.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(){var items;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(items=$.isFunction(this.source)?this.source(this.query,$.proxy(this.process,this)):this.source,items?this.process(items):this)},process:function(items){var that=this;return items=$.grep(items,function(item){return that.matcher(item)}),items=this.sorter(items),items.length?this.render(items.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(item){return~item.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(items){for(var item,beginswith=[],caseSensitive=[],caseInsensitive=[];item=items.shift();)item.toLowerCase().indexOf(this.query.toLowerCase())?~item.indexOf(this.query)?caseSensitive.push(item):caseInsensitive.push(item):beginswith.push(item);return beginswith.concat(caseSensitive,caseInsensitive)},highlighter:function(item){var query=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return item.replace(RegExp("("+query+")","ig"),function($1,match){return"<strong>"+match+"</strong>"})},render:function(items){var that=this;return items=$(items).map(function(i,item){return i=$(that.options.item).attr("data-value",item),i.find("a").html(that.highlighter(item)),i[0]}),items.first().addClass("active"),this.$menu.html(items),this},next:function(){var active=this.$menu.find(".active").removeClass("active"),next=active.next();next.length||(next=$(this.$menu.find("li")[0])),next.addClass("active")},prev:function(){var active=this.$menu.find(".active").removeClass("active"),prev=active.prev();prev.length||(prev=this.$menu.find("li").last()),prev.addClass("active")},listen:function(){this.$element.on("blur",$.proxy(this.blur,this)).on("keypress",$.proxy(this.keypress,this)).on("keyup",$.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",$.proxy(this.keydown,this)),this.$menu.on("click",$.proxy(this.click,this)).on("mouseenter","li",$.proxy(this.mouseenter,this))},eventSupported:function(eventName){var isSupported=eventName in this.$element;return isSupported||(this.$element.setAttribute(eventName,"return;"),isSupported="function"==typeof this.$element[eventName]),isSupported},move:function(e){if(this.shown){switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()}},keydown:function(e){this.suppressKeyPressRepeat=~$.inArray(e.keyCode,[40,38,9,13,27]),this.move(e)},keypress:function(e){this.suppressKeyPressRepeat||this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},blur:function(){var that=this;setTimeout(function(){that.hide()},150)},click:function(e){e.stopPropagation(),e.preventDefault(),this.select()},mouseenter:function(e){this.$menu.find(".active").removeClass("active"),$(e.currentTarget).addClass("active")}};var old=$.fn.typeahead;$.fn.typeahead=function(option){return this.each(function(){var $this=$(this),data=$this.data("typeahead"),options="object"==typeof option&&option;data||$this.data("typeahead",data=new Typeahead(this,options)),"string"==typeof option&&data[option]()})},$.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},$.fn.typeahead.Constructor=Typeahead,$.fn.typeahead.noConflict=function(){return $.fn.typeahead=old,this},$(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(e){var $this=$(this);$this.data("typeahead")||(e.preventDefault(),$this.typeahead($this.data()))})}(window.jQuery),!function($){"use strict";var Affix=function(element,options){this.options=$.extend({},$.fn.affix.defaults,options),this.$window=$(window).on("scroll.affix.data-api",$.proxy(this.checkPosition,this)).on("click.affix.data-api",$.proxy(function(){setTimeout($.proxy(this.checkPosition,this),1)},this)),this.$element=$(element),this.checkPosition()};Affix.prototype.checkPosition=function(){if(this.$element.is(":visible")){var affix,scrollHeight=$(document).height(),scrollTop=this.$window.scrollTop(),position=this.$element.offset(),offset=this.options.offset,offsetBottom=offset.bottom,offsetTop=offset.top,reset="affix affix-top affix-bottom";"object"!=typeof offset&&(offsetBottom=offsetTop=offset),"function"==typeof offsetTop&&(offsetTop=offset.top()),"function"==typeof offsetBottom&&(offsetBottom=offset.bottom()),affix=null!=this.unpin&&scrollTop+this.unpin<=position.top?!1:null!=offsetBottom&&position.top+this.$element.height()>=scrollHeight-offsetBottom?"bottom":null!=offsetTop&&offsetTop>=scrollTop?"top":!1,this.affixed!==affix&&(this.affixed=affix,this.unpin="bottom"==affix?position.top-scrollTop:null,this.$element.removeClass(reset).addClass("affix"+(affix?"-"+affix:"")))}};var old=$.fn.affix;$.fn.affix=function(option){return this.each(function(){var $this=$(this),data=$this.data("affix"),options="object"==typeof option&&option;data||$this.data("affix",data=new Affix(this,options)),"string"==typeof option&&data[option]()})},$.fn.affix.Constructor=Affix,$.fn.affix.defaults={offset:0},$.fn.affix.noConflict=function(){return $.fn.affix=old,this},$(window).on("load",function(){$('[data-spy="affix"]').each(function(){var $spy=$(this),data=$spy.data();data.offset=data.offset||{},data.offsetBottom&&(data.offset.bottom=data.offsetBottom),data.offsetTop&&(data.offset.top=data.offsetTop),$spy.affix(data)})})}(window.jQuery); +\ No newline at end of file diff --git a/src/main/javascript/libs/jquery-1.8.3.min.js b/src/main/javascript/libs/jquery-1.8.3.min.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r<i;r++)v.event.add(t,n,u[n][r])}o.data&&(o.data=v.extend({},o.data))}function Ot(e,t){var n;if(t.nodeType!==1)return;t.clearAttributes&&t.clearAttributes(),t.mergeAttributes&&t.mergeAttributes(e),n=t.nodeName.toLowerCase(),n==="object"?(t.parentNode&&(t.outerHTML=e.outerHTML),v.support.html5Clone&&e.innerHTML&&!v.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):n==="input"&&Et.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):n==="option"?t.selected=e.defaultSelected:n==="input"||n==="textarea"?t.defaultValue=e.defaultValue:n==="script"&&t.text!==e.text&&(t.text=e.text),t.removeAttribute(v.expando)}function Mt(e){return typeof e.getElementsByTagName!="undefined"?e.getElementsByTagName("*"):typeof e.querySelectorAll!="undefined"?e.querySelectorAll("*"):[]}function _t(e){Et.test(e.type)&&(e.defaultChecked=e.checked)}function Qt(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Jt.length;while(i--){t=Jt[i]+n;if(t in e)return t}return r}function Gt(e,t){return e=t||e,v.css(e,"display")==="none"||!v.contains(e.ownerDocument,e)}function Yt(e,t){var n,r,i=[],s=0,o=e.length;for(;s<o;s++){n=e[s];if(!n.style)continue;i[s]=v._data(n,"olddisplay"),t?(!i[s]&&n.style.display==="none"&&(n.style.display=""),n.style.display===""&&Gt(n)&&(i[s]=v._data(n,"olddisplay",nn(n.nodeName)))):(r=Dt(n,"display"),!i[s]&&r!=="none"&&v._data(n,"olddisplay",r))}for(s=0;s<o;s++){n=e[s];if(!n.style)continue;if(!t||n.style.display==="none"||n.style.display==="")n.style.display=t?i[s]||"":"none"}return e}function Zt(e,t,n){var r=Rt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function en(e,t,n,r){var i=n===(r?"border":"content")?4:t==="width"?1:0,s=0;for(;i<4;i+=2)n==="margin"&&(s+=v.css(e,n+$t[i],!0)),r?(n==="content"&&(s-=parseFloat(Dt(e,"padding"+$t[i]))||0),n!=="margin"&&(s-=parseFloat(Dt(e,"border"+$t[i]+"Width"))||0)):(s+=parseFloat(Dt(e,"padding"+$t[i]))||0,n!=="padding"&&(s+=parseFloat(Dt(e,"border"+$t[i]+"Width"))||0));return s}function tn(e,t,n){var r=t==="width"?e.offsetWidth:e.offsetHeight,i=!0,s=v.support.boxSizing&&v.css(e,"boxSizing")==="border-box";if(r<=0||r==null){r=Dt(e,t);if(r<0||r==null)r=e.style[t];if(Ut.test(r))return r;i=s&&(v.support.boxSizingReliable||r===e.style[t]),r=parseFloat(r)||0}return r+en(e,t,n||(s?"border":"content"),i)+"px"}function nn(e){if(Wt[e])return Wt[e];var t=v("<"+e+">").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write("<!doctype html><html><body>"),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u<a;u++)r=o[u],s=/^\+/.test(r),s&&(r=r.substr(1)||"*"),i=e[r]=e[r]||[],i[s?"unshift":"push"](n)}}function kn(e,n,r,i,s,o){s=s||n.dataTypes[0],o=o||{},o[s]=!0;var u,a=e[s],f=0,l=a?a.length:0,c=e===Sn;for(;f<l&&(c||!u);f++)u=a[f](n,r,i),typeof u=="string"&&(!c||o[u]?u=t:(n.dataTypes.unshift(u),u=kn(e,n,r,i,u,o)));return(c||!u)&&!o["*"]&&(u=kn(e,n,r,i,"*",o)),u}function Ln(e,n){var r,i,s=v.ajaxSettings.flatOptions||{};for(r in n)n[r]!==t&&((s[r]?e:i||(i={}))[r]=n[r]);i&&v.extend(!0,e,i)}function An(e,n,r){var i,s,o,u,a=e.contents,f=e.dataTypes,l=e.responseFields;for(s in l)s in r&&(n[l[s]]=r[s]);while(f[0]==="*")f.shift(),i===t&&(i=e.mimeType||n.getResponseHeader("content-type"));if(i)for(s in a)if(a[s]&&a[s].test(i)){f.unshift(s);break}if(f[0]in r)o=f[0];else{for(s in r){if(!f[0]||e.converters[s+" "+f[0]]){o=s;break}u||(u=s)}o=o||u}if(o)return o!==f[0]&&f.unshift(o),r[o]}function On(e,t){var n,r,i,s,o=e.dataTypes.slice(),u=o[0],a={},f=0;e.dataFilter&&(t=e.dataFilter(t,e.dataType));if(o[1])for(n in e.converters)a[n.toLowerCase()]=e.converters[n];for(;i=o[++f];)if(i!=="*"){if(u!=="*"&&u!==i){n=a[u+" "+i]||a["* "+i];if(!n)for(r in a){s=r.split(" ");if(s[1]===i){n=a[u+" "+s[0]]||a["* "+s[0]];if(n){n===!0?n=a[r]:a[r]!==!0&&(i=s[0],o.splice(f--,0,i));break}}}if(n!==!0)if(n&&e["throws"])t=n(t);else try{t=n(t)}catch(l){return{state:"parsererror",error:n?l:"No conversion from "+u+" to "+i}}}u=i}return{state:"success",data:t}}function Fn(){try{return new e.XMLHttpRequest}catch(t){}}function In(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}function $n(){return setTimeout(function(){qn=t},0),qn=v.now()}function Jn(e,t){v.each(t,function(t,n){var r=(Vn[t]||[]).concat(Vn["*"]),i=0,s=r.length;for(;i<s;i++)if(r[i].call(e,t,n))return})}function Kn(e,t,n){var r,i=0,s=0,o=Xn.length,u=v.Deferred().always(function(){delete a.elem}),a=function(){var t=qn||$n(),n=Math.max(0,f.startTime+f.duration-t),r=n/f.duration||0,i=1-r,s=0,o=f.tweens.length;for(;s<o;s++)f.tweens[s].run(i);return u.notifyWith(e,[f,i,n]),i<1&&o?n:(u.resolveWith(e,[f]),!1)},f=u.promise({elem:e,props:v.extend({},t),opts:v.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:qn||$n(),duration:n.duration,tweens:[],createTween:function(t,n,r){var i=v.Tween(e,f.opts,t,n,f.opts.specialEasing[t]||f.opts.easing);return f.tweens.push(i),i},stop:function(t){var n=0,r=t?f.tweens.length:0;for(;n<r;n++)f.tweens[n].run(1);return t?u.resolveWith(e,[f,t]):u.rejectWith(e,[f,t]),this}}),l=f.props;Qn(l,f.opts.specialEasing);for(;i<o;i++){r=Xn[i].call(f,e,l,f.opts);if(r)return r}return Jn(f,l),v.isFunction(f.opts.start)&&f.opts.start.call(e,f),v.fx.timer(v.extend(a,{anim:f,queue:f.opts.queue,elem:e})),f.progress(f.opts.progress).done(f.opts.done,f.opts.complete).fail(f.opts.fail).always(f.opts.always)}function Qn(e,t){var n,r,i,s,o;for(n in e){r=v.camelCase(n),i=t[r],s=e[n],v.isArray(s)&&(i=s[1],s=e[n]=s[0]),n!==r&&(e[r]=s,delete e[n]),o=v.cssHooks[r];if(o&&"expand"in o){s=o.expand(s),delete e[r];for(n in s)n in e||(e[n]=s[n],t[n]=i)}else t[r]=i}}function Gn(e,t,n){var r,i,s,o,u,a,f,l,c,h=this,p=e.style,d={},m=[],g=e.nodeType&&Gt(e);n.queue||(l=v._queueHooks(e,"fx"),l.unqueued==null&&(l.unqueued=0,c=l.empty.fire,l.empty.fire=function(){l.unqueued||c()}),l.unqueued++,h.always(function(){h.always(function(){l.unqueued--,v.queue(e,"fx").length||l.empty.fire()})})),e.nodeType===1&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],v.css(e,"display")==="inline"&&v.css(e,"float")==="none"&&(!v.support.inlineBlockNeedsLayout||nn(e.nodeName)==="inline"?p.display="inline-block":p.zoom=1)),n.overflow&&(p.overflow="hidden",v.support.shrinkWrapBlocks||h.done(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t){s=t[r];if(Un.exec(s)){delete t[r],a=a||s==="toggle";if(s===(g?"hide":"show"))continue;m.push(r)}}o=m.length;if(o){u=v._data(e,"fxshow")||v._data(e,"fxshow",{}),"hidden"in u&&(g=u.hidden),a&&(u.hidden=!g),g?v(e).show():h.done(function(){v(e).hide()}),h.done(function(){var t;v.removeData(e,"fxshow",!0);for(t in d)v.style(e,t,d[t])});for(r=0;r<o;r++)i=m[r],f=h.createTween(i,g?u[i]:0),d[i]=u[i]||v.style(e,i),i in u||(u[i]=f.start,g&&(f.end=f.start,f.start=i==="width"||i==="height"?1:0))}}function Yn(e,t,n,r,i){return new Yn.prototype.init(e,t,n,r,i)}function Zn(e,t){var n,r={height:e},i=0;t=t?1:0;for(;i<4;i+=2-t)n=$t[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}function tr(e){return v.isWindow(e)?e:e.nodeType===9?e.defaultView||e.parentWindow:!1}var n,r,i=e.document,s=e.location,o=e.navigator,u=e.jQuery,a=e.$,f=Array.prototype.push,l=Array.prototype.slice,c=Array.prototype.indexOf,h=Object.prototype.toString,p=Object.prototype.hasOwnProperty,d=String.prototype.trim,v=function(e,t){return new v.fn.init(e,t,n)},m=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,g=/\S/,y=/\s+/,b=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,w=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a<f;a++)if((e=arguments[a])!=null)for(n in e){r=u[n],i=e[n];if(u===i)continue;l&&i&&(v.isPlainObject(i)||(s=v.isArray(i)))?(s?(s=!1,o=r&&v.isArray(r)?r:[]):o=r&&v.isPlainObject(r)?r:{},u[n]=v.extend(l,o,i)):i!==t&&(u[n]=i)}return u},v.extend({noConflict:function(t){return e.$===v&&(e.$=a),t&&e.jQuery===v&&(e.jQuery=u),v},isReady:!1,readyWait:1,holdReady:function(e){e?v.readyWait++:v.ready(!0)},ready:function(e){if(e===!0?--v.readyWait:v.isReady)return;if(!i.body)return setTimeout(v.ready,1);v.isReady=!0;if(e!==!0&&--v.readyWait>0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s<o;)if(n.apply(e[s++],r)===!1)break}else if(u){for(i in e)if(n.call(e[i],i,e[i])===!1)break}else for(;s<o;)if(n.call(e[s],s,e[s++])===!1)break;return e},trim:d&&!d.call("\ufeff\u00a0")?function(e){return e==null?"":d.call(e)}:function(e){return e==null?"":(e+"").replace(b,"")},makeArray:function(e,t){var n,r=t||[];return e!=null&&(n=v.type(e),e.length==null||n==="string"||n==="function"||n==="regexp"||v.isWindow(e)?f.call(r,e):v.merge(r,e)),r},inArray:function(e,t,n){var r;if(t){if(c)return c.call(t,e,n);r=t.length,n=n?n<0?Math.max(0,r+n):n:0;for(;n<r;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,s=0;if(typeof r=="number")for(;s<r;s++)e[i++]=n[s];else while(n[s]!==t)e[i++]=n[s++];return e.length=i,e},grep:function(e,t,n){var r,i=[],s=0,o=e.length;n=!!n;for(;s<o;s++)r=!!t(e[s],s),n!==r&&i.push(e[s]);return i},map:function(e,n,r){var i,s,o=[],u=0,a=e.length,f=e instanceof v||a!==t&&typeof a=="number"&&(a>0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u<a;u++)i=n(e[u],u,r),i!=null&&(o[o.length]=i);else for(s in e)i=n(e[s],s,r),i!=null&&(o[o.length]=i);return o.concat.apply([],o)},guid:1,proxy:function(e,n){var r,i,s;return typeof n=="string"&&(r=e[n],n=e,e=r),v.isFunction(e)?(i=l.call(arguments,2),s=function(){return e.apply(n,i.concat(l.call(arguments)))},s.guid=e.guid=e.guid||v.guid++,s):t},access:function(e,n,r,i,s,o,u){var a,f=r==null,l=0,c=e.length;if(r&&typeof r=="object"){for(l in r)v.access(e,n,l,r[l],1,o,i);s=1}else if(i!==t){a=u===t&&v.isFunction(i),f&&(a?(a=n,n=function(e,t,n){return a.call(v(e),n)}):(n.call(e,i),n=null));if(n)for(;l<c;l++)n(e[l],r,a?i.call(e[l],l,n(e[l],r)):i,u);s=1}return s?e:f?n.call(e):c?n(e[0],r):o},now:function(){return(new Date).getTime()}}),v.ready.promise=function(t){if(!r){r=v.Deferred();if(i.readyState==="complete")setTimeout(v.ready,1);else if(i.addEventListener)i.addEventListener("DOMContentLoaded",A,!1),e.addEventListener("load",v.ready,!1);else{i.attachEvent("onreadystatechange",A),e.attachEvent("onload",v.ready);var n=!1;try{n=e.frameElement==null&&i.documentElement}catch(s){}n&&n.doScroll&&function o(){if(!v.isReady){try{n.doScroll("left")}catch(e){return setTimeout(o,50)}v.ready()}}()}}return r.promise(t)},v.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(e,t){O["[object "+t+"]"]=t.toLowerCase()}),n=v(i);var M={};v.Callbacks=function(e){e=typeof e=="string"?M[e]||_(e):v.extend({},e);var n,r,i,s,o,u,a=[],f=!e.once&&[],l=function(t){n=e.memory&&t,r=!0,u=s||0,s=0,o=a.length,i=!0;for(;a&&u<o;u++)if(a[u].apply(t[0],t[1])===!1&&e.stopOnFalse){n=!1;break}i=!1,a&&(f?f.length&&l(f.shift()):n?a=[]:c.disable())},c={add:function(){if(a){var t=a.length;(function r(t){v.each(t,function(t,n){var i=v.type(n);i==="function"?(!e.unique||!c.has(n))&&a.push(n):n&&n.length&&i!=="string"&&r(n)})})(arguments),i?o=a.length:n&&(s=t,l(n))}return this},remove:function(){return a&&v.each(arguments,function(e,t){var n;while((n=v.inArray(t,a,n))>-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t<r;t++)n[t]&&v.isFunction(n[t].promise)?n[t].promise().done(o(t,f,n)).fail(s.reject).progress(o(t,a,u)):--i}return i||s.resolveWith(f,n),s.promise()}}),v.support=function(){var t,n,r,s,o,u,a,f,l,c,h,p=i.createElement("div");p.setAttribute("className","t"),p.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="<table><tr><td></td><td>t</td></tr></table>",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="<div></div>",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i<s;i++)delete r[t[i]];if(!(n?B:v.isEmptyObject)(r))return}}if(!n){delete u[a].data;if(!B(u[a]))return}o?v.cleanData([e],!0):v.support.deleteExpando||u!=u.window?delete u[a]:u[a]=null},_data:function(e,t,n){return v.data(e,t,n,!0)},acceptData:function(e){var t=e.nodeName&&v.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),v.fn.extend({data:function(e,n){var r,i,s,o,u,a=this[0],f=0,l=null;if(e===t){if(this.length){l=v.data(a);if(a.nodeType===1&&!v._data(a,"parsedAttrs")){s=a.attributes;for(u=s.length;f<u;f++)o=s[f].name,o.indexOf("data-")||(o=v.camelCase(o.substring(5)),H(a,o,l[o]));v._data(a,"parsedAttrs",!0)}}return l}return typeof e=="object"?this.each(function(){v.data(this,e)}):(r=e.split(".",2),r[1]=r[1]?"."+r[1]:"",i=r[1]+"!",v.access(this,function(n){if(n===t)return l=this.triggerHandler("getData"+i,[r[0]]),l===t&&a&&(l=v.data(a,e),l=H(a,e,l)),l===t&&r[1]?this.data(r[0]):l;r[1]=n,this.each(function(){var t=v(this);t.triggerHandler("setData"+i,r),v.data(this,e,n),t.triggerHandler("changeData"+i,r)})},null,n,arguments.length>1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length<r?v.queue(this[0],e):n===t?this:this.each(function(){var t=v.queue(this,e,n);v._queueHooks(this,e),e==="fx"&&t[0]!=="inprogress"&&v.dequeue(this,e)})},dequeue:function(e){return this.each(function(){v.dequeue(this,e)})},delay:function(e,t){return e=v.fx?v.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,s=v.Deferred(),o=this,u=this.length,a=function(){--i||s.resolveWith(o,[o])};typeof e!="string"&&(n=e,e=t),e=e||"fx";while(u--)r=v._data(o[u],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(a));return a(),s.promise(n)}});var j,F,I,q=/[\t\r\n]/g,R=/\r/g,U=/^(?:button|input)$/i,z=/^(?:button|input|object|select|textarea)$/i,W=/^a(?:rea|)$/i,X=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,V=v.support.getSetAttribute;v.fn.extend({attr:function(e,t){return v.access(this,v.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n<r;n++){i=this[n];if(i.nodeType===1)if(!i.className&&t.length===1)i.className=e;else{s=" "+i.className+" ";for(o=0,u=t.length;o<u;o++)s.indexOf(" "+t[o]+" ")<0&&(s+=t[o]+" ");i.className=v.trim(s)}}}return this},removeClass:function(e){var n,r,i,s,o,u,a;if(v.isFunction(e))return this.each(function(t){v(this).removeClass(e.call(this,t,this.className))});if(e&&typeof e=="string"||e===t){n=(e||"").split(y);for(u=0,a=this.length;u<a;u++){i=this[u];if(i.nodeType===1&&i.className){r=(" "+i.className+" ").replace(q," ");for(s=0,o=n.length;s<o;s++)while(r.indexOf(" "+n[s]+" ")>=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n<r;n++)if(this[n].nodeType===1&&(" "+this[n].className+" ").replace(q," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a<u;a++){n=r[a];if((n.selected||a===i)&&(v.support.optDisabled?!n.disabled:n.getAttribute("disabled")===null)&&(!n.parentNode.disabled||!v.nodeName(n.parentNode,"optgroup"))){t=v(n).val();if(s)return t;o.push(t)}}return o},set:function(e,t){var n=v.makeArray(t);return v(e).find("option").each(function(){this.selected=v.inArray(v(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o<r.length;o++)i=r[o],i&&(n=v.propFix[i]||i,s=X.test(i),s||v.attr(e,i,""),e.removeAttribute(V?i:n),s&&n in e&&(e[n]=!1))}},attrHooks:{type:{set:function(e,t){if(U.test(e.nodeName)&&e.parentNode)v.error("type property can't be changed");else if(!v.support.radioValue&&t==="radio"&&v.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}},value:{get:function(e,t){return j&&v.nodeName(e,"button")?j.get(e,t):t in e?e.value:null},set:function(e,t,n){if(j&&v.nodeName(e,"button"))return j.set(e,t,n);e.value=t}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,s,o,u=e.nodeType;if(!e||u===3||u===8||u===2)return;return o=u!==1||!v.isXMLDoc(e),o&&(n=v.propFix[n]||n,s=v.propHooks[n]),r!==t?s&&"set"in s&&(i=s.set(e,r,n))!==t?i:e[n]=r:s&&"get"in s&&(i=s.get(e,n))!==null?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):z.test(e.nodeName)||W.test(e.nodeName)&&e.href?0:t}}}}),F={get:function(e,n){var r,i=v.prop(e,n);return i===!0||typeof i!="boolean"&&(r=e.getAttributeNode(n))&&r.nodeValue!==!1?n.toLowerCase():t},set:function(e,t,n){var r;return t===!1?v.removeAttr(e,n):(r=v.propFix[n]||n,r in e&&(e[r]=!0),e.setAttribute(n,n.toLowerCase())),n}},V||(I={name:!0,id:!0,coords:!0},j=v.valHooks.button={get:function(e,n){var r;return r=e.getAttributeNode(n),r&&(I[n]?r.value!=="":r.specified)?r.value:t},set:function(e,t,n){var r=e.getAttributeNode(n);return r||(r=i.createAttribute(n),e.setAttributeNode(r)),r.value=t+""}},v.each(["width","height"],function(e,t){v.attrHooks[t]=v.extend(v.attrHooks[t],{set:function(e,n){if(n==="")return e.setAttribute(t,"auto"),n}})}),v.attrHooks.contenteditable={get:j.get,set:function(e,t,n){t===""&&(t="false"),j.set(e,t,n)}}),v.support.hrefNormalized||v.each(["href","src","width","height"],function(e,n){v.attrHooks[n]=v.extend(v.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return r===null?t:r}})}),v.support.style||(v.attrHooks.style={get:function(e){return e.style.cssText.toLowerCase()||t},set:function(e,t){return e.style.cssText=t+""}}),v.support.optSelected||(v.propHooks.selected=v.extend(v.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),v.support.enctype||(v.propFix.enctype="encoding"),v.support.checkOn||v.each(["radio","checkbox"],function(){v.valHooks[this]={get:function(e){return e.getAttribute("value")===null?"on":e.value}}}),v.each(["radio","checkbox"],function(){v.valHooks[this]=v.extend(v.valHooks[this],{set:function(e,t){if(v.isArray(t))return e.checked=v.inArray(v(e).val(),t)>=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f<n.length;f++){l=J.exec(n[f])||[],c=l[1],h=(l[2]||"").split(".").sort(),g=v.event.special[c]||{},c=(s?g.delegateType:g.bindType)||c,g=v.event.special[c]||{},p=v.extend({type:c,origType:l[1],data:i,handler:r,guid:r.guid,selector:s,needsContext:s&&v.expr.match.needsContext.test(s),namespace:h.join(".")},d),m=a[c];if(!m){m=a[c]=[],m.delegateCount=0;if(!g.setup||g.setup.call(e,i,h,u)===!1)e.addEventListener?e.addEventListener(c,u,!1):e.attachEvent&&e.attachEvent("on"+c,u)}g.add&&(g.add.call(e,p),p.handler.guid||(p.handler.guid=r.guid)),s?m.splice(m.delegateCount++,0,p):m.push(p),v.event.global[c]=!0}e=null},global:{},remove:function(e,t,n,r,i){var s,o,u,a,f,l,c,h,p,d,m,g=v.hasData(e)&&v._data(e);if(!g||!(h=g.events))return;t=v.trim(Z(t||"")).split(" ");for(s=0;s<t.length;s++){o=J.exec(t[s])||[],u=a=o[1],f=o[2];if(!u){for(u in h)v.event.remove(e,u+t[s],n,r,!0);continue}p=v.event.special[u]||{},u=(r?p.delegateType:p.bindType)||u,d=h[u]||[],l=d.length,f=f?new RegExp("(^|\\.)"+f.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(c=0;c<d.length;c++)m=d[c],(i||a===m.origType)&&(!n||n.guid===m.guid)&&(!f||f.test(m.namespace))&&(!r||r===m.selector||r==="**"&&m.selector)&&(d.splice(c--,1),m.selector&&d.delegateCount--,p.remove&&p.remove.call(e,m));d.length===0&&l!==d.length&&((!p.teardown||p.teardown.call(e,f,g.handle)===!1)&&v.removeEvent(e,u,g.handle),delete h[u])}v.isEmptyObject(h)&&(delete g.handle,v.removeData(e,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(n,r,s,o){if(!s||s.nodeType!==3&&s.nodeType!==8){var u,a,f,l,c,h,p,d,m,g,y=n.type||n,b=[];if(Y.test(y+v.event.triggered))return;y.indexOf("!")>=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f<m.length&&!n.isPropagationStopped();f++)l=m[f][0],n.type=m[f][1],d=(v._data(l,"events")||{})[n.type]&&v._data(l,"handle"),d&&d.apply(l,r),d=h&&l[h],d&&v.acceptData(l)&&d.apply&&d.apply(l,r)===!1&&n.preventDefault();return n.type=y,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(s.ownerDocument,r)===!1)&&(y!=="click"||!v.nodeName(s,"a"))&&v.acceptData(s)&&h&&s[y]&&(y!=="focus"&&y!=="blur"||n.target.offsetWidth!==0)&&!v.isWindow(s)&&(c=s[h],c&&(s[h]=null),v.event.triggered=y,s[y](),v.event.triggered=t,c&&(s[h]=c)),n.result}return},dispatch:function(n){n=v.event.fix(n||e.event);var r,i,s,o,u,a,f,c,h,p,d=(v._data(this,"events")||{})[n.type]||[],m=d.delegateCount,g=l.call(arguments),y=!n.exclusive&&!n.namespace,b=v.event.special[n.type]||{},w=[];g[0]=n,n.delegateTarget=this;if(b.preDispatch&&b.preDispatch.call(this,n)===!1)return;if(m&&(!n.button||n.type!=="click"))for(s=n.target;s!=this;s=s.parentNode||this)if(s.disabled!==!0||n.type!=="click"){u={},f=[];for(r=0;r<m;r++)c=d[r],h=c.selector,u[h]===t&&(u[h]=c.needsContext?v(h,this).index(s)>=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r<w.length&&!n.isPropagationStopped();r++){a=w[r],n.currentTarget=a.elem;for(i=0;i<a.matches.length&&!n.isImmediatePropagationStopped();i++){c=a.matches[i];if(y||!n.namespace&&!c.namespace||n.namespace_re&&n.namespace_re.test(c.namespace))n.data=c.data,n.handleObj=c,o=((v.event.special[c.origType]||{}).handle||c.handler).apply(a.elem,g),o!==t&&(n.result=o,o===!1&&(n.preventDefault(),n.stopPropagation()))}}return b.postDispatch&&b.postDispatch.call(this,n),n.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return e.which==null&&(e.which=t.charCode!=null?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,s,o,u=n.button,a=n.fromElement;return e.pageX==null&&n.clientX!=null&&(r=e.target.ownerDocument||i,s=r.documentElement,o=r.body,e.pageX=n.clientX+(s&&s.scrollLeft||o&&o.scrollLeft||0)-(s&&s.clientLeft||o&&o.clientLeft||0),e.pageY=n.clientY+(s&&s.scrollTop||o&&o.scrollTop||0)-(s&&s.clientTop||o&&o.clientTop||0)),!e.relatedTarget&&a&&(e.relatedTarget=a===e.target?n.toElement:a),!e.which&&u!==t&&(e.which=u&1?1:u&2?3:u&4?2:0),e}},fix:function(e){if(e[v.expando])return e;var t,n,r=e,s=v.event.fixHooks[e.type]||{},o=s.props?this.props.concat(s.props):this.props;e=v.Event(r);for(t=o.length;t;)n=o[--t],e[n]=r[n];return e.target||(e.target=r.srcElement||i),e.target.nodeType===3&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,r):e},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(e,t,n){v.isWindow(this)&&(this.onbeforeunload=n)},teardown:function(e,t){this.onbeforeunload===t&&(this.onbeforeunload=null)}}},simulate:function(e,t,n,r){var i=v.extend(new v.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?v.event.trigger(i,null,t):v.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},v.event.handle=v.event.dispatch,v.removeEvent=i.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]=="undefined"&&(e[r]=null),e.detachEvent(r,n))},v.Event=function(e,t){if(!(this instanceof v.Event))return new v.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?tt:et):this.type=e,t&&v.extend(this,t),this.timeStamp=e&&e.timeStamp||v.now(),this[v.expando]=!0},v.Event.prototype={preventDefault:function(){this.isDefaultPrevented=tt;var e=this.originalEvent;if(!e)return;e.preventDefault?e.preventDefault():e.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=tt;var e=this.originalEvent;if(!e)return;e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=tt,this.stopPropagation()},isDefaultPrevented:et,isPropagationStopped:et,isImmediatePropagationStopped:et},v.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){v.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,s=e.handleObj,o=s.selector;if(!i||i!==r&&!v.contains(r,i))e.type=s.origType,n=s.handler.apply(this,arguments),e.type=t;return n}}}),v.support.submitBubbles||(v.event.special.submit={setup:function(){if(v.nodeName(this,"form"))return!1;v.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=v.nodeName(n,"input")||v.nodeName(n,"button")?n.form:t;r&&!v._data(r,"_submit_attached")&&(v.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),v._data(r,"_submit_attached",!0))})},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&v.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){if(v.nodeName(this,"form"))return!1;v.event.remove(this,"._submit")}}),v.support.changeBubbles||(v.event.special.change={setup:function(){if($.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")v.event.add(this,"propertychange._change",function(e){e.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),v.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),v.event.simulate("change",this,e,!0)});return!1}v.event.add(this,"beforeactivate._change",function(e){var t=e.target;$.test(t.nodeName)&&!v._data(t,"_change_attached")&&(v.event.add(t,"change._change",function(e){this.parentNode&&!e.isSimulated&&!e.isTrigger&&v.event.simulate("change",this.parentNode,e,!0)}),v._data(t,"_change_attached",!0))})},handle:function(e){var t=e.target;if(this!==t||e.isSimulated||e.isTrigger||t.type!=="radio"&&t.type!=="checkbox")return e.handleObj.handler.apply(this,arguments)},teardown:function(){return v.event.remove(this,"._change"),!$.test(this.nodeName)}}),v.support.focusinBubbles||v.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){v.event.simulate(t,e.target,v.event.fix(e),!0)};v.event.special[t]={setup:function(){n++===0&&i.addEventListener(e,r,!0)},teardown:function(){--n===0&&i.removeEventListener(e,r,!0)}}}),v.fn.extend({on:function(e,n,r,i,s){var o,u;if(typeof e=="object"){typeof n!="string"&&(r=r||n,n=t);for(u in e)this.on(u,n,r,e[u],s);return this}r==null&&i==null?(i=n,r=n=t):i==null&&(typeof n=="string"?(i=r,r=t):(i=r,r=n,n=t));if(i===!1)i=et;else if(!i)return this;return s===1&&(o=i,i=function(e){return v().off(e),o.apply(this,arguments)},i.guid=o.guid||(o.guid=v.guid++)),this.each(function(){v.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,s;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,v(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if(typeof e=="object"){for(s in e)this.off(s,n,e[s]);return this}if(n===!1||typeof n=="function")r=n,n=t;return r===!1&&(r=et),this.each(function(){v.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},live:function(e,t,n){return v(this.context).on(e,this.selector,t,n),this},die:function(e,t){return v(this.context).off(e,this.selector||"**",t),this},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return arguments.length===1?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){v.event.trigger(e,t,this)})},triggerHandler:function(e,t){if(this[0])return v.event.trigger(e,t,this[0],!0)},toggle:function(e){var t=arguments,n=e.guid||v.guid++,r=0,i=function(n){var i=(v._data(this,"lastToggle"+e.guid)||0)%r;return v._data(this,"lastToggle"+e.guid,i+1),n.preventDefault(),t[i].apply(this,arguments)||!1};i.guid=n;while(r<t.length)t[r++].guid=n;return this.click(i)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),v.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){v.fn[t]=function(e,n){return n==null&&(n=e,e=null),arguments.length>0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u<a;u++)if(s=e[u])if(!n||n(s,r,i))o.push(s),f&&t.push(u);return o}function ct(e,t,n,r,i,s){return r&&!r[d]&&(r=ct(r)),i&&!i[d]&&(i=ct(i,s)),N(function(s,o,u,a){var f,l,c,h=[],p=[],d=o.length,v=s||dt(t||"*",u.nodeType?[u]:u,[]),m=e&&(s||!t)?lt(v,h,e,u,a):v,g=n?i||(s?e:d||r)?[]:o:m;n&&n(m,g,u,a);if(r){f=lt(g,p),r(f,[],u,a),l=f.length;while(l--)if(c=f[l])g[p[l]]=!(m[p[l]]=c)}if(s){if(i||e){if(i){f=[],l=g.length;while(l--)(c=g[l])&&f.push(m[l]=c);i(null,g=[],f,a)}l=g.length;while(l--)(c=g[l])&&(f=i?T.call(s,c):h[l])>-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a<s;a++)if(n=i.relative[e[a].type])h=[at(ft(h),n)];else{n=i.filter[e[a].type].apply(null,e[a].matches);if(n[d]){r=++a;for(;r<s;r++)if(i.relative[e[r].type])break;return ct(a>1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a<r&&ht(e.slice(a,r)),r<s&&ht(e=e.slice(r)),r<s&&e.join(""))}h.push(n)}return ft(h)}function pt(e,t){var r=t.length>0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r<i;r++)nt(e,t[r],n);return n}function vt(e,t,n,r,s){var o,u,f,l,c,h=ut(e),p=h.length;if(!r&&h.length===1){u=h[0]=h[0].slice(0);if(u.length>2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;t<n;t++)if(this[t]===e)return t;return-1},N=function(e,t){return e[d]=t==null||t,e},C=function(){var e={},t=[];return N(function(n,r){return t.push(n)>i.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="<a name='"+d+"'></a><div name='"+d+"'></div>",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:st(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:st(function(e,t,n){for(var r=n<0?n+t:n;--r>=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}},f=y.compareDocumentPosition?function(e,t){return e===t?(l=!0,0):(!e.compareDocumentPosition||!t.compareDocumentPosition?e.compareDocumentPosition:e.compareDocumentPosition(t)&4)?-1:1}:function(e,t){if(e===t)return l=!0,0;if(e.sourceIndex&&t.sourceIndex)return e.sourceIndex-t.sourceIndex;var n,r,i=[],s=[],o=e.parentNode,u=t.parentNode,a=o;if(o===u)return ot(e,t);if(!o)return-1;if(!u)return 1;while(a)i.unshift(a),a=a.parentNode;a=u;while(a)s.unshift(a),a=a.parentNode;n=i.length,r=s.length;for(var f=0;f<n&&f<r;f++)if(i[f]!==s[f])return ot(i[f],s[f]);return f===n?ot(e,s[f],-1):ot(i[f],t,1)},[0,0].sort(f),h=!l,nt.uniqueSort=function(e){var t,n=[],r=1,i=0;l=h,e.sort(f);if(l){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e},nt.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},a=nt.compile=function(e,t){var n,r=[],i=[],s=A[d][e+" "];if(!s){t||(t=ut(e)),n=t.length;while(n--)s=ht(t[n]),s[d]?r.push(s):i.push(s);s=A(e,pt(i,r))}return s},g.querySelectorAll&&function(){var e,t=vt,n=/'|\\/g,r=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,i=[":focus"],s=[":active"],u=y.matchesSelector||y.mozMatchesSelector||y.webkitMatchesSelector||y.oMatchesSelector||y.msMatchesSelector;K(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="<p test=''></p>",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="<input type='hidden'/>",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t<n;t++)if(v.contains(u[t],this))return!0});o=this.pushStack("","find",e);for(t=0,n=this.length;t<n;t++){r=o.length,v.find(e,this[t],o);if(t>0)for(i=r;i<o.length;i++)for(s=0;s<r;s++)if(o[s]===o[i]){o.splice(i--,1);break}}return o},has:function(e){var t,n=v(e,this),r=n.length;return this.filter(function(){for(t=0;t<r;t++)if(v.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1),"not",e)},filter:function(e){return this.pushStack(ft(this,e,!0),"filter",e)},is:function(e){return!!e&&(typeof e=="string"?st.test(e)?v(e,this.context).index(this[0])>=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r<i;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&n.nodeType!==11){if(o?o.index(n)>-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/<tbody/i,gt=/<|&#?\w+;/,yt=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,wt=new RegExp("<(?:"+ct+")[\\s/>]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,Nt={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X<div>","</div>"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1></$2>");try{for(;r<i;r++)n=this[r]||{},n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),n.innerHTML=e);n=0}catch(s){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){return ut(this[0])?this.length?this.pushStack(v(v.isFunction(e)?e():e),"replaceWith",e):this:v.isFunction(e)?this.each(function(t){var n=v(this),r=n.html();n.replaceWith(e.call(this,t,r))}):(typeof e!="string"&&(e=v(e).detach()),this.each(function(){var t=this.nextSibling,n=this.parentNode;v(this).remove(),t?v(t).before(e):v(n).append(e)}))},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=[].concat.apply([],e);var i,s,o,u,a=0,f=e[0],l=[],c=this.length;if(!v.support.checkClone&&c>1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a<c;a++)r.call(n&&v.nodeName(this[a],"table")?Lt(this[a],"tbody"):this[a],a===u?o:v.clone(o,!0,!0))}o=s=null,l.length&&v.each(l,function(e,t){t.src?v.ajax?v.ajax({url:t.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):v.error("no ajax"):v.globalEval((t.text||t.textContent||t.innerHTML||"").replace(Tt,"")),t.parentNode&&t.parentNode.removeChild(t)})}return this}}),v.buildFragment=function(e,n,r){var s,o,u,a=e[0];return n=n||i,n=!n.nodeType&&n[0]||n,n=n.ownerDocument||n,e.length===1&&typeof a=="string"&&a.length<512&&n===i&&a.charAt(0)==="<"&&!bt.test(a)&&(v.support.checkClone||!St.test(a))&&(v.support.html5Clone||!wt.test(a))&&(o=!0,s=v.fragments[a],u=s!==t),s||(s=n.createDocumentFragment(),v.clean(e,n,s,r),o&&(v.fragments[a]=u&&s)),{fragment:s,cacheable:o}},v.fragments={},v.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){v.fn[e]=function(n){var r,i=0,s=[],o=v(n),u=o.length,a=this.length===1&&this[0].parentNode;if((a==null||a&&a.nodeType===11&&a.childNodes.length===1)&&u===1)return o[t](this[0]),this;for(;i<u;i++)r=(i>0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1></$2>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]==="<table>"&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("<div>").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r<i;r++)n=e[r],Vn[n]=Vn[n]||[],Vn[n].unshift(t)},prefilter:function(e,t){t?Xn.unshift(e):Xn.push(e)}}),v.Tween=Yn,Yn.prototype={constructor:Yn,init:function(e,t,n,r,i,s){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=s||(v.cssNumber[n]?"":"px")},cur:function(){var e=Yn.propHooks[this.prop];return e&&e.get?e.get(this):Yn.propHooks._default.get(this)},run:function(e){var t,n=Yn.propHooks[this.prop];return this.options.duration?this.pos=t=v.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Yn.propHooks._default.set(this),this}},Yn.prototype.init.prototype=Yn.prototype,Yn.propHooks={_default:{get:function(e){var t;return e.elem[e.prop]==null||!!e.elem.style&&e.elem.style[e.prop]!=null?(t=v.css(e.elem,e.prop,!1,""),!t||t==="auto"?0:t):e.elem[e.prop]},set:function(e){v.fx.step[e.prop]?v.fx.step[e.prop](e):e.elem.style&&(e.elem.style[v.cssProps[e.prop]]!=null||v.cssHooks[e.prop])?v.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},Yn.propHooks.scrollTop=Yn.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},v.each(["toggle","show","hide"],function(e,t){var n=v.fn[t];v.fn[t]=function(r,i,s){return r==null||typeof r=="boolean"||!e&&v.isFunction(r)&&v.isFunction(i)?n.apply(this,arguments):this.animate(Zn(t,!0),r,i,s)}}),v.fn.extend({fadeTo:function(e,t,n,r){return this.filter(Gt).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=v.isEmptyObject(e),s=v.speed(t,n,r),o=function(){var t=Kn(this,v.extend({},e),s);i&&t.stop(!0)};return i||s.queue===!1?this.each(o):this.queue(s.queue,o)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return typeof e!="string"&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=e!=null&&e+"queueHooks",s=v.timers,o=v._data(this);if(n)o[n]&&o[n].stop&&i(o[n]);else for(n in o)o[n]&&o[n].stop&&Wn.test(n)&&i(o[n]);for(n=s.length;n--;)s[n].elem===this&&(e==null||s[n].queue===e)&&(s[n].anim.stop(r),t=!1,s.splice(n,1));(t||!r)&&v.dequeue(this,e)})}}),v.each({slideDown:Zn("show"),slideUp:Zn("hide"),slideToggle:Zn("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){v.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),v.speed=function(e,t,n){var r=e&&typeof e=="object"?v.extend({},e):{complete:n||!n&&t||v.isFunction(e)&&e,duration:e,easing:n&&t||t&&!v.isFunction(t)&&t};r.duration=v.fx.off?0:typeof r.duration=="number"?r.duration:r.duration in v.fx.speeds?v.fx.speeds[r.duration]:v.fx.speeds._default;if(r.queue==null||r.queue===!0)r.queue="fx";return r.old=r.complete,r.complete=function(){v.isFunction(r.old)&&r.old.call(this),r.queue&&v.dequeue(this,r.queue)},r},v.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},v.timers=[],v.fx=Yn.prototype.init,v.fx.tick=function(){var e,n=v.timers,r=0;qn=v.now();for(;r<n.length;r++)e=n[r],!e()&&n[r]===e&&n.splice(r--,1);n.length||v.fx.stop(),qn=t},v.fx.timer=function(e){e()&&v.timers.push(e)&&!Rn&&(Rn=setInterval(v.fx.tick,v.fx.interval))},v.fx.interval=13,v.fx.stop=function(){clearInterval(Rn),Rn=null},v.fx.speeds={slow:600,fast:200,_default:400},v.fx.step={},v.expr&&v.expr.filters&&(v.expr.filters.animated=function(e){return v.grep(v.timers,function(t){return e===t.elem}).length});var er=/^(?:body|html)$/i;v.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){v.offset.setOffset(this,e,t)});var n,r,i,s,o,u,a,f={top:0,left:0},l=this[0],c=l&&l.ownerDocument;if(!c)return;return(r=c.body)===l?v.offset.bodyOffset(l):(n=c.documentElement,v.contains(n,l)?(typeof l.getBoundingClientRect!="undefined"&&(f=l.getBoundingClientRect()),i=tr(c),s=n.clientTop||r.clientTop||0,o=n.clientLeft||r.clientLeft||0,u=i.pageYOffset||n.scrollTop,a=i.pageXOffset||n.scrollLeft,{top:f.top+u-s,left:f.left+a-o}):f)},v.offset={bodyOffset:function(e){var t=e.offsetTop,n=e.offsetLeft;return v.support.doesNotIncludeMarginInBodyOffset&&(t+=parseFloat(v.css(e,"marginTop"))||0,n+=parseFloat(v.css(e,"marginLeft"))||0),{top:t,left:n}},setOffset:function(e,t,n){var r=v.css(e,"position");r==="static"&&(e.style.position="relative");var i=v(e),s=i.offset(),o=v.css(e,"top"),u=v.css(e,"left"),a=(r==="absolute"||r==="fixed")&&v.inArray("auto",[o,u])>-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); +\ No newline at end of file diff --git a/src/main/javascript/libs/jquery.bsAlerts.min.js b/src/main/javascript/libs/jquery.bsAlerts.min.js @@ -0,0 +1,7 @@ +/*! bsAlerts version: 0.1.1 +* 2013-04-19 +* Author: Tim Nelson +* Website: http://eltimn.github.com/jquery-bs-alerts +* MIT License http://www.opensource.org/licenses/mit-license.php +*/ +(function(t,n,r){"use strict";function e(n){return{errs:t.grep(n,function(t){return"error"===t.priority||"danger"===t.priority}),warns:t.grep(n,function(t){return"warning"===t.priority}),infos:t.grep(n,function(t){return"notice"===t.priority||"info"===t.priority}),succs:t.grep(n,function(t){return"success"===t.priority})}}function a(t){return"notice"===t?"info":"danger"===t?"error":t}var i=function(n,i){var s,o=this;o.element=n,o.options=t.extend({},t.fn.bsAlerts.defaults,i),t(r).on("add-alerts",function(){var t=Array.prototype.slice.call(arguments,1);o.addAlerts(t)}),t(r).on("clear-alerts",function(){o.clearAlerts()}),t.each(this.options.ids.split(","),function(n,e){var a=e.trim();if(a.length>0){var i="set-alert-id-"+a;t(r).on(i,function(){var t=Array.prototype.slice.call(arguments,1);o.addAlerts(t)})}}),o.clearAlerts=function(){t(this.element).html("")},o.addAlerts=function(t){var n=e([].concat(t));o.addAlertsToContainer(n.errs),o.addAlertsToContainer(n.warns),o.addAlertsToContainer(n.infos),o.addAlertsToContainer(n.succs);var r=parseInt(o.options.fade,10);!isNaN(r)&&r>0&&(clearTimeout(s),s=setTimeout(o.fade,r))},o.fade=function(){t("[data-alerts-container]").fadeOut("slow",function(){t(this).remove()})},o.buildNoticeContainer=function(n){if(n.length>0){var r=a(n[0].priority),e=t("<button/>",{type:"button","class":"close","data-dismiss":"alert"}).html("×"),i=t("<ul/>");o.attachLIs(i,n);var s=t("<div/>",{"data-alerts-container":r,"class":"alert alert-"+r});s.append(e);var l=this.options.titles[r];return l&&l.length>0&&s.append(t("<strong/>").html(l)),s.append(i),s}return null},o.addAlertsToContainer=function(n){if(n.length>0){var r=t(this.element),e=a(n[0].priority),i=t("[data-alerts-container='"+e+"']",r);if(i.length>0){var s=i.find("ul");o.attachLIs(s,n)}else i=o.buildNoticeContainer(n),r.append(i)}},o.attachLIs=function(n,r){t.each(r,function(r,e){n.append(t("<li/>").html(e.message))})}},s=t.fn.bsAlerts;t.fn.bsAlerts=function(n){return this.each(function(){var r=t(this),e=r.data("bsAlerts"),a="object"==typeof n&&n;e||r.data("bsAlerts",e=new i(this,a)),"string"==typeof n&&e[n]()})},t.fn.bsAlerts.Constructor=i,t.fn.bsAlerts.defaults={titles:{},ids:"",fade:"0"},t.fn.bsAlerts.noConflict=function(){return t.fn.bsAlerts=s,this},t(r).ready(function(){t('[data-alerts="alerts"]').each(function(){var n=t(this);n.bsAlerts(n.data())})})})(jQuery,window,document); +\ No newline at end of file diff --git a/src/main/javascript/libs/jquery.bsFormAlerts.min.js b/src/main/javascript/libs/jquery.bsFormAlerts.min.js @@ -0,0 +1,7 @@ +/*! bsFormAlerts version: 0.1.2 +* 2013-04-18 +* Author: Tim Nelson +* Website: http://eltimn.github.com/jquery-bs-formalerts +* MIT License http://www.opensource.org/licenses/mit-license.php +*/ +(function(r,t,e){"use strict";function n(r){return"notice"===r?"info":"danger"===r?"error":r}function s(t){var e=r.grep(t,function(r){return"error"===r.priority}).length;if(e>0)return"error";var n=r.grep(t,function(r){return"warning"===r.priority}).length;if(n>0)return"warning";var s=r.grep(t,function(r){return"success"===r.priority}).length;return s>0?"success":"info"}function o(r){return r.closest("div.control-group")}function i(r){r.removeClass("info"),r.removeClass("warning"),r.removeClass("error"),r.removeClass("success")}var a=function(t,n){var s=this;this.element=t,this.options=r.extend({},r.fn.bsFormAlerts.defaults,n),r(e).on("set-alert-id-"+this.options.alertid,function(){var r=Array.prototype.slice.call(arguments,1);s.renderAlerts(r)}),r(e).on("clear-alert-id."+this.options.alertid,function(){s.clear()})};a.prototype={constructor:a,clear:function(){var t=r(this.element),e=o(t);i(e),t.html("")},renderAlerts:function(t){var e=r(this.element);if(t.length>0){var a=o(e),l=s(t);i(a),e.html("");var c=r("<ul/>");r.each(t,function(t,e){if(e.message&&e.message.length>0){var s=r("<li/>",{"class":"text-"+n(e.priority)}).html(e.message);c.append(s)}}),e.append(c),a.addClass(n(l))}}};var l=r.fn.bsFormAlerts;r.fn.bsFormAlerts=function(t){return this.each(function(){var e=r(this),n=e.data("bsFormAlerts"),s="object"==typeof t&&t;n||e.data("bsFormAlerts",n=new a(this,s)),"string"==typeof t&&n[t]()})},r.fn.bsFormAlerts.Constructor=a,r.fn.bsFormAlerts.defaults={alertid:"bs-form-alert"},r.fn.bsFormAlerts.noConflict=function(){return r.fn.bsFormAlerts=l,this},r(e).ready(function(){r("[data-alertid]").each(function(){var t=r(this);t.bsFormAlerts(t.data())})})})(jQuery,window,document); +\ No newline at end of file diff --git a/src/main/javascript/libs/jquery.fileupload.js b/src/main/javascript/libs/jquery.fileupload.js @@ -0,0 +1,1315 @@ +/* + * jQuery File Upload Plugin 5.31.6 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window, document, location, File, Blob, FormData */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'jquery.ui.widget' + ], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // The FileReader API is not actually used, but works as feature detection, + // as e.g. Safari supports XHR file uploads via the FormData API, + // but not non-multipart XHR file uploads: + $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader); + $.support.xhrFormDataFileUpload = !!window.FormData; + + // Detect support for Blob slicing (required for chunked uploads): + $.support.blobSlice = window.Blob && (Blob.prototype.slice || + Blob.prototype.webkitSlice || Blob.prototype.mozSlice); + + // The fileupload widget listens for change events on file input fields defined + // via fileInput setting and paste or drop events of the given dropZone. + // In addition to the default jQuery Widget methods, the fileupload widget + // exposes the "add" and "send" methods, to add or directly send files using + // the fileupload API. + // By default, files added via file input selection, paste, drag & drop or + // "add" method are uploaded immediately, but it is possible to override + // the "add" callback option to queue file uploads. + $.widget('blueimp.fileupload', { + + options: { + // The drop target element(s), by the default the complete document. + // Set to null to disable drag & drop support: + dropZone: $(document), + // The paste target element(s), by the default the complete document. + // Set to null to disable paste support: + pasteZone: $(document), + // The file input field(s), that are listened to for change events. + // If undefined, it is set to the file input fields inside + // of the widget element on plugin initialization. + // Set to null to disable the change listener. + fileInput: undefined, + // By default, the file input field is replaced with a clone after + // each input field change event. This is required for iframe transport + // queues and allows change events to be fired for the same file + // selection, but can be disabled by setting the following option to false: + replaceFileInput: true, + // The parameter name for the file form data (the request argument name). + // If undefined or empty, the name property of the file input field is + // used, or "files[]" if the file input name property is also empty, + // can be a string or an array of strings: + paramName: undefined, + // By default, each file of a selection is uploaded using an individual + // request for XHR type uploads. Set to false to upload file + // selections in one request each: + singleFileUploads: true, + // To limit the number of files uploaded with one XHR request, + // set the following option to an integer greater than 0: + limitMultiFileUploads: undefined, + // Set the following option to true to issue all file upload requests + // in a sequential order: + sequentialUploads: false, + // To limit the number of concurrent uploads, + // set the following option to an integer greater than 0: + limitConcurrentUploads: undefined, + // Set the following option to true to force iframe transport uploads: + forceIframeTransport: false, + // Set the following option to the location of a redirect url on the + // origin server, for cross-domain iframe transport uploads: + redirect: undefined, + // The parameter name for the redirect url, sent as part of the form + // data and set to 'redirect' if this option is empty: + redirectParamName: undefined, + // Set the following option to the location of a postMessage window, + // to enable postMessage transport uploads: + postMessage: undefined, + // By default, XHR file uploads are sent as multipart/form-data. + // The iframe transport is always using multipart/form-data. + // Set to false to enable non-multipart XHR uploads: + multipart: true, + // To upload large files in smaller chunks, set the following option + // to a preferred maximum chunk size. If set to 0, null or undefined, + // or the browser does not support the required Blob API, files will + // be uploaded as a whole. + maxChunkSize: undefined, + // When a non-multipart upload or a chunked multipart upload has been + // aborted, this option can be used to resume the upload by setting + // it to the size of the already uploaded bytes. This option is most + // useful when modifying the options object inside of the "add" or + // "send" callbacks, as the options are cloned for each file upload. + uploadedBytes: undefined, + // By default, failed (abort or error) file uploads are removed from the + // global progress calculation. Set the following option to false to + // prevent recalculating the global progress data: + recalculateProgress: true, + // Interval in milliseconds to calculate and trigger progress events: + progressInterval: 100, + // Interval in milliseconds to calculate progress bitrate: + bitrateInterval: 500, + // By default, uploads are started automatically when adding files: + autoUpload: true, + + // Error and info messages: + messages: { + uploadedBytes: 'Uploaded bytes exceed file size' + }, + + // Translation function, gets the message key to be translated + // and an object with context specific data as arguments: + i18n: function (message, context) { + message = this.messages[message] || message.toString(); + if (context) { + $.each(context, function (key, value) { + message = message.replace('{' + key + '}', value); + }); + } + return message; + }, + + // Additional form data to be sent along with the file uploads can be set + // using this option, which accepts an array of objects with name and + // value properties, a function returning such an array, a FormData + // object (for XHR file uploads), or a simple object. + // The form of the first fileInput is given as parameter to the function: + formData: function (form) { + return form.serializeArray(); + }, + + // The add callback is invoked as soon as files are added to the fileupload + // widget (via file input selection, drag & drop, paste or add API call). + // If the singleFileUploads option is enabled, this callback will be + // called once for each file in the selection for XHR file uploads, else + // once for each file selection. + // + // The upload starts when the submit method is invoked on the data parameter. + // The data object contains a files property holding the added files + // and allows you to override plugin options as well as define ajax settings. + // + // Listeners for this callback can also be bound the following way: + // .bind('fileuploadadd', func); + // + // data.submit() returns a Promise object and allows to attach additional + // handlers using jQuery's Deferred callbacks: + // data.submit().done(func).fail(func).always(func); + add: function (e, data) { + if (data.autoUpload || (data.autoUpload !== false && + $(this).fileupload('option', 'autoUpload'))) { + data.process().done(function () { + data.submit(); + }); + } + }, + + // Other callbacks: + + // Callback for the submit event of each file upload: + // submit: function (e, data) {}, // .bind('fileuploadsubmit', func); + + // Callback for the start of each file upload request: + // send: function (e, data) {}, // .bind('fileuploadsend', func); + + // Callback for successful uploads: + // done: function (e, data) {}, // .bind('fileuploaddone', func); + + // Callback for failed (abort or error) uploads: + // fail: function (e, data) {}, // .bind('fileuploadfail', func); + + // Callback for completed (success, abort or error) requests: + // always: function (e, data) {}, // .bind('fileuploadalways', func); + + // Callback for upload progress events: + // progress: function (e, data) {}, // .bind('fileuploadprogress', func); + + // Callback for global upload progress events: + // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func); + + // Callback for uploads start, equivalent to the global ajaxStart event: + // start: function (e) {}, // .bind('fileuploadstart', func); + + // Callback for uploads stop, equivalent to the global ajaxStop event: + // stop: function (e) {}, // .bind('fileuploadstop', func); + + // Callback for change events of the fileInput(s): + // change: function (e, data) {}, // .bind('fileuploadchange', func); + + // Callback for paste events to the pasteZone(s): + // paste: function (e, data) {}, // .bind('fileuploadpaste', func); + + // Callback for drop events of the dropZone(s): + // drop: function (e, data) {}, // .bind('fileuploaddrop', func); + + // Callback for dragover events of the dropZone(s): + // dragover: function (e) {}, // .bind('fileuploaddragover', func); + + // Callback for the start of each chunk upload request: + // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func); + + // Callback for successful chunk uploads: + // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func); + + // Callback for failed (abort or error) chunk uploads: + // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func); + + // Callback for completed (success, abort or error) chunk upload requests: + // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func); + + // The plugin options are used as settings object for the ajax calls. + // The following are jQuery ajax settings required for the file uploads: + processData: false, + contentType: false, + cache: false + }, + + // A list of options that require reinitializing event listeners and/or + // special initialization code: + _specialOptions: [ + 'fileInput', + 'dropZone', + 'pasteZone', + 'multipart', + 'forceIframeTransport' + ], + + _blobSlice: $.support.blobSlice && function () { + var slice = this.slice || this.webkitSlice || this.mozSlice; + return slice.apply(this, arguments); + }, + + _BitrateTimer: function () { + this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime()); + this.loaded = 0; + this.bitrate = 0; + this.getBitrate = function (now, loaded, interval) { + var timeDiff = now - this.timestamp; + if (!this.bitrate || !interval || timeDiff > interval) { + this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; + this.loaded = loaded; + this.timestamp = now; + } + return this.bitrate; + }; + }, + + _isXHRUpload: function (options) { + return !options.forceIframeTransport && + ((!options.multipart && $.support.xhrFileUpload) || + $.support.xhrFormDataFileUpload); + }, + + _getFormData: function (options) { + var formData; + if (typeof options.formData === 'function') { + return options.formData(options.form); + } + if ($.isArray(options.formData)) { + return options.formData; + } + if ($.type(options.formData) === 'object') { + formData = []; + $.each(options.formData, function (name, value) { + formData.push({name: name, value: value}); + }); + return formData; + } + return []; + }, + + _getTotal: function (files) { + var total = 0; + $.each(files, function (index, file) { + total += file.size || 1; + }); + return total; + }, + + _initProgressObject: function (obj) { + var progress = { + loaded: 0, + total: 0, + bitrate: 0 + }; + if (obj._progress) { + $.extend(obj._progress, progress); + } else { + obj._progress = progress; + } + }, + + _initResponseObject: function (obj) { + var prop; + if (obj._response) { + for (prop in obj._response) { + if (obj._response.hasOwnProperty(prop)) { + delete obj._response[prop]; + } + } + } else { + obj._response = {}; + } + }, + + _onProgress: function (e, data) { + if (e.lengthComputable) { + var now = ((Date.now) ? Date.now() : (new Date()).getTime()), + loaded; + if (data._time && data.progressInterval && + (now - data._time < data.progressInterval) && + e.loaded !== e.total) { + return; + } + data._time = now; + loaded = Math.floor( + e.loaded / e.total * (data.chunkSize || data._progress.total) + ) + (data.uploadedBytes || 0); + // Add the difference from the previously loaded state + // to the global loaded counter: + this._progress.loaded += (loaded - data._progress.loaded); + this._progress.bitrate = this._bitrateTimer.getBitrate( + now, + this._progress.loaded, + data.bitrateInterval + ); + data._progress.loaded = data.loaded = loaded; + data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate( + now, + loaded, + data.bitrateInterval + ); + // Trigger a custom progress event with a total data property set + // to the file size(s) of the current upload and a loaded data + // property calculated accordingly: + this._trigger('progress', e, data); + // Trigger a global progress event for all current file uploads, + // including ajax calls queued for sequential file uploads: + this._trigger('progressall', e, this._progress); + } + }, + + _initProgressListener: function (options) { + var that = this, + xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); + // Accesss to the native XHR object is required to add event listeners + // for the upload progress event: + if (xhr.upload) { + $(xhr.upload).bind('progress', function (e) { + var oe = e.originalEvent; + // Make sure the progress event properties get copied over: + e.lengthComputable = oe.lengthComputable; + e.loaded = oe.loaded; + e.total = oe.total; + that._onProgress(e, options); + }); + options.xhr = function () { + return xhr; + }; + } + }, + + _isInstanceOf: function (type, obj) { + // Cross-frame instanceof check + return Object.prototype.toString.call(obj) === '[object ' + type + ']'; + }, + + _initXHRData: function (options) { + var that = this, + formData, + file = options.files[0], + // Ignore non-multipart setting if not supported: + multipart = options.multipart || !$.support.xhrFileUpload, + paramName = options.paramName[0]; + options.headers = options.headers || {}; + if (options.contentRange) { + options.headers['Content-Range'] = options.contentRange; + } + if (!multipart || options.blob || !this._isInstanceOf('File', file)) { + options.headers['Content-Disposition'] = 'attachment; filename="' + + encodeURI(file.name) + '"'; + } + if (!multipart) { + options.contentType = file.type; + options.data = options.blob || file; + } else if ($.support.xhrFormDataFileUpload) { + if (options.postMessage) { + // window.postMessage does not allow sending FormData + // objects, so we just add the File/Blob objects to + // the formData array and let the postMessage window + // create the FormData object out of this array: + formData = this._getFormData(options); + if (options.blob) { + formData.push({ + name: paramName, + value: options.blob + }); + } else { + $.each(options.files, function (index, file) { + formData.push({ + name: options.paramName[index] || paramName, + value: file + }); + }); + } + } else { + if (that._isInstanceOf('FormData', options.formData)) { + formData = options.formData; + } else { + formData = new FormData(); + $.each(this._getFormData(options), function (index, field) { + formData.append(field.name, field.value); + }); + } + if (options.blob) { + formData.append(paramName, options.blob, file.name); + } else { + $.each(options.files, function (index, file) { + // This check allows the tests to run with + // dummy objects: + if (that._isInstanceOf('File', file) || + that._isInstanceOf('Blob', file)) { + formData.append( + options.paramName[index] || paramName, + file, + file.name + ); + } + }); + } + } + options.data = formData; + } + // Blob reference is not needed anymore, free memory: + options.blob = null; + }, + + _initIframeSettings: function (options) { + var targetHost = $('<a></a>').prop('href', options.url).prop('host'); + // Setting the dataType to iframe enables the iframe transport: + options.dataType = 'iframe ' + (options.dataType || ''); + // The iframe transport accepts a serialized array as form data: + options.formData = this._getFormData(options); + // Add redirect url to form data on cross-domain uploads: + if (options.redirect && targetHost && targetHost !== location.host) { + options.formData.push({ + name: options.redirectParamName || 'redirect', + value: options.redirect + }); + } + }, + + _initDataSettings: function (options) { + if (this._isXHRUpload(options)) { + if (!this._chunkedUpload(options, true)) { + if (!options.data) { + this._initXHRData(options); + } + this._initProgressListener(options); + } + if (options.postMessage) { + // Setting the dataType to postmessage enables the + // postMessage transport: + options.dataType = 'postmessage ' + (options.dataType || ''); + } + } else { + this._initIframeSettings(options); + } + }, + + _getParamName: function (options) { + var fileInput = $(options.fileInput), + paramName = options.paramName; + if (!paramName) { + paramName = []; + fileInput.each(function () { + var input = $(this), + name = input.prop('name') || 'files[]', + i = (input.prop('files') || [1]).length; + while (i) { + paramName.push(name); + i -= 1; + } + }); + if (!paramName.length) { + paramName = [fileInput.prop('name') || 'files[]']; + } + } else if (!$.isArray(paramName)) { + paramName = [paramName]; + } + return paramName; + }, + + _initFormSettings: function (options) { + // Retrieve missing options from the input field and the + // associated form, if available: + if (!options.form || !options.form.length) { + options.form = $(options.fileInput.prop('form')); + // If the given file input doesn't have an associated form, + // use the default widget file input's form: + if (!options.form.length) { + options.form = $(this.options.fileInput.prop('form')); + } + } + options.paramName = this._getParamName(options); + if (!options.url) { + options.url = options.form.prop('action') || location.href; + } + // The HTTP request method must be "POST" or "PUT": + options.type = (options.type || options.form.prop('method') || '') + .toUpperCase(); + if (options.type !== 'POST' && options.type !== 'PUT' && + options.type !== 'PATCH') { + options.type = 'POST'; + } + if (!options.formAcceptCharset) { + options.formAcceptCharset = options.form.attr('accept-charset'); + } + }, + + _getAJAXSettings: function (data) { + var options = $.extend({}, this.options, data); + this._initFormSettings(options); + this._initDataSettings(options); + return options; + }, + + // jQuery 1.6 doesn't provide .state(), + // while jQuery 1.8+ removed .isRejected() and .isResolved(): + _getDeferredState: function (deferred) { + if (deferred.state) { + return deferred.state(); + } + if (deferred.isResolved()) { + return 'resolved'; + } + if (deferred.isRejected()) { + return 'rejected'; + } + return 'pending'; + }, + + // Maps jqXHR callbacks to the equivalent + // methods of the given Promise object: + _enhancePromise: function (promise) { + promise.success = promise.done; + promise.error = promise.fail; + promise.complete = promise.always; + return promise; + }, + + // Creates and returns a Promise object enhanced with + // the jqXHR methods abort, success, error and complete: + _getXHRPromise: function (resolveOrReject, context, args) { + var dfd = $.Deferred(), + promise = dfd.promise(); + context = context || this.options.context || promise; + if (resolveOrReject === true) { + dfd.resolveWith(context, args); + } else if (resolveOrReject === false) { + dfd.rejectWith(context, args); + } + promise.abort = dfd.promise; + return this._enhancePromise(promise); + }, + + // Adds convenience methods to the data callback argument: + _addConvenienceMethods: function (e, data) { + var that = this, + getPromise = function (data) { + return $.Deferred().resolveWith(that, [data]).promise(); + }; + data.process = function (resolveFunc, rejectFunc) { + if (resolveFunc || rejectFunc) { + data._processQueue = this._processQueue = + (this._processQueue || getPromise(this)) + .pipe(resolveFunc, rejectFunc); + } + return this._processQueue || getPromise(this); + }; + data.submit = function () { + if (this.state() !== 'pending') { + data.jqXHR = this.jqXHR = + (that._trigger('submit', e, this) !== false) && + that._onSend(e, this); + } + return this.jqXHR || that._getXHRPromise(); + }; + data.abort = function () { + if (this.jqXHR) { + return this.jqXHR.abort(); + } + return that._getXHRPromise(); + }; + data.state = function () { + if (this.jqXHR) { + return that._getDeferredState(this.jqXHR); + } + if (this._processQueue) { + return that._getDeferredState(this._processQueue); + } + }; + data.progress = function () { + return this._progress; + }; + data.response = function () { + return this._response; + }; + }, + + // Parses the Range header from the server response + // and returns the uploaded bytes: + _getUploadedBytes: function (jqXHR) { + var range = jqXHR.getResponseHeader('Range'), + parts = range && range.split('-'), + upperBytesPos = parts && parts.length > 1 && + parseInt(parts[1], 10); + return upperBytesPos && upperBytesPos + 1; + }, + + // Uploads a file in multiple, sequential requests + // by splitting the file up in multiple blob chunks. + // If the second parameter is true, only tests if the file + // should be uploaded in chunks, but does not invoke any + // upload requests: + _chunkedUpload: function (options, testOnly) { + options.uploadedBytes = options.uploadedBytes || 0; + var that = this, + file = options.files[0], + fs = file.size, + ub = options.uploadedBytes, + mcs = options.maxChunkSize || fs, + slice = this._blobSlice, + dfd = $.Deferred(), + promise = dfd.promise(), + jqXHR, + upload; + if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || + options.data) { + return false; + } + if (testOnly) { + return true; + } + if (ub >= fs) { + file.error = options.i18n('uploadedBytes'); + return this._getXHRPromise( + false, + options.context, + [null, 'error', file.error] + ); + } + // The chunk upload method: + upload = function () { + // Clone the options object for each chunk upload: + var o = $.extend({}, options), + currentLoaded = o._progress.loaded; + o.blob = slice.call( + file, + ub, + ub + mcs, + file.type + ); + // Store the current chunk size, as the blob itself + // will be dereferenced after data processing: + o.chunkSize = o.blob.size; + // Expose the chunk bytes position range: + o.contentRange = 'bytes ' + ub + '-' + + (ub + o.chunkSize - 1) + '/' + fs; + // Process the upload data (the blob and potential form data): + that._initXHRData(o); + // Add progress listeners for this chunk upload: + that._initProgressListener(o); + jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) || + that._getXHRPromise(false, o.context)) + .done(function (result, textStatus, jqXHR) { + ub = that._getUploadedBytes(jqXHR) || + (ub + o.chunkSize); + // Create a progress event if no final progress event + // with loaded equaling total has been triggered + // for this chunk: + if (currentLoaded + o.chunkSize - o._progress.loaded) { + that._onProgress($.Event('progress', { + lengthComputable: true, + loaded: ub - o.uploadedBytes, + total: ub - o.uploadedBytes + }), o); + } + options.uploadedBytes = o.uploadedBytes = ub; + o.result = result; + o.textStatus = textStatus; + o.jqXHR = jqXHR; + that._trigger('chunkdone', null, o); + that._trigger('chunkalways', null, o); + if (ub < fs) { + // File upload not yet complete, + // continue with the next chunk: + upload(); + } else { + dfd.resolveWith( + o.context, + [result, textStatus, jqXHR] + ); + } + }) + .fail(function (jqXHR, textStatus, errorThrown) { + o.jqXHR = jqXHR; + o.textStatus = textStatus; + o.errorThrown = errorThrown; + that._trigger('chunkfail', null, o); + that._trigger('chunkalways', null, o); + dfd.rejectWith( + o.context, + [jqXHR, textStatus, errorThrown] + ); + }); + }; + this._enhancePromise(promise); + promise.abort = function () { + return jqXHR.abort(); + }; + upload(); + return promise; + }, + + _beforeSend: function (e, data) { + if (this._active === 0) { + // the start callback is triggered when an upload starts + // and no other uploads are currently running, + // equivalent to the global ajaxStart event: + this._trigger('start'); + // Set timer for global bitrate progress calculation: + this._bitrateTimer = new this._BitrateTimer(); + // Reset the global progress values: + this._progress.loaded = this._progress.total = 0; + this._progress.bitrate = 0; + } + // Make sure the container objects for the .response() and + // .progress() methods on the data object are available + // and reset to their initial state: + this._initResponseObject(data); + this._initProgressObject(data); + data._progress.loaded = data.loaded = data.uploadedBytes || 0; + data._progress.total = data.total = this._getTotal(data.files) || 1; + data._progress.bitrate = data.bitrate = 0; + this._active += 1; + // Initialize the global progress values: + this._progress.loaded += data.loaded; + this._progress.total += data.total; + }, + + _onDone: function (result, textStatus, jqXHR, options) { + var total = options._progress.total, + response = options._response; + if (options._progress.loaded < total) { + // Create a progress event if no final progress event + // with loaded equaling total has been triggered: + this._onProgress($.Event('progress', { + lengthComputable: true, + loaded: total, + total: total + }), options); + } + response.result = options.result = result; + response.textStatus = options.textStatus = textStatus; + response.jqXHR = options.jqXHR = jqXHR; + this._trigger('done', null, options); + }, + + _onFail: function (jqXHR, textStatus, errorThrown, options) { + var response = options._response; + if (options.recalculateProgress) { + // Remove the failed (error or abort) file upload from + // the global progress calculation: + this._progress.loaded -= options._progress.loaded; + this._progress.total -= options._progress.total; + } + response.jqXHR = options.jqXHR = jqXHR; + response.textStatus = options.textStatus = textStatus; + response.errorThrown = options.errorThrown = errorThrown; + this._trigger('fail', null, options); + }, + + _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { + // jqXHRorResult, textStatus and jqXHRorError are added to the + // options object via done and fail callbacks + this._trigger('always', null, options); + }, + + _onSend: function (e, data) { + if (!data.submit) { + this._addConvenienceMethods(e, data); + } + var that = this, + jqXHR, + aborted, + slot, + pipe, + options = that._getAJAXSettings(data), + send = function () { + that._sending += 1; + // Set timer for bitrate progress calculation: + options._bitrateTimer = new that._BitrateTimer(); + jqXHR = jqXHR || ( + ((aborted || that._trigger('send', e, options) === false) && + that._getXHRPromise(false, options.context, aborted)) || + that._chunkedUpload(options) || $.ajax(options) + ).done(function (result, textStatus, jqXHR) { + that._onDone(result, textStatus, jqXHR, options); + }).fail(function (jqXHR, textStatus, errorThrown) { + that._onFail(jqXHR, textStatus, errorThrown, options); + }).always(function (jqXHRorResult, textStatus, jqXHRorError) { + that._onAlways( + jqXHRorResult, + textStatus, + jqXHRorError, + options + ); + that._sending -= 1; + that._active -= 1; + if (options.limitConcurrentUploads && + options.limitConcurrentUploads > that._sending) { + // Start the next queued upload, + // that has not been aborted: + var nextSlot = that._slots.shift(); + while (nextSlot) { + if (that._getDeferredState(nextSlot) === 'pending') { + nextSlot.resolve(); + break; + } + nextSlot = that._slots.shift(); + } + } + if (that._active === 0) { + // The stop callback is triggered when all uploads have + // been completed, equivalent to the global ajaxStop event: + that._trigger('stop'); + } + }); + return jqXHR; + }; + this._beforeSend(e, options); + if (this.options.sequentialUploads || + (this.options.limitConcurrentUploads && + this.options.limitConcurrentUploads <= this._sending)) { + if (this.options.limitConcurrentUploads > 1) { + slot = $.Deferred(); + this._slots.push(slot); + pipe = slot.pipe(send); + } else { + this._sequence = this._sequence.pipe(send, send); + pipe = this._sequence; + } + // Return the piped Promise object, enhanced with an abort method, + // which is delegated to the jqXHR object of the current upload, + // and jqXHR callbacks mapped to the equivalent Promise methods: + pipe.abort = function () { + aborted = [undefined, 'abort', 'abort']; + if (!jqXHR) { + if (slot) { + slot.rejectWith(options.context, aborted); + } + return send(); + } + return jqXHR.abort(); + }; + return this._enhancePromise(pipe); + } + return send(); + }, + + _onAdd: function (e, data) { + var that = this, + result = true, + options = $.extend({}, this.options, data), + limit = options.limitMultiFileUploads, + paramName = this._getParamName(options), + paramNameSet, + paramNameSlice, + fileSet, + i; + if (!(options.singleFileUploads || limit) || + !this._isXHRUpload(options)) { + fileSet = [data.files]; + paramNameSet = [paramName]; + } else if (!options.singleFileUploads && limit) { + fileSet = []; + paramNameSet = []; + for (i = 0; i < data.files.length; i += limit) { + fileSet.push(data.files.slice(i, i + limit)); + paramNameSlice = paramName.slice(i, i + limit); + if (!paramNameSlice.length) { + paramNameSlice = paramName; + } + paramNameSet.push(paramNameSlice); + } + } else { + paramNameSet = paramName; + } + data.originalFiles = data.files; + $.each(fileSet || data.files, function (index, element) { + var newData = $.extend({}, data); + newData.files = fileSet ? element : [element]; + newData.paramName = paramNameSet[index]; + that._initResponseObject(newData); + that._initProgressObject(newData); + that._addConvenienceMethods(e, newData); + result = that._trigger('add', e, newData); + return result; + }); + return result; + }, + + _replaceFileInput: function (input) { + var inputClone = input.clone(true); + $('<form></form>').append(inputClone)[0].reset(); + // Detaching allows to insert the fileInput on another form + // without loosing the file input value: + input.after(inputClone).detach(); + // Avoid memory leaks with the detached file input: + $.cleanData(input.unbind('remove')); + // Replace the original file input element in the fileInput + // elements set with the clone, which has been copied including + // event handlers: + this.options.fileInput = this.options.fileInput.map(function (i, el) { + if (el === input[0]) { + return inputClone[0]; + } + return el; + }); + // If the widget has been initialized on the file input itself, + // override this.element with the file input clone: + if (input[0] === this.element[0]) { + this.element = inputClone; + } + }, + + _handleFileTreeEntry: function (entry, path) { + var that = this, + dfd = $.Deferred(), + errorHandler = function (e) { + if (e && !e.entry) { + e.entry = entry; + } + // Since $.when returns immediately if one + // Deferred is rejected, we use resolve instead. + // This allows valid files and invalid items + // to be returned together in one set: + dfd.resolve([e]); + }, + dirReader; + path = path || ''; + if (entry.isFile) { + if (entry._file) { + // Workaround for Chrome bug #149735 + entry._file.relativePath = path; + dfd.resolve(entry._file); + } else { + entry.file(function (file) { + file.relativePath = path; + dfd.resolve(file); + }, errorHandler); + } + } else if (entry.isDirectory) { + dirReader = entry.createReader(); + dirReader.readEntries(function (entries) { + that._handleFileTreeEntries( + entries, + path + entry.name + '/' + ).done(function (files) { + dfd.resolve(files); + }).fail(errorHandler); + }, errorHandler); + } else { + // Return an empy list for file system items + // other than files or directories: + dfd.resolve([]); + } + return dfd.promise(); + }, + + _handleFileTreeEntries: function (entries, path) { + var that = this; + return $.when.apply( + $, + $.map(entries, function (entry) { + return that._handleFileTreeEntry(entry, path); + }) + ).pipe(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _getDroppedFiles: function (dataTransfer) { + dataTransfer = dataTransfer || {}; + var items = dataTransfer.items; + if (items && items.length && (items[0].webkitGetAsEntry || + items[0].getAsEntry)) { + return this._handleFileTreeEntries( + $.map(items, function (item) { + var entry; + if (item.webkitGetAsEntry) { + entry = item.webkitGetAsEntry(); + if (entry) { + // Workaround for Chrome bug #149735: + entry._file = item.getAsFile(); + } + return entry; + } + return item.getAsEntry(); + }) + ); + } + return $.Deferred().resolve( + $.makeArray(dataTransfer.files) + ).promise(); + }, + + _getSingleFileInputFiles: function (fileInput) { + fileInput = $(fileInput); + var entries = fileInput.prop('webkitEntries') || + fileInput.prop('entries'), + files, + value; + if (entries && entries.length) { + return this._handleFileTreeEntries(entries); + } + files = $.makeArray(fileInput.prop('files')); + if (!files.length) { + value = fileInput.prop('value'); + if (!value) { + return $.Deferred().resolve([]).promise(); + } + // If the files property is not available, the browser does not + // support the File API and we add a pseudo File object with + // the input value as name with path information removed: + files = [{name: value.replace(/^.*\\/, '')}]; + } else if (files[0].name === undefined && files[0].fileName) { + // File normalization for Safari 4 and Firefox 3: + $.each(files, function (index, file) { + file.name = file.fileName; + file.size = file.fileSize; + }); + } + return $.Deferred().resolve(files).promise(); + }, + + _getFileInputFiles: function (fileInput) { + if (!(fileInput instanceof $) || fileInput.length === 1) { + return this._getSingleFileInputFiles(fileInput); + } + return $.when.apply( + $, + $.map(fileInput, this._getSingleFileInputFiles) + ).pipe(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _onChange: function (e) { + var that = this, + data = { + fileInput: $(e.target), + form: $(e.target.form) + }; + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + if (that.options.replaceFileInput) { + that._replaceFileInput(data.fileInput); + } + if (that._trigger('change', e, data) !== false) { + that._onAdd(e, data); + } + }); + }, + + _onPaste: function (e) { + var items = e.originalEvent && e.originalEvent.clipboardData && + e.originalEvent.clipboardData.items, + data = {files: []}; + if (items && items.length) { + $.each(items, function (index, item) { + var file = item.getAsFile && item.getAsFile(); + if (file) { + data.files.push(file); + } + }); + if (this._trigger('paste', e, data) === false || + this._onAdd(e, data) === false) { + return false; + } + } + }, + + _onDrop: function (e) { + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; + var that = this, + dataTransfer = e.dataTransfer, + data = {}; + if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { + e.preventDefault(); + this._getDroppedFiles(dataTransfer).always(function (files) { + data.files = files; + if (that._trigger('drop', e, data) !== false) { + that._onAdd(e, data); + } + }); + } + }, + + _onDragOver: function (e) { + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; + var dataTransfer = e.dataTransfer; + if (dataTransfer) { + if (this._trigger('dragover', e) === false) { + return false; + } + if ($.inArray('Files', dataTransfer.types) !== -1) { + dataTransfer.dropEffect = 'copy'; + e.preventDefault(); + } + } + }, + + _initEventHandlers: function () { + if (this._isXHRUpload(this.options)) { + this._on(this.options.dropZone, { + dragover: this._onDragOver, + drop: this._onDrop + }); + this._on(this.options.pasteZone, { + paste: this._onPaste + }); + } + this._on(this.options.fileInput, { + change: this._onChange + }); + }, + + _destroyEventHandlers: function () { + this._off(this.options.dropZone, 'dragover drop'); + this._off(this.options.pasteZone, 'paste'); + this._off(this.options.fileInput, 'change'); + }, + + _setOption: function (key, value) { + var reinit = $.inArray(key, this._specialOptions) !== -1; + if (reinit) { + this._destroyEventHandlers(); + } + this._super(key, value); + if (reinit) { + this._initSpecialOptions(); + this._initEventHandlers(); + } + }, + + _initSpecialOptions: function () { + var options = this.options; + if (options.fileInput === undefined) { + options.fileInput = this.element.is('input[type="file"]') ? + this.element : this.element.find('input[type="file"]'); + } else if (!(options.fileInput instanceof $)) { + options.fileInput = $(options.fileInput); + } + if (!(options.dropZone instanceof $)) { + options.dropZone = $(options.dropZone); + } + if (!(options.pasteZone instanceof $)) { + options.pasteZone = $(options.pasteZone); + } + }, + + _getRegExp: function (str) { + var parts = str.split('/'), + modifiers = parts.pop(); + parts.shift(); + return new RegExp(parts.join('/'), modifiers); + }, + + _isRegExpOption: function (key, value) { + return key !== 'url' && $.type(value) === 'string' && + /^\/.*\/[igm]{0,3}$/.test(value); + }, + + _initDataAttributes: function () { + var that = this, + options = this.options; + // Initialize options set via HTML5 data-attributes: + $.each( + $(this.element[0].cloneNode(false)).data(), + function (key, value) { + if (that._isRegExpOption(key, value)) { + value = that._getRegExp(value); + } + options[key] = value; + } + ); + }, + + _create: function () { + this._initDataAttributes(); + this._initSpecialOptions(); + this._slots = []; + this._sequence = this._getXHRPromise(true); + this._sending = this._active = 0; + this._initProgressObject(this); + this._initEventHandlers(); + }, + + // This method is exposed to the widget API and allows to query + // the number of active uploads: + active: function () { + return this._active; + }, + + // This method is exposed to the widget API and allows to query + // the widget upload progress. + // It returns an object with loaded, total and bitrate properties + // for the running uploads: + progress: function () { + return this._progress; + }, + + // This method is exposed to the widget API and allows adding files + // using the fileupload API. The data parameter accepts an object which + // must have a files property and can contain additional options: + // .fileupload('add', {files: filesList}); + add: function (data) { + var that = this; + if (!data || this.options.disabled) { + return; + } + if (data.fileInput && !data.files) { + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + that._onAdd(null, data); + }); + } else { + data.files = $.makeArray(data.files); + this._onAdd(null, data); + } + }, + + // This method is exposed to the widget API and allows sending files + // using the fileupload API. The data parameter accepts an object which + // must have a files or fileInput property and can contain additional options: + // .fileupload('send', {files: filesList}); + // The method returns a Promise object for the file upload call. + send: function (data) { + if (data && !this.options.disabled) { + if (data.fileInput && !data.files) { + var that = this, + dfd = $.Deferred(), + promise = dfd.promise(), + jqXHR, + aborted; + promise.abort = function () { + aborted = true; + if (jqXHR) { + return jqXHR.abort(); + } + dfd.reject(null, 'abort', 'abort'); + return promise; + }; + this._getFileInputFiles(data.fileInput).always( + function (files) { + if (aborted) { + return; + } + data.files = files; + jqXHR = that._onSend(null, data).then( + function (result, textStatus, jqXHR) { + dfd.resolve(result, textStatus, jqXHR); + }, + function (jqXHR, textStatus, errorThrown) { + dfd.reject(jqXHR, textStatus, errorThrown); + } + ); + } + ); + return this._enhancePromise(promise); + } + data.files = $.makeArray(data.files); + if (data.files.length) { + return this._onSend(null, data); + } + } + return this._getXHRPromise(false, data && data.context); + } + + }); + +})); +\ No newline at end of file diff --git a/src/main/javascript/libs/jquery.iframe-transport.js b/src/main/javascript/libs/jquery.iframe-transport.js @@ -0,0 +1,205 @@ +/* + * jQuery Iframe Transport Plugin 1.7 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint unparam: true, nomen: true */ +/*global define, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Helper variable to create unique names for the transport iframes: + var counter = 0; + + // The iframe transport accepts three additional options: + // options.fileInput: a jQuery collection of file input fields + // options.paramName: the parameter name for the file form data, + // overrides the name property of the file input field(s), + // can be a string or an array of strings. + // options.formData: an array of objects with name and value properties, + // equivalent to the return data of .serializeArray(), e.g.: + // [{name: 'a', value: 1}, {name: 'b', value: 2}] + $.ajaxTransport('iframe', function (options) { + if (options.async) { + var form, + iframe, + addParamChar; + return { + send: function (_, completeCallback) { + form = $('<form style="display:none;"></form>'); + form.attr('accept-charset', options.formAcceptCharset); + addParamChar = /\?/.test(options.url) ? '&' : '?'; + // XDomainRequest only supports GET and POST: + if (options.type === 'DELETE') { + options.url = options.url + addParamChar + '_method=DELETE'; + options.type = 'POST'; + } else if (options.type === 'PUT') { + options.url = options.url + addParamChar + '_method=PUT'; + options.type = 'POST'; + } else if (options.type === 'PATCH') { + options.url = options.url + addParamChar + '_method=PATCH'; + options.type = 'POST'; + } + // javascript:false as initial iframe src + // prevents warning popups on HTTPS in IE6. + // IE versions below IE8 cannot set the name property of + // elements that have already been added to the DOM, + // so we set the name along with the iframe HTML markup: + counter += 1; + iframe = $( + '<iframe src="javascript:false;" name="iframe-transport-' + + counter + '"></iframe>' + ).bind('load', function () { + var fileInputClones, + paramNames = $.isArray(options.paramName) ? + options.paramName : [options.paramName]; + iframe + .unbind('load') + .bind('load', function () { + var response; + // Wrap in a try/catch block to catch exceptions thrown + // when trying to access cross-domain iframe contents: + try { + response = iframe.contents(); + // Google Chrome and Firefox do not throw an + // exception when calling iframe.contents() on + // cross-domain requests, so we unify the response: + if (!response.length || !response[0].firstChild) { + throw new Error(); + } + } catch (e) { + response = undefined; + } + // The complete callback returns the + // iframe content document as response object: + completeCallback( + 200, + 'success', + {'iframe': response} + ); + // Fix for IE endless progress bar activity bug + // (happens on form submits to iframe targets): + $('<iframe src="javascript:false;"></iframe>') + .appendTo(form); + window.setTimeout(function () { + // Removing the form in a setTimeout call + // allows Chrome's developer tools to display + // the response result + form.remove(); + }, 0); + }); + form + .prop('target', iframe.prop('name')) + .prop('action', options.url) + .prop('method', options.type); + if (options.formData) { + $.each(options.formData, function (index, field) { + $('<input type="hidden"/>') + .prop('name', field.name) + .val(field.value) + .appendTo(form); + }); + } + if (options.fileInput && options.fileInput.length && + options.type === 'POST') { + fileInputClones = options.fileInput.clone(); + // Insert a clone for each file input field: + options.fileInput.after(function (index) { + return fileInputClones[index]; + }); + if (options.paramName) { + options.fileInput.each(function (index) { + $(this).prop( + 'name', + paramNames[index] || options.paramName + ); + }); + } + // Appending the file input fields to the hidden form + // removes them from their original location: + form + .append(options.fileInput) + .prop('enctype', 'multipart/form-data') + // enctype must be set as encoding for IE: + .prop('encoding', 'multipart/form-data'); + } + form.submit(); + // Insert the file input fields at their original location + // by replacing the clones with the originals: + if (fileInputClones && fileInputClones.length) { + options.fileInput.each(function (index, input) { + var clone = $(fileInputClones[index]); + $(input).prop('name', clone.prop('name')); + clone.replaceWith(input); + }); + } + }); + form.append(iframe).appendTo(document.body); + }, + abort: function () { + if (iframe) { + // javascript:false as iframe src aborts the request + // and prevents warning popups on HTTPS in IE6. + // concat is used to avoid the "Script URL" JSLint error: + iframe + .unbind('load') + .prop('src', 'javascript'.concat(':false;')); + } + if (form) { + form.remove(); + } + } + }; + } + }); + + // The iframe transport returns the iframe content document as response. + // The following adds converters from iframe to text, json, html, xml + // and script. + // Please note that the Content-Type for JSON responses has to be text/plain + // or text/html, if the browser doesn't include application/json in the + // Accept header, else IE will show a download dialog. + // The Content-Type for XML responses on the other hand has to be always + // application/xml or text/xml, so IE properly parses the XML response. + // See also + // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation + $.ajaxSetup({ + converters: { + 'iframe text': function (iframe) { + return iframe && $(iframe[0].body).text(); + }, + 'iframe json': function (iframe) { + return iframe && $.parseJSON($(iframe[0].body).text()); + }, + 'iframe html': function (iframe) { + return iframe && $(iframe[0].body).html(); + }, + 'iframe xml': function (iframe) { + var xmlDoc = iframe && iframe[0]; + return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc : + $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) || + $(xmlDoc.body).html()); + }, + 'iframe script': function (iframe) { + return iframe && $.globalEval($(iframe[0].body).text()); + } + } + }); + +})); +\ No newline at end of file diff --git a/src/main/javascript/libs/jquery.timeago.js b/src/main/javascript/libs/jquery.timeago.js @@ -0,0 +1,193 @@ +/** + * Timeago is a jQuery plugin that makes it easy to support automatically + * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago"). + * + * @name timeago + * @version 1.3.0 + * @requires jQuery v1.2.3+ + * @author Ryan McGeary + * @license MIT License - http://www.opensource.org/licenses/mit-license.php + * + * For usage and examples, visit: + * http://timeago.yarp.com/ + * + * Copyright (c) 2008-2013, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org) + */ + +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + $.timeago = function(timestamp) { + if (timestamp instanceof Date) { + return inWords(timestamp); + } else if (typeof timestamp === "string") { + return inWords($.timeago.parse(timestamp)); + } else if (typeof timestamp === "number") { + return inWords(new Date(timestamp)); + } else { + return inWords($.timeago.datetime(timestamp)); + } + }; + var $t = $.timeago; + + $.extend($.timeago, { + settings: { + refreshMillis: 60000, + allowFuture: false, + localeTitle: false, + cutoff: 0, + strings: { + prefixAgo: null, + prefixFromNow: null, + suffixAgo: "ago", + suffixFromNow: "from now", + seconds: "less than a minute", + minute: "about a minute", + minutes: "%d minutes", + hour: "about an hour", + hours: "about %d hours", + day: "a day", + days: "%d days", + month: "about a month", + months: "%d months", + year: "about a year", + years: "%d years", + wordSeparator: " ", + numbers: [] + } + }, + inWords: function(distanceMillis) { + var $l = this.settings.strings; + var prefix = $l.prefixAgo; + var suffix = $l.suffixAgo; + if (this.settings.allowFuture) { + if (distanceMillis < 0) { + prefix = $l.prefixFromNow; + suffix = $l.suffixFromNow; + } + } + + var seconds = Math.abs(distanceMillis) / 1000; + var minutes = seconds / 60; + var hours = minutes / 60; + var days = hours / 24; + var years = days / 365; + + function substitute(stringOrFunction, number) { + var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction; + var value = ($l.numbers && $l.numbers[number]) || number; + return string.replace(/%d/i, value); + } + + var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) || + seconds < 90 && substitute($l.minute, 1) || + minutes < 45 && substitute($l.minutes, Math.round(minutes)) || + minutes < 90 && substitute($l.hour, 1) || + hours < 24 && substitute($l.hours, Math.round(hours)) || + hours < 42 && substitute($l.day, 1) || + days < 30 && substitute($l.days, Math.round(days)) || + days < 45 && substitute($l.month, 1) || + days < 365 && substitute($l.months, Math.round(days / 30)) || + years < 1.5 && substitute($l.year, 1) || + substitute($l.years, Math.round(years)); + + var separator = $l.wordSeparator || ""; + if ($l.wordSeparator === undefined) { separator = " "; } + return $.trim([prefix, words, suffix].join(separator)); + }, + parse: function(iso8601) { + var s = $.trim(iso8601); + s = s.replace(/\.\d+/,""); // remove milliseconds + s = s.replace(/-/,"/").replace(/-/,"/"); + s = s.replace(/T/," ").replace(/Z/," UTC"); + s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400 + return new Date(s); + }, + datetime: function(elem) { + var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title"); + return $t.parse(iso8601); + }, + isTime: function(elem) { + // jQuery's `is()` doesn't play well with HTML5 in IE + return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time"); + } + }); + + // functions that can be called via $(el).timeago('action') + // init is default when no action is given + // functions are called with context of a single element + var functions = { + init: function(){ + var refresh_el = $.proxy(refresh, this); + refresh_el(); + var $s = $t.settings; + if ($s.refreshMillis > 0) { + setInterval(refresh_el, $s.refreshMillis); + } + }, + update: function(time){ + $(this).data('timeago', { datetime: $t.parse(time) }); + refresh.apply(this); + }, + updateFromDOM: function(){ + $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) }); + refresh.apply(this); + } + }; + + $.fn.timeago = function(action, options) { + var fn = action ? functions[action] : functions.init; + if(!fn){ + throw new Error("Unknown function name '"+ action +"' for timeago"); + } + // each over objects here and call the requested function + this.each(function(){ + fn.call(this, options); + }); + return this; + }; + + function refresh() { + var data = prepareData(this); + var $s = $t.settings; + + if (!isNaN(data.datetime)) { + if ( $s.cutoff == 0 || distance(data.datetime) < $s.cutoff) { + $(this).text(inWords(data.datetime)); + } + } + return this; + } + + function prepareData(element) { + element = $(element); + if (!element.data("timeago")) { + element.data("timeago", { datetime: $t.datetime(element) }); + var text = $.trim(element.text()); + if ($t.settings.localeTitle) { + element.attr("title", element.data('timeago').datetime.toLocaleString()); + } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) { + element.attr("title", text); + } + } + return element.data("timeago"); + } + + function inWords(date) { + return $t.inWords(distance(date)); + } + + function distance(date) { + return (new Date().getTime() - date.getTime()); + } + + // fix for IE6 suckage + document.createElement("abbr"); + document.createElement("time"); +})); diff --git a/src/main/javascript/libs/jquery.ui.widget.js b/src/main/javascript/libs/jquery.ui.widget.js @@ -0,0 +1,530 @@ +/* + * jQuery UI Widget 1.10.3+amd + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/jQuery.widget/ + */ + +(function (factory) { + if (typeof define === "function" && define.amd) { + // Register as an anonymous AMD module: + define(["jquery"], factory); + } else { + // Browser globals: + factory(jQuery); + } +}(function( $, undefined ) { + +var uuid = 0, + slice = Array.prototype.slice, + _cleanData = $.cleanData; +$.cleanData = function( elems ) { + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + try { + $( elem ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + } + _cleanData( elems ); +}; + +$.widget = function( name, base, prototype ) { + var fullName, existingConstructor, constructor, basePrototype, + // proxiedPrototype allows the provided prototype to remain unmodified + // so that it can be used as a mixin for multiple widgets (#8876) + proxiedPrototype = {}, + namespace = name.split( "." )[ 0 ]; + + name = name.split( "." )[ 1 ]; + fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + // create selector for plugin + $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); + }; + + $[ namespace ] = $[ namespace ] || {}; + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without "new" keyword + if ( !this._createWidget ) { + return new constructor( options, element ); + } + + // allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + // extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + // copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + // track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + }); + + basePrototype = new base(); + // we need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( !$.isFunction( value ) ) { + proxiedPrototype[ prop ] = value; + return; + } + proxiedPrototype[ prop ] = (function() { + var _super = function() { + return base.prototype[ prop ].apply( this, arguments ); + }, + _superApply = function( args ) { + return base.prototype[ prop ].apply( this, args ); + }; + return function() { + var __super = this._super, + __superApply = this._superApply, + returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + })(); + }); + constructor.prototype = $.widget.extend( basePrototype, { + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name + }, proxiedPrototype, { + constructor: constructor, + namespace: namespace, + widgetName: name, + widgetFullName: fullName + }); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); + }); + // remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } + + $.widget.bridge( name, constructor ); +}; + +$.widget.extend = function( target ) { + var input = slice.call( arguments, 1 ), + inputIndex = 0, + inputLength = input.length, + key, + value; + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; +}; + +$.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName || name; + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string", + args = slice.call( arguments, 1 ), + returnValue = this; + + // allow multiple hashes to be passed on init + options = !isMethodCall && args.length ? + $.widget.extend.apply( null, [ options ].concat(args) ) : + options; + + if ( isMethodCall ) { + this.each(function() { + var methodValue, + instance = $.data( this, fullName ); + if ( !instance ) { + return $.error( "cannot call methods on " + name + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + " widget instance" ); + } + methodValue = instance[ options ].apply( instance, args ); + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + }); + } else { + this.each(function() { + var instance = $.data( this, fullName ); + if ( instance ) { + instance.option( options || {} )._init(); + } else { + $.data( this, fullName, new object( options, this ) ); + } + }); + } + + return returnValue; + }; +}; + +$.Widget = function( /* options, element */ ) {}; +$.Widget._childConstructors = []; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + defaultElement: "<div>", + options: { + disabled: false, + + // callbacks + create: null + }, + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = uuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + }); + this.document = $( element.style ? + // element within the document + element.ownerDocument : + // element is window or document + element.document || element ); + this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); + } + + this._create(); + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + _getCreateOptions: $.noop, + _getCreateEventData: $.noop, + _create: $.noop, + _init: $.noop, + + destroy: function() { + this._destroy(); + // we can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .unbind( this.eventNamespace ) + // 1.9 BC for #7810 + // TODO remove dual storage + .removeData( this.widgetName ) + .removeData( this.widgetFullName ) + // support: jquery <1.6.3 + // http://bugs.jquery.com/ticket/9413 + .removeData( $.camelCase( this.widgetFullName ) ); + this.widget() + .unbind( this.eventNamespace ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetFullName + "-disabled " + + "ui-state-disabled" ); + + // clean up events and states + this.bindings.unbind( this.eventNamespace ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + }, + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key, + parts, + curOption, + i; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( value === undefined ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( value === undefined ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) + .attr( "aria-disabled", value ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + } + + return this; + }, + + enable: function() { + return this._setOption( "disabled", false ); + }, + disable: function() { + return this._setOption( "disabled", true ); + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement, + instance = this; + + // no suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // no element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + // accept selectors, DOM elements + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + // allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^(\w+)\s*(.*)$/ ), + eventName = match[1] + instance.eventNamespace, + selector = match[2]; + if ( selector ) { + delegateElement.delegate( selector, eventName, handlerProxy ); + } else { + element.bind( eventName, handlerProxy ); + } + }); + }, + + _off: function( element, eventName ) { + eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; + element.unbind( eventName ).undelegate( eventName ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + $( event.currentTarget ).addClass( "ui-state-hover" ); + }, + mouseleave: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-hover" ); + } + }); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + $( event.currentTarget ).addClass( "ui-state-focus" ); + }, + focusout: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-focus" ); + } + }); + }, + + _trigger: function( type, event, data ) { + var prop, orig, + callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + // the original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + return !( $.isFunction( callback ) && + callback.apply( this.element[0], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } +}; + +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + var hasOptions, + effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + if ( options.delay ) { + element.delay( options.delay ); + } + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue(function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + }); + } + }; +}); + +})); +\ No newline at end of file diff --git a/src/main/javascript/libs/liftAjax.js b/src/main/javascript/libs/liftAjax.js @@ -0,0 +1,212 @@ + + +(function() { + + window.liftAjax = { + lift_ajaxQueue: [], + lift_ajaxInProcess: null, + lift_doCycleQueueCnt: 0, + lift_ajaxShowing: false, + lift_ajaxRetryCount: 3, + + lift_ajaxHandler: function(theData, theSuccess, theFailure, responseType){ + var toSend = {retryCnt: 0}; + toSend.when = (new Date()).getTime(); + toSend.theData = theData; + toSend.onSuccess = theSuccess; + toSend.onFailure = theFailure; + toSend.responseType = responseType; + toSend.version = liftAjax.lift_ajaxVersion++; + + // Make sure we wrap when we hit JS max int. + var version = liftAjax.lift_ajaxVersion + if ((version - (version + 1) != -1) || (version - (version - 1) != 1)) + liftAjax.lift_ajaxVersion = 0; + + if (liftAjax.lift_uriSuffix) { + theData += '&' + liftAjax.lift_uriSuffix; + toSend.theData = theData; + liftAjax.lift_uriSuffix = undefined; + } + + liftAjax.lift_ajaxQueue.push(toSend); + liftAjax.lift_ajaxQueueSort(); + liftAjax.lift_doCycleQueueCnt++; + liftAjax.lift_doAjaxCycle(); + return false; // buttons in forms don't trigger the form + + }, + + lift_uriSuffix: undefined, + + lift_logError: function(msg) { + liftAjax.lift_defaultLogError(msg); + }, + + lift_defaultLogError: function(msg) { + if (console && typeof console.error == 'function') + console.error(msg); + else + alert(msg); + }, + + lift_ajaxQueueSort: function() { + liftAjax.lift_ajaxQueue.sort(function (a, b) {return a.when - b.when;}); + }, + + lift_defaultFailure: function() { + alert("The server cannot be contacted at this time"); + }, + + lift_startAjax: function() { + liftAjax.lift_ajaxShowing = true; + jQuery('#'+"ajax-spinner").show(); + }, + + lift_endAjax: function() { + liftAjax.lift_ajaxShowing = false; + jQuery('#'+"ajax-spinner").hide(); + }, + + lift_testAndShowAjax: function() { + if (liftAjax.lift_ajaxShowing && liftAjax.lift_ajaxQueue.length == 0 && liftAjax.lift_ajaxInProcess == null) { + liftAjax.lift_endAjax(); + } else if (!liftAjax.lift_ajaxShowing && (liftAjax.lift_ajaxQueue.length > 0 || liftAjax.lift_ajaxInProcess != null)) { + liftAjax.lift_startAjax(); + } + }, + + lift_traverseAndCall: function(node, func) { + if (node.nodeType == 1) func(node); + var i = 0; + var cn = node.childNodes; + + for (i = 0; i < cn.length; i++) { + liftAjax.lift_traverseAndCall(cn.item(i), func); + } + }, + + lift_successRegisterGC: function() { + setTimeout("liftAjax.lift_registerGC()", 75000); + }, + + lift_failRegisterGC: function() { + setTimeout("liftAjax.lift_registerGC()", 15000); + }, + + lift_registerGC: function() { + var data = "__lift__GC=_", + version = null; + jQuery.ajax({ url : liftAjax.addPageNameAndVersion("/ajax_request/", version), data : data, type : "POST", dataType : "script", timeout : 5000, cache : false, success : liftAjax.lift_successRegisterGC, error : liftAjax.lift_failRegisterGC }); + }, + + + lift_sessionLost: function() { + location.reload(); + }, + + lift_doAjaxCycle: function() { + if (liftAjax.lift_doCycleQueueCnt > 0) liftAjax.lift_doCycleQueueCnt--; + var queue = liftAjax.lift_ajaxQueue; + if (queue.length > 0) { + var now = (new Date()).getTime(); + if (liftAjax.lift_ajaxInProcess == null && queue[0].when <= now) { + var aboutToSend = queue.shift(); + + liftAjax.lift_ajaxInProcess = aboutToSend; + + var successFunc = function(data) { + liftAjax.lift_ajaxInProcess = null; + if (aboutToSend.onSuccess) { + aboutToSend.onSuccess(data); + } + liftAjax.lift_doCycleQueueCnt++; + liftAjax.lift_doAjaxCycle(); + }; + + var failureFunc = function() { + liftAjax.lift_ajaxInProcess = null; + var cnt = aboutToSend.retryCnt; + if (arguments.length == 3 && arguments[1] == 'parsererror') { + liftAjax.lift_logError('The server call succeeded, but the returned Javascript contains an error: '+arguments[2]) + } else + + if (cnt < liftAjax.lift_ajaxRetryCount) { + aboutToSend.retryCnt = cnt + 1; + var now = (new Date()).getTime(); + aboutToSend.when = now + (1000 * Math.pow(2, cnt)); + queue.push(aboutToSend); + liftAjax.lift_ajaxQueueSort(); + } else { + if (aboutToSend.onFailure) { + aboutToSend.onFailure(); + } else { + liftAjax.lift_defaultFailure(); + } + } + liftAjax.lift_doCycleQueueCnt++; + liftAjax.lift_doAjaxCycle(); + }; + + if (aboutToSend.responseType != undefined && + aboutToSend.responseType != null && + aboutToSend.responseType.toLowerCase() === "json") { + liftAjax.lift_actualJSONCall(aboutToSend.theData, successFunc, failureFunc); + } else { + var theData = aboutToSend.theData, + version = aboutToSend.version; + + liftAjax.lift_actualAjaxCall(theData, version, successFunc, failureFunc); + } + } + } + + liftAjax.lift_testAndShowAjax(); + if (liftAjax.lift_doCycleQueueCnt <= 0) liftAjax.lift_doCycleIn200() + }, + + lift_doCycleIn200: function() { + liftAjax.lift_doCycleQueueCnt++; + setTimeout("liftAjax.lift_doAjaxCycle();", 200); + }, + + lift_ajaxVersion: 0, + + addPageNameAndVersion: function(url, version) { + + var replacement = 'ajax_request/'+lift_page; + if (version!=null) + replacement += ('-'+version.toString(36)) + (liftAjax.lift_ajaxQueue.length > 35 ? 35 : liftAjax.lift_ajaxQueue.length).toString(36); + return url.replace('ajax_request', replacement); + }, + + lift_actualAjaxCall: function(data, version, onSuccess, onFailure) { + jQuery.ajax({ url : liftAjax.addPageNameAndVersion("/ajax_request/", version), data : data, type : "POST", dataType : "script", timeout : 5000, cache : false, success : onSuccess, error : onFailure }); + }, + + lift_actualJSONCall: function(data, onSuccess, onFailure) { + var version = null; + jQuery.ajax({ url : liftAjax.addPageNameAndVersion("/ajax_request/", version), data : data, type : "POST", dataType : "json", timeout : 5000, cache : false, success : onSuccess, error : onFailure }); + } + }; + + window.liftUtils = { + lift_blurIfReturn: function(e) { + var code; + if (!e) var e = window.event; + if (e.keyCode) code = e.keyCode; + else if (e.which) code = e.which; + + var targ; + + if (e.target) targ = e.target; + else if (e.srcElement) targ = e.srcElement; + if (targ.nodeType == 3) // defeat Safari bug + targ = targ.parentNode; + if (code == 13) {targ.blur(); return false;} else {return true;}; + } + }; + + + })(); + jQuery(document).ready(function() {liftAjax.lift_doCycleIn200();}); diff --git a/src/main/javascript/libs/mediaelement-and-player.min.js b/src/main/javascript/libs/mediaelement-and-player.min.js @@ -0,0 +1,172 @@ +/*! +* MediaElement.js +* HTML5 <video> and <audio> shim and player +* http://mediaelementjs.com/ +* +* Creates a JavaScript object that mimics HTML5 MediaElement API +* for browsers that don't understand HTML5 or can't play the provided codec +* Can play MP4 (H.264), Ogg, WebM, FLV, WMV, WMA, ACC, and MP3 +* +* Copyright 2010-2013, John Dyer (http://j.hn) +* License: MIT +* +*/var mejs=mejs||{};mejs.version="2.12.0";mejs.meIndex=0; +mejs.plugins={silverlight:[{version:[3,0],types:["video/mp4","video/m4v","video/mov","video/wmv","audio/wma","audio/m4a","audio/mp3","audio/wav","audio/mpeg"]}],flash:[{version:[9,0,124],types:["video/mp4","video/m4v","video/mov","video/flv","video/rtmp","video/x-flv","audio/flv","audio/x-flv","audio/mp3","audio/m4a","audio/mpeg","video/youtube","video/x-youtube"]}],youtube:[{version:null,types:["video/youtube","video/x-youtube","audio/youtube","audio/x-youtube"]}],vimeo:[{version:null,types:["video/vimeo", +"video/x-vimeo"]}]}; +mejs.Utility={encodeUrl:function(a){return encodeURIComponent(a)},escapeHTML:function(a){return a.toString().split("&").join("&").split("<").join("<").split('"').join(""")},absolutizeUrl:function(a){var b=document.createElement("div");b.innerHTML='<a href="'+this.escapeHTML(a)+'">x</a>';return b.firstChild.href},getScriptPath:function(a){for(var b=0,c,d="",e="",f,g,h=document.getElementsByTagName("script"),l=h.length,j=a.length;b<l;b++){f=h[b].src;c=f.lastIndexOf("/");if(c>-1){g=f.substring(c+ +1);f=f.substring(0,c+1)}else{g=f;f=""}for(c=0;c<j;c++){e=a[c];e=g.indexOf(e);if(e>-1){d=f;break}}if(d!=="")break}return d},secondsToTimeCode:function(a,b,c,d){if(typeof c=="undefined")c=false;else if(typeof d=="undefined")d=25;var e=Math.floor(a/3600)%24,f=Math.floor(a/60)%60,g=Math.floor(a%60);a=Math.floor((a%1*d).toFixed(3));return(b||e>0?(e<10?"0"+e:e)+":":"")+(f<10?"0"+f:f)+":"+(g<10?"0"+g:g)+(c?":"+(a<10?"0"+a:a):"")},timeCodeToSeconds:function(a,b,c,d){if(typeof c=="undefined")c=false;else if(typeof d== +"undefined")d=25;a=a.split(":");b=parseInt(a[0],10);var e=parseInt(a[1],10),f=parseInt(a[2],10),g=0,h=0;if(c)g=parseInt(a[3])/d;return h=b*3600+e*60+f+g},convertSMPTEtoSeconds:function(a){if(typeof a!="string")return false;a=a.replace(",",".");var b=0,c=a.indexOf(".")!=-1?a.split(".")[1].length:0,d=1;a=a.split(":").reverse();for(var e=0;e<a.length;e++){d=1;if(e>0)d=Math.pow(60,e);b+=Number(a[e])*d}return Number(b.toFixed(c))},removeSwf:function(a){var b=document.getElementById(a);if(b&&/object|embed/i.test(b.nodeName))if(mejs.MediaFeatures.isIE){b.style.display= +"none";(function(){b.readyState==4?mejs.Utility.removeObjectInIE(a):setTimeout(arguments.callee,10)})()}else b.parentNode.removeChild(b)},removeObjectInIE:function(a){if(a=document.getElementById(a)){for(var b in a)if(typeof a[b]=="function")a[b]=null;a.parentNode.removeChild(a)}}}; +mejs.PluginDetector={hasPluginVersion:function(a,b){var c=this.plugins[a];b[1]=b[1]||0;b[2]=b[2]||0;return c[0]>b[0]||c[0]==b[0]&&c[1]>b[1]||c[0]==b[0]&&c[1]==b[1]&&c[2]>=b[2]?true:false},nav:window.navigator,ua:window.navigator.userAgent.toLowerCase(),plugins:[],addPlugin:function(a,b,c,d,e){this.plugins[a]=this.detectPlugin(b,c,d,e)},detectPlugin:function(a,b,c,d){var e=[0,0,0],f;if(typeof this.nav.plugins!="undefined"&&typeof this.nav.plugins[a]=="object"){if((c=this.nav.plugins[a].description)&& +!(typeof this.nav.mimeTypes!="undefined"&&this.nav.mimeTypes[b]&&!this.nav.mimeTypes[b].enabledPlugin)){e=c.replace(a,"").replace(/^\s+/,"").replace(/\sr/gi,".").split(".");for(a=0;a<e.length;a++)e[a]=parseInt(e[a].match(/\d+/),10)}}else if(typeof window.ActiveXObject!="undefined")try{if(f=new ActiveXObject(c))e=d(f)}catch(g){}return e}}; +mejs.PluginDetector.addPlugin("flash","Shockwave Flash","application/x-shockwave-flash","ShockwaveFlash.ShockwaveFlash",function(a){var b=[];if(a=a.GetVariable("$version")){a=a.split(" ")[1].split(",");b=[parseInt(a[0],10),parseInt(a[1],10),parseInt(a[2],10)]}return b}); +mejs.PluginDetector.addPlugin("silverlight","Silverlight Plug-In","application/x-silverlight-2","AgControl.AgControl",function(a){var b=[0,0,0,0],c=function(d,e,f,g){for(;d.isVersionSupported(e[0]+"."+e[1]+"."+e[2]+"."+e[3]);)e[f]+=g;e[f]-=g};c(a,b,0,1);c(a,b,1,1);c(a,b,2,1E4);c(a,b,2,1E3);c(a,b,2,100);c(a,b,2,10);c(a,b,2,1);c(a,b,3,1);return b}); +mejs.MediaFeatures={init:function(){var a=this,b=document,c=mejs.PluginDetector.nav,d=mejs.PluginDetector.ua.toLowerCase(),e,f=["source","track","audio","video"];a.isiPad=d.match(/ipad/i)!==null;a.isiPhone=d.match(/iphone/i)!==null;a.isiOS=a.isiPhone||a.isiPad;a.isAndroid=d.match(/android/i)!==null;a.isBustedAndroid=d.match(/android 2\.[12]/)!==null;a.isBustedNativeHTTPS=location.protocol==="https:"&&(d.match(/android [12]\./)!==null||d.match(/macintosh.* version.* safari/)!==null);a.isIE=c.appName.toLowerCase().indexOf("microsoft")!= +-1;a.isChrome=d.match(/chrome/gi)!==null;a.isFirefox=d.match(/firefox/gi)!==null;a.isWebkit=d.match(/webkit/gi)!==null;a.isGecko=d.match(/gecko/gi)!==null&&!a.isWebkit;a.isOpera=d.match(/opera/gi)!==null;a.hasTouch="ontouchstart"in window;a.svg=!!document.createElementNS&&!!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect;for(c=0;c<f.length;c++)e=document.createElement(f[c]);a.supportsMediaTag=typeof e.canPlayType!=="undefined"||a.isBustedAndroid;try{e.canPlayType("video/mp4")}catch(g){a.supportsMediaTag= +false}a.hasSemiNativeFullScreen=typeof e.webkitEnterFullscreen!=="undefined";a.hasWebkitNativeFullScreen=typeof e.webkitRequestFullScreen!=="undefined";a.hasMozNativeFullScreen=typeof e.mozRequestFullScreen!=="undefined";a.hasTrueNativeFullScreen=a.hasWebkitNativeFullScreen||a.hasMozNativeFullScreen;a.nativeFullScreenEnabled=a.hasTrueNativeFullScreen;if(a.hasMozNativeFullScreen)a.nativeFullScreenEnabled=e.mozFullScreenEnabled;if(this.isChrome)a.hasSemiNativeFullScreen=false;if(a.hasTrueNativeFullScreen){a.fullScreenEventName= +a.hasWebkitNativeFullScreen?"webkitfullscreenchange":"mozfullscreenchange";a.isFullScreen=function(){if(e.mozRequestFullScreen)return b.mozFullScreen;else if(e.webkitRequestFullScreen)return b.webkitIsFullScreen};a.requestFullScreen=function(h){if(a.hasWebkitNativeFullScreen)h.webkitRequestFullScreen();else a.hasMozNativeFullScreen&&h.mozRequestFullScreen()};a.cancelFullScreen=function(){if(a.hasWebkitNativeFullScreen)document.webkitCancelFullScreen();else a.hasMozNativeFullScreen&&document.mozCancelFullScreen()}}if(a.hasSemiNativeFullScreen&& +d.match(/mac os x 10_5/i)){a.hasNativeFullScreen=false;a.hasSemiNativeFullScreen=false}}};mejs.MediaFeatures.init(); +mejs.HtmlMediaElement={pluginType:"native",isFullScreen:false,setCurrentTime:function(a){this.currentTime=a},setMuted:function(a){this.muted=a},setVolume:function(a){this.volume=a},stop:function(){this.pause()},setSrc:function(a){for(var b=this.getElementsByTagName("source");b.length>0;)this.removeChild(b[0]);if(typeof a=="string")this.src=a;else{var c;for(b=0;b<a.length;b++){c=a[b];if(this.canPlayType(c.type)){this.src=c.src;break}}}},setVideoSize:function(a,b){this.width=a;this.height=b}}; +mejs.PluginMediaElement=function(a,b,c){this.id=a;this.pluginType=b;this.src=c;this.events={};this.attributes={}}; +mejs.PluginMediaElement.prototype={pluginElement:null,pluginType:"",isFullScreen:false,playbackRate:-1,defaultPlaybackRate:-1,seekable:[],played:[],paused:true,ended:false,seeking:false,duration:0,error:null,tagName:"",muted:false,volume:1,currentTime:0,play:function(){if(this.pluginApi!=null){this.pluginType=="youtube"?this.pluginApi.playVideo():this.pluginApi.playMedia();this.paused=false}},load:function(){if(this.pluginApi!=null){this.pluginType!="youtube"&&this.pluginApi.loadMedia();this.paused= +false}},pause:function(){if(this.pluginApi!=null){this.pluginType=="youtube"?this.pluginApi.pauseVideo():this.pluginApi.pauseMedia();this.paused=true}},stop:function(){if(this.pluginApi!=null){this.pluginType=="youtube"?this.pluginApi.stopVideo():this.pluginApi.stopMedia();this.paused=true}},canPlayType:function(a){var b,c,d,e=mejs.plugins[this.pluginType];for(b=0;b<e.length;b++){d=e[b];if(mejs.PluginDetector.hasPluginVersion(this.pluginType,d.version))for(c=0;c<d.types.length;c++)if(a==d.types[c])return"probably"}return""}, +positionFullscreenButton:function(a,b,c){this.pluginApi!=null&&this.pluginApi.positionFullscreenButton&&this.pluginApi.positionFullscreenButton(Math.floor(a),Math.floor(b),c)},hideFullscreenButton:function(){this.pluginApi!=null&&this.pluginApi.hideFullscreenButton&&this.pluginApi.hideFullscreenButton()},setSrc:function(a){if(typeof a=="string"){this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(a));this.src=mejs.Utility.absolutizeUrl(a)}else{var b,c;for(b=0;b<a.length;b++){c=a[b];if(this.canPlayType(c.type)){this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(c.src)); +this.src=mejs.Utility.absolutizeUrl(a);break}}}},setCurrentTime:function(a){if(this.pluginApi!=null){this.pluginType=="youtube"?this.pluginApi.seekTo(a):this.pluginApi.setCurrentTime(a);this.currentTime=a}},setVolume:function(a){if(this.pluginApi!=null){this.pluginType=="youtube"?this.pluginApi.setVolume(a*100):this.pluginApi.setVolume(a);this.volume=a}},setMuted:function(a){if(this.pluginApi!=null){if(this.pluginType=="youtube"){a?this.pluginApi.mute():this.pluginApi.unMute();this.muted=a;this.dispatchEvent("volumechange")}else this.pluginApi.setMuted(a); +this.muted=a}},setVideoSize:function(a,b){if(this.pluginElement.style){this.pluginElement.style.width=a+"px";this.pluginElement.style.height=b+"px"}this.pluginApi!=null&&this.pluginApi.setVideoSize&&this.pluginApi.setVideoSize(a,b)},setFullscreen:function(a){this.pluginApi!=null&&this.pluginApi.setFullscreen&&this.pluginApi.setFullscreen(a)},enterFullScreen:function(){this.pluginApi!=null&&this.pluginApi.setFullscreen&&this.setFullscreen(true)},exitFullScreen:function(){this.pluginApi!=null&&this.pluginApi.setFullscreen&& +this.setFullscreen(false)},addEventListener:function(a,b){this.events[a]=this.events[a]||[];this.events[a].push(b)},removeEventListener:function(a,b){if(!a){this.events={};return true}var c=this.events[a];if(!c)return true;if(!b){this.events[a]=[];return true}for(i=0;i<c.length;i++)if(c[i]===b){this.events[a].splice(i,1);return true}return false},dispatchEvent:function(a){var b,c,d=this.events[a];if(d){c=Array.prototype.slice.call(arguments,1);for(b=0;b<d.length;b++)d[b].apply(null,c)}},hasAttribute:function(a){return a in +this.attributes},removeAttribute:function(a){delete this.attributes[a]},getAttribute:function(a){if(this.hasAttribute(a))return this.attributes[a];return""},setAttribute:function(a,b){this.attributes[a]=b},remove:function(){mejs.Utility.removeSwf(this.pluginElement.id);mejs.MediaPluginBridge.unregisterPluginElement(this.pluginElement.id)}}; +mejs.MediaPluginBridge={pluginMediaElements:{},htmlMediaElements:{},registerPluginElement:function(a,b,c){this.pluginMediaElements[a]=b;this.htmlMediaElements[a]=c},unregisterPluginElement:function(a){delete this.pluginMediaElements[a];delete this.htmlMediaElements[a]},initPlugin:function(a){var b=this.pluginMediaElements[a],c=this.htmlMediaElements[a];if(b){switch(b.pluginType){case "flash":b.pluginElement=b.pluginApi=document.getElementById(a);break;case "silverlight":b.pluginElement=document.getElementById(b.id); +b.pluginApi=b.pluginElement.Content.MediaElementJS}b.pluginApi!=null&&b.success&&b.success(b,c)}},fireEvent:function(a,b,c){var d,e;a=this.pluginMediaElements[a];b={type:b,target:a};for(d in c){a[d]=c[d];b[d]=c[d]}e=c.bufferedTime||0;b.target.buffered=b.buffered={start:function(){return 0},end:function(){return e},length:1};a.dispatchEvent(b.type,b)}}; +mejs.MediaElementDefaults={mode:"auto",plugins:["flash","silverlight","youtube","vimeo"],enablePluginDebug:false,type:"",pluginPath:mejs.Utility.getScriptPath(["mediaelement.js","mediaelement.min.js","mediaelement-and-player.js","mediaelement-and-player.min.js"]),flashName:"flashmediaelement.swf",flashStreamer:"",enablePluginSmoothing:false,enablePseudoStreaming:false,pseudoStreamingStartQueryParam:"start",silverlightName:"silverlightmediaelement.xap",defaultVideoWidth:480,defaultVideoHeight:270, +pluginWidth:-1,pluginHeight:-1,pluginVars:[],timerRate:250,startVolume:0.8,success:function(){},error:function(){}};mejs.MediaElement=function(a,b){return mejs.HtmlMediaElementShim.create(a,b)}; +mejs.HtmlMediaElementShim={create:function(a,b){var c=mejs.MediaElementDefaults,d=typeof a=="string"?document.getElementById(a):a,e=d.tagName.toLowerCase(),f=e==="audio"||e==="video",g=f?d.getAttribute("src"):d.getAttribute("href");e=d.getAttribute("poster");var h=d.getAttribute("autoplay"),l=d.getAttribute("preload"),j=d.getAttribute("controls"),k;for(k in b)c[k]=b[k];g=typeof g=="undefined"||g===null||g==""?null:g;e=typeof e=="undefined"||e===null?"":e;l=typeof l=="undefined"||l===null||l==="false"? +"none":l;h=!(typeof h=="undefined"||h===null||h==="false");j=!(typeof j=="undefined"||j===null||j==="false");k=this.determinePlayback(d,c,mejs.MediaFeatures.supportsMediaTag,f,g);k.url=k.url!==null?mejs.Utility.absolutizeUrl(k.url):"";if(k.method=="native"){if(mejs.MediaFeatures.isBustedAndroid){d.src=k.url;d.addEventListener("click",function(){d.play()},false)}return this.updateNative(k,c,h,l)}else if(k.method!=="")return this.createPlugin(k,c,e,h,l,j);else{this.createErrorMessage(k,c,e);return this}}, +determinePlayback:function(a,b,c,d,e){var f=[],g,h,l,j={method:"",url:"",htmlMediaElement:a,isVideo:a.tagName.toLowerCase()!="audio"},k;if(typeof b.type!="undefined"&&b.type!=="")if(typeof b.type=="string")f.push({type:b.type,url:e});else for(g=0;g<b.type.length;g++)f.push({type:b.type[g],url:e});else if(e!==null){l=this.formatType(e,a.getAttribute("type"));f.push({type:l,url:e})}else for(g=0;g<a.childNodes.length;g++){h=a.childNodes[g];if(h.nodeType==1&&h.tagName.toLowerCase()=="source"){e=h.getAttribute("src"); +l=this.formatType(e,h.getAttribute("type"));h=h.getAttribute("media");if(!h||!window.matchMedia||window.matchMedia&&window.matchMedia(h).matches)f.push({type:l,url:e})}}if(!d&&f.length>0&&f[0].url!==null&&this.getTypeFromFile(f[0].url).indexOf("audio")>-1)j.isVideo=false;if(mejs.MediaFeatures.isBustedAndroid)a.canPlayType=function(m){return m.match(/video\/(mp4|m4v)/gi)!==null?"maybe":""};if(c&&(b.mode==="auto"||b.mode==="auto_plugin"||b.mode==="native")&&!mejs.MediaFeatures.isBustedNativeHTTPS){if(!d){g= +document.createElement(j.isVideo?"video":"audio");a.parentNode.insertBefore(g,a);a.style.display="none";j.htmlMediaElement=a=g}for(g=0;g<f.length;g++)if(a.canPlayType(f[g].type).replace(/no/,"")!==""||a.canPlayType(f[g].type.replace(/mp3/,"mpeg")).replace(/no/,"")!==""){j.method="native";j.url=f[g].url;break}if(j.method==="native"){if(j.url!==null)a.src=j.url;if(b.mode!=="auto_plugin")return j}}if(b.mode==="auto"||b.mode==="auto_plugin"||b.mode==="shim")for(g=0;g<f.length;g++){l=f[g].type;for(a=0;a< +b.plugins.length;a++){e=b.plugins[a];h=mejs.plugins[e];for(c=0;c<h.length;c++){k=h[c];if(k.version==null||mejs.PluginDetector.hasPluginVersion(e,k.version))for(d=0;d<k.types.length;d++)if(l==k.types[d]){j.method=e;j.url=f[g].url;return j}}}}if(b.mode==="auto_plugin"&&j.method==="native")return j;if(j.method===""&&f.length>0)j.url=f[0].url;return j},formatType:function(a,b){return a&&!b?this.getTypeFromFile(a):b&&~b.indexOf(";")?b.substr(0,b.indexOf(";")):b},getTypeFromFile:function(a){a=a.split("?")[0]; +a=a.substring(a.lastIndexOf(".")+1).toLowerCase();return(/(mp4|m4v|ogg|ogv|webm|webmv|flv|wmv|mpeg|mov)/gi.test(a)?"video":"audio")+"/"+this.getTypeFromExtension(a)},getTypeFromExtension:function(a){switch(a){case "mp4":case "m4v":return"mp4";case "webm":case "webma":case "webmv":return"webm";case "ogg":case "oga":case "ogv":return"ogg";default:return a}},createErrorMessage:function(a,b,c){var d=a.htmlMediaElement,e=document.createElement("div");e.className="me-cannotplay";try{e.style.width=d.width+ +"px";e.style.height=d.height+"px"}catch(f){}e.innerHTML=b.customError?b.customError:c!==""?'<a href="'+a.url+'"><img src="'+c+'" width="100%" height="100%" /></a>':'<a href="'+a.url+'"><span>'+mejs.i18n.t("Download File")+"</span></a>";d.parentNode.insertBefore(e,d);d.style.display="none";b.error(d)},createPlugin:function(a,b,c,d,e,f){c=a.htmlMediaElement;var g=1,h=1,l="me_"+a.method+"_"+mejs.meIndex++,j=new mejs.PluginMediaElement(l,a.method,a.url),k=document.createElement("div"),m;j.tagName=c.tagName; +for(m=0;m<c.attributes.length;m++){var n=c.attributes[m];n.specified==true&&j.setAttribute(n.name,n.value)}for(m=c.parentNode;m!==null&&m.tagName.toLowerCase()!="body";){if(m.parentNode.tagName.toLowerCase()=="p"){m.parentNode.parentNode.insertBefore(m,m.parentNode);break}m=m.parentNode}if(a.isVideo){g=b.pluginWidth>0?b.pluginWidth:b.videoWidth>0?b.videoWidth:c.getAttribute("width")!==null?c.getAttribute("width"):b.defaultVideoWidth;h=b.pluginHeight>0?b.pluginHeight:b.videoHeight>0?b.videoHeight: +c.getAttribute("height")!==null?c.getAttribute("height"):b.defaultVideoHeight;g=mejs.Utility.encodeUrl(g);h=mejs.Utility.encodeUrl(h)}else if(b.enablePluginDebug){g=320;h=240}j.success=b.success;mejs.MediaPluginBridge.registerPluginElement(l,j,c);k.className="me-plugin";k.id=l+"_container";a.isVideo?c.parentNode.insertBefore(k,c):document.body.insertBefore(k,document.body.childNodes[0]);d=["id="+l,"isvideo="+(a.isVideo?"true":"false"),"autoplay="+(d?"true":"false"),"preload="+e,"width="+g,"startvolume="+ +b.startVolume,"timerrate="+b.timerRate,"flashstreamer="+b.flashStreamer,"height="+h,"pseudostreamstart="+b.pseudoStreamingStartQueryParam];if(a.url!==null)a.method=="flash"?d.push("file="+mejs.Utility.encodeUrl(a.url)):d.push("file="+a.url);b.enablePluginDebug&&d.push("debug=true");b.enablePluginSmoothing&&d.push("smoothing=true");b.enablePseudoStreaming&&d.push("pseudostreaming=true");f&&d.push("controls=true");if(b.pluginVars)d=d.concat(b.pluginVars);switch(a.method){case "silverlight":k.innerHTML= +'<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="'+l+'" name="'+l+'" width="'+g+'" height="'+h+'" class="mejs-shim"><param name="initParams" value="'+d.join(",")+'" /><param name="windowless" value="true" /><param name="background" value="black" /><param name="minRuntimeVersion" value="3.0.0.0" /><param name="autoUpgrade" value="true" /><param name="source" value="'+b.pluginPath+b.silverlightName+'" /></object>';break;case "flash":if(mejs.MediaFeatures.isIE){a= +document.createElement("div");k.appendChild(a);a.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" id="'+l+'" width="'+g+'" height="'+h+'" class="mejs-shim"><param name="movie" value="'+b.pluginPath+b.flashName+"?x="+new Date+'" /><param name="flashvars" value="'+d.join("&")+'" /><param name="quality" value="high" /><param name="bgcolor" value="#000000" /><param name="wmode" value="transparent" /><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="true" /></object>'}else k.innerHTML= +'<embed id="'+l+'" name="'+l+'" play="true" loop="false" quality="high" bgcolor="#000000" wmode="transparent" allowScriptAccess="always" allowFullScreen="true" type="application/x-shockwave-flash" pluginspage="//www.macromedia.com/go/getflashplayer" src="'+b.pluginPath+b.flashName+'" flashvars="'+d.join("&")+'" width="'+g+'" height="'+h+'" class="mejs-shim"></embed>';break;case "youtube":b=a.url.substr(a.url.lastIndexOf("=")+1);youtubeSettings={container:k,containerId:k.id,pluginMediaElement:j,pluginId:l, +videoId:b,height:h,width:g};mejs.PluginDetector.hasPluginVersion("flash",[10,0,0])?mejs.YouTubeApi.createFlash(youtubeSettings):mejs.YouTubeApi.enqueueIframe(youtubeSettings);break;case "vimeo":j.vimeoid=a.url.substr(a.url.lastIndexOf("/")+1);k.innerHTML='<iframe src="http://player.vimeo.com/video/'+j.vimeoid+'?portrait=0&byline=0&title=0" width="'+g+'" height="'+h+'" frameborder="0" class="mejs-shim"></iframe>'}c.style.display="none";return j},updateNative:function(a,b){var c=a.htmlMediaElement, +d;for(d in mejs.HtmlMediaElement)c[d]=mejs.HtmlMediaElement[d];b.success(c,c);return c}}; +mejs.YouTubeApi={isIframeStarted:false,isIframeLoaded:false,loadIframeApi:function(){if(!this.isIframeStarted){var a=document.createElement("script");a.src="//www.youtube.com/player_api";var b=document.getElementsByTagName("script")[0];b.parentNode.insertBefore(a,b);this.isIframeStarted=true}},iframeQueue:[],enqueueIframe:function(a){if(this.isLoaded)this.createIframe(a);else{this.loadIframeApi();this.iframeQueue.push(a)}},createIframe:function(a){var b=a.pluginMediaElement,c=new YT.Player(a.containerId, +{height:a.height,width:a.width,videoId:a.videoId,playerVars:{controls:0},events:{onReady:function(){a.pluginMediaElement.pluginApi=c;mejs.MediaPluginBridge.initPlugin(a.pluginId);setInterval(function(){mejs.YouTubeApi.createEvent(c,b,"timeupdate")},250)},onStateChange:function(d){mejs.YouTubeApi.handleStateChange(d.data,c,b)}}})},createEvent:function(a,b,c){c={type:c,target:b};if(a&&a.getDuration){b.currentTime=c.currentTime=a.getCurrentTime();b.duration=c.duration=a.getDuration();c.paused=b.paused; +c.ended=b.ended;c.muted=a.isMuted();c.volume=a.getVolume()/100;c.bytesTotal=a.getVideoBytesTotal();c.bufferedBytes=a.getVideoBytesLoaded();var d=c.bufferedBytes/c.bytesTotal*c.duration;c.target.buffered=c.buffered={start:function(){return 0},end:function(){return d},length:1}}b.dispatchEvent(c.type,c)},iFrameReady:function(){for(this.isIframeLoaded=this.isLoaded=true;this.iframeQueue.length>0;)this.createIframe(this.iframeQueue.pop())},flashPlayers:{},createFlash:function(a){this.flashPlayers[a.pluginId]= +a;var b,c="//www.youtube.com/apiplayer?enablejsapi=1&playerapiid="+a.pluginId+"&version=3&autoplay=0&controls=0&modestbranding=1&loop=0";if(mejs.MediaFeatures.isIE){b=document.createElement("div");a.container.appendChild(b);b.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" id="'+a.pluginId+'" width="'+a.width+'" height="'+a.height+'" class="mejs-shim"><param name="movie" value="'+ +c+'" /><param name="wmode" value="transparent" /><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="true" /></object>'}else a.container.innerHTML='<object type="application/x-shockwave-flash" id="'+a.pluginId+'" data="'+c+'" width="'+a.width+'" height="'+a.height+'" style="visibility: visible; " class="mejs-shim"><param name="allowScriptAccess" value="always"><param name="wmode" value="transparent"></object>'},flashReady:function(a){var b=this.flashPlayers[a],c= +document.getElementById(a),d=b.pluginMediaElement;d.pluginApi=d.pluginElement=c;mejs.MediaPluginBridge.initPlugin(a);c.cueVideoById(b.videoId);a=b.containerId+"_callback";window[a]=function(e){mejs.YouTubeApi.handleStateChange(e,c,d)};c.addEventListener("onStateChange",a);setInterval(function(){mejs.YouTubeApi.createEvent(c,d,"timeupdate")},250)},handleStateChange:function(a,b,c){switch(a){case -1:c.paused=true;c.ended=true;mejs.YouTubeApi.createEvent(b,c,"loadedmetadata");break;case 0:c.paused=false; +c.ended=true;mejs.YouTubeApi.createEvent(b,c,"ended");break;case 1:c.paused=false;c.ended=false;mejs.YouTubeApi.createEvent(b,c,"play");mejs.YouTubeApi.createEvent(b,c,"playing");break;case 2:c.paused=true;c.ended=false;mejs.YouTubeApi.createEvent(b,c,"pause");break;case 3:mejs.YouTubeApi.createEvent(b,c,"progress")}}};function onYouTubePlayerAPIReady(){mejs.YouTubeApi.iFrameReady()}function onYouTubePlayerReady(a){mejs.YouTubeApi.flashReady(a)}window.mejs=mejs;window.MediaElement=mejs.MediaElement; +(function(a,b){var c={locale:{strings:{}},methods:{}};c.locale.getLanguage=function(){return c.locale||{language:navigator.language}};c.locale.INIT_LANGUAGE=c.locale.getLanguage();c.methods.checkPlain=function(d){var e,f,g={"&":"&",'"':""","<":"<",">":">"};d=String(d);for(e in g)if(g.hasOwnProperty(e)){f=RegExp(e,"g");d=d.replace(f,g[e])}return d};c.methods.formatString=function(d,e){for(var f in e){switch(f.charAt(0)){case "@":e[f]=c.methods.checkPlain(e[f]);break;case "!":break;default:e[f]= +'<em class="placeholder">'+c.methods.checkPlain(e[f])+"</em>"}d=d.replace(f,e[f])}return d};c.methods.t=function(d,e,f){if(c.locale.strings&&c.locale.strings[f.context]&&c.locale.strings[f.context][d])d=c.locale.strings[f.context][d];if(e)d=c.methods.formatString(d,e);return d};c.t=function(d,e,f){if(typeof d==="string"&&d.length>0){var g=c.locale.getLanguage();f=f||{context:g.language};return c.methods.t(d,e,f)}else throw{name:"InvalidArgumentException",message:"First argument is either not a string or empty."}; +};b.i18n=c})(document,mejs);(function(a){if(mejs.i18n.locale.language&&mejs.i18n.locale.strings)a[mejs.i18n.locale.language]=mejs.i18n.locale.strings})(mejs.i18n.locale.strings);(function(a){a.de={Fullscreen:"Vollbild","Go Fullscreen":"Vollbild an","Turn off Fullscreen":"Vollbild aus",Close:"Schlie\u00dfen"}})(mejs.i18n.locale.strings);(function(a){a.zh={Fullscreen:"\u5168\u87a2\u5e55","Go Fullscreen":"\u5168\u5c4f\u6a21\u5f0f","Turn off Fullscreen":"\u9000\u51fa\u5168\u5c4f\u6a21\u5f0f",Close:"\u95dc\u9589"}})(mejs.i18n.locale.strings); + +/*! + * MediaElementPlayer + * http://mediaelementjs.com/ + * + * Creates a controller bar for HTML5 <video> add <audio> tags + * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper) + * + * Copyright 2010-2013, John Dyer (http://j.hn/) + * License: MIT + * + */if(typeof jQuery!="undefined")mejs.$=jQuery;else if(typeof ender!="undefined")mejs.$=ender; +(function(f){mejs.MepDefaults={poster:"",defaultVideoWidth:480,defaultVideoHeight:270,videoWidth:-1,videoHeight:-1,defaultAudioWidth:400,defaultAudioHeight:30,defaultSeekBackwardInterval:function(a){return a.duration*0.05},defaultSeekForwardInterval:function(a){return a.duration*0.05},audioWidth:-1,audioHeight:-1,startVolume:0.8,loop:false,autoRewind:true,enableAutosize:true,alwaysShowHours:false,showTimecodeFrameCount:false,framesPerSecond:25,autosizeProgress:true,alwaysShowControls:false,hideVideoControlsOnLoad:false, +clickToPlayPause:true,iPadUseNativeControls:false,iPhoneUseNativeControls:false,AndroidUseNativeControls:false,features:["playpause","current","progress","duration","tracks","volume","fullscreen"],isVideo:true,enableKeyboard:true,pauseOtherPlayers:true,keyActions:[{keys:[32,179],action:function(a,b){b.paused||b.ended?b.play():b.pause()}},{keys:[38],action:function(a,b){b.setVolume(Math.min(b.volume+0.1,1))}},{keys:[40],action:function(a,b){b.setVolume(Math.max(b.volume-0.1,0))}},{keys:[37,227],action:function(a, +b){if(!isNaN(b.duration)&&b.duration>0){if(a.isVideo){a.showControls();a.startControlsTimer()}var c=Math.max(b.currentTime-a.options.defaultSeekBackwardInterval(b),0);b.setCurrentTime(c)}}},{keys:[39,228],action:function(a,b){if(!isNaN(b.duration)&&b.duration>0){if(a.isVideo){a.showControls();a.startControlsTimer()}var c=Math.min(b.currentTime+a.options.defaultSeekForwardInterval(b),b.duration);b.setCurrentTime(c)}}},{keys:[70],action:function(a){if(typeof a.enterFullScreen!="undefined")a.isFullScreen? +a.exitFullScreen():a.enterFullScreen()}}]};mejs.mepIndex=0;mejs.players={};mejs.MediaElementPlayer=function(a,b){if(!(this instanceof mejs.MediaElementPlayer))return new mejs.MediaElementPlayer(a,b);this.$media=this.$node=f(a);this.node=this.media=this.$media[0];if(typeof this.node.player!="undefined")return this.node.player;else this.node.player=this;if(typeof b=="undefined")b=this.$node.data("mejsoptions");this.options=f.extend({},mejs.MepDefaults,b);this.id="mep_"+mejs.mepIndex++;mejs.players[this.id]= +this;this.init();return this};mejs.MediaElementPlayer.prototype={hasFocus:false,controlsAreVisible:true,init:function(){var a=this,b=mejs.MediaFeatures,c=f.extend(true,{},a.options,{success:function(d,g){a.meReady(d,g)},error:function(d){a.handleError(d)}}),e=a.media.tagName.toLowerCase();a.isDynamic=e!=="audio"&&e!=="video";a.isVideo=a.isDynamic?a.options.isVideo:e!=="audio"&&a.options.isVideo;if(b.isiPad&&a.options.iPadUseNativeControls||b.isiPhone&&a.options.iPhoneUseNativeControls){a.$media.attr("controls", +"controls");if(b.isiPad&&a.media.getAttribute("autoplay")!==null){a.media.load();a.media.play()}}else if(!(b.isAndroid&&a.options.AndroidUseNativeControls)){a.$media.removeAttr("controls");a.container=f('<div id="'+a.id+'" class="mejs-container '+(mejs.MediaFeatures.svg?"svg":"no-svg")+'"><div class="mejs-inner"><div class="mejs-mediaelement"></div><div class="mejs-layers"></div><div class="mejs-controls"></div><div class="mejs-clear"></div></div></div>').addClass(a.$media[0].className).insertBefore(a.$media); +a.container.addClass((b.isAndroid?"mejs-android ":"")+(b.isiOS?"mejs-ios ":"")+(b.isiPad?"mejs-ipad ":"")+(b.isiPhone?"mejs-iphone ":"")+(a.isVideo?"mejs-video ":"mejs-audio "));if(b.isiOS){b=a.$media.clone();a.container.find(".mejs-mediaelement").append(b);a.$media.remove();a.$node=a.$media=b;a.node=a.media=b[0]}else a.container.find(".mejs-mediaelement").append(a.$media);a.controls=a.container.find(".mejs-controls");a.layers=a.container.find(".mejs-layers");b=a.isVideo?"video":"audio";e=b.substring(0, +1).toUpperCase()+b.substring(1);a.width=a.options[b+"Width"]>0||a.options[b+"Width"].toString().indexOf("%")>-1?a.options[b+"Width"]:a.media.style.width!==""&&a.media.style.width!==null?a.media.style.width:a.media.getAttribute("width")!==null?a.$media.attr("width"):a.options["default"+e+"Width"];a.height=a.options[b+"Height"]>0||a.options[b+"Height"].toString().indexOf("%")>-1?a.options[b+"Height"]:a.media.style.height!==""&&a.media.style.height!==null?a.media.style.height:a.$media[0].getAttribute("height")!== +null?a.$media.attr("height"):a.options["default"+e+"Height"];a.setPlayerSize(a.width,a.height);c.pluginWidth=a.width;c.pluginHeight=a.height}mejs.MediaElement(a.$media[0],c);typeof a.container!="undefined"&&a.container.trigger("controlsshown")},showControls:function(a){var b=this;a=typeof a=="undefined"||a;if(!b.controlsAreVisible){if(a){b.controls.css("visibility","visible").stop(true,true).fadeIn(200,function(){b.controlsAreVisible=true;b.container.trigger("controlsshown")});b.container.find(".mejs-control").css("visibility", +"visible").stop(true,true).fadeIn(200,function(){b.controlsAreVisible=true})}else{b.controls.css("visibility","visible").css("display","block");b.container.find(".mejs-control").css("visibility","visible").css("display","block");b.controlsAreVisible=true;b.container.trigger("controlsshown")}b.setControlsSize()}},hideControls:function(a){var b=this;a=typeof a=="undefined"||a;if(b.controlsAreVisible)if(a){b.controls.stop(true,true).fadeOut(50,function(){f(this).css("visibility","hidden").css("display", +"block");b.controlsAreVisible=false;b.container.trigger("controlshidden")});b.container.find(".mejs-control").stop(true,true).fadeOut(50,function(){f(this).css("visibility","hidden").css("display","block")})}else{b.controls.css("visibility","hidden").css("display","block");b.container.find(".mejs-control").css("visibility","hidden").css("display","block");b.controlsAreVisible=false;b.container.trigger("controlshidden")}},controlsTimer:null,startControlsTimer:function(a){var b=this;a=typeof a!="undefined"? +a:1500;b.killControlsTimer("start");b.controlsTimer=setTimeout(function(){b.hideControls();b.killControlsTimer("hide")},a)},killControlsTimer:function(){if(this.controlsTimer!==null){clearTimeout(this.controlsTimer);delete this.controlsTimer;this.controlsTimer=null}},controlsEnabled:true,disableControls:function(){this.killControlsTimer();this.hideControls(false);this.controlsEnabled=false},enableControls:function(){this.showControls(false);this.controlsEnabled=true},meReady:function(a,b){var c=this, +e=mejs.MediaFeatures,d=b.getAttribute("autoplay");d=!(typeof d=="undefined"||d===null||d==="false");var g;if(!c.created){c.created=true;c.media=a;c.domNode=b;if(!(e.isAndroid&&c.options.AndroidUseNativeControls)&&!(e.isiPad&&c.options.iPadUseNativeControls)&&!(e.isiPhone&&c.options.iPhoneUseNativeControls)){c.buildposter(c,c.controls,c.layers,c.media);c.buildkeyboard(c,c.controls,c.layers,c.media);c.buildoverlays(c,c.controls,c.layers,c.media);c.findTracks();for(g in c.options.features){e=c.options.features[g]; +if(c["build"+e])try{c["build"+e](c,c.controls,c.layers,c.media)}catch(k){}}c.container.trigger("controlsready");c.setPlayerSize(c.width,c.height);c.setControlsSize();if(c.isVideo){if(mejs.MediaFeatures.hasTouch)c.$media.bind("touchstart",function(){if(c.controlsAreVisible)c.hideControls(false);else c.controlsEnabled&&c.showControls(false)});else{mejs.MediaElementPlayer.prototype.clickToPlayPauseCallback=function(){console.log("media clicked",c.media,c.media.paused);if(c.options.clickToPlayPause)c.media.paused? +c.media.play():c.media.pause()};c.media.addEventListener("click",c.clickToPlayPauseCallback);c.container.bind("mouseenter mouseover",function(){if(c.controlsEnabled)if(!c.options.alwaysShowControls){c.killControlsTimer("enter");c.showControls();c.startControlsTimer(1000)}}).bind("mousemove",function(){if(c.controlsEnabled){c.controlsAreVisible||c.showControls();c.options.alwaysShowControls||c.startControlsTimer(1000)}}).bind("mouseleave",function(){c.controlsEnabled&&!c.media.paused&&!c.options.alwaysShowControls&& +c.startControlsTimer(400)})}c.options.hideVideoControlsOnLoad&&c.hideControls(false);d&&!c.options.alwaysShowControls&&c.hideControls();c.options.enableAutosize&&c.media.addEventListener("loadedmetadata",function(j){if(c.options.videoHeight<=0&&c.domNode.getAttribute("height")===null&&!isNaN(j.target.videoHeight)){c.setPlayerSize(j.target.videoWidth,j.target.videoHeight);c.setControlsSize();c.media.setVideoSize(j.target.videoWidth,j.target.videoHeight)}},false)}a.addEventListener("play",function(){for(var j in mejs.players){var m= +mejs.players[j];m.id!=c.id&&c.options.pauseOtherPlayers&&!m.paused&&!m.ended&&m.pause();m.hasFocus=false}c.hasFocus=true},false);c.media.addEventListener("ended",function(){if(c.options.autoRewind)try{c.media.setCurrentTime(0)}catch(j){}c.media.pause();c.setProgressRail&&c.setProgressRail();c.setCurrentRail&&c.setCurrentRail();if(c.options.loop)c.media.play();else!c.options.alwaysShowControls&&c.controlsEnabled&&c.showControls()},false);c.media.addEventListener("loadedmetadata",function(){c.updateDuration&& +c.updateDuration();c.updateCurrent&&c.updateCurrent();if(!c.isFullScreen){c.setPlayerSize(c.width,c.height);c.setControlsSize()}},false);setTimeout(function(){c.setPlayerSize(c.width,c.height);c.setControlsSize()},50);c.globalBind("resize",function(){c.isFullScreen||mejs.MediaFeatures.hasTrueNativeFullScreen&&document.webkitIsFullScreen||c.setPlayerSize(c.width,c.height);c.setControlsSize()});c.media.pluginType=="youtube"&&c.container.find(".mejs-overlay-play").hide()}if(d&&a.pluginType=="native"){a.load(); +a.play()}if(c.options.success)typeof c.options.success=="string"?window[c.options.success](c.media,c.domNode,c):c.options.success(c.media,c.domNode,c)}},handleError:function(a){this.controls.hide();this.options.error&&this.options.error(a)},setPlayerSize:function(a,b){if(typeof a!="undefined")this.width=a;if(typeof b!="undefined")this.height=b;if(this.height.toString().indexOf("%")>0||this.$node.css("max-width")==="100%"||this.$node[0].currentStyle&&this.$node[0].currentStyle.maxWidth==="100%"){var c= +this.isVideo?this.media.videoWidth&&this.media.videoWidth>0?this.media.videoWidth:this.options.defaultVideoWidth:this.options.defaultAudioWidth,e=this.isVideo?this.media.videoHeight&&this.media.videoHeight>0?this.media.videoHeight:this.options.defaultVideoHeight:this.options.defaultAudioHeight,d=this.container.parent().closest(":visible").width();c=this.isVideo||!this.options.autosizeProgress?parseInt(d*e/c,10):e;if(this.container.parent()[0].tagName.toLowerCase()==="body"){d=f(window).width();c= +f(window).height()}if(c!=0&&d!=0){this.container.width(d).height(c);this.$media.add(this.container.find(".mejs-shim")).width("100%").height("100%");this.isVideo&&this.media.setVideoSize&&this.media.setVideoSize(d,c);this.layers.children(".mejs-layer").width("100%").height("100%")}}else{this.container.width(this.width).height(this.height);this.layers.children(".mejs-layer").width(this.width).height(this.height)}d=this.layers.find(".mejs-overlay-play");c=d.find(".mejs-overlay-button");d.height(this.container.height()- +this.controls.height());c.css("margin-top","-"+(c.height()/2-this.controls.height()/2).toString()+"px")},setControlsSize:function(){var a=0,b=0,c=this.controls.find(".mejs-time-rail"),e=this.controls.find(".mejs-time-total");this.controls.find(".mejs-time-current");this.controls.find(".mejs-time-loaded");var d=c.siblings();if(this.options&&!this.options.autosizeProgress)b=parseInt(c.css("width"));if(b===0||!b){d.each(function(){var g=f(this);if(g.css("position")!="absolute"&&g.is(":visible"))a+=f(this).outerWidth(true)}); +b=this.controls.width()-a-(c.outerWidth(true)-c.width())}c.width(b);e.width(b-(e.outerWidth(true)-e.width()));this.setProgressRail&&this.setProgressRail();this.setCurrentRail&&this.setCurrentRail()},buildposter:function(a,b,c,e){var d=f('<div class="mejs-poster mejs-layer"></div>').appendTo(c);b=a.$media.attr("poster");if(a.options.poster!=="")b=a.options.poster;b!==""&&b!=null?this.setPoster(b):d.hide();e.addEventListener("play",function(){d.hide()},false)},setPoster:function(a){var b=this.container.find(".mejs-poster"), +c=b.find("img");if(c.length==0)c=f('<img width="100%" height="100%" />').appendTo(b);c.attr("src",a);b.css({"background-image":"url("+a+")"})},buildoverlays:function(a,b,c,e){var d=this;if(a.isVideo){var g=f('<div class="mejs-overlay mejs-layer"><div class="mejs-overlay-loading"><span></span></div></div>').hide().appendTo(c),k=f('<div class="mejs-overlay mejs-layer"><div class="mejs-overlay-error"></div></div>').hide().appendTo(c),j=f('<div class="mejs-overlay mejs-layer mejs-overlay-play"><div class="mejs-overlay-button"></div></div>').appendTo(c).click(function(){if(d.options.clickToPlayPause)e.paused? +e.play():e.pause()});e.addEventListener("play",function(){j.hide();g.hide();b.find(".mejs-time-buffering").hide();k.hide()},false);e.addEventListener("playing",function(){j.hide();g.hide();b.find(".mejs-time-buffering").hide();k.hide()},false);e.addEventListener("seeking",function(){g.show();b.find(".mejs-time-buffering").show()},false);e.addEventListener("seeked",function(){g.hide();b.find(".mejs-time-buffering").hide()},false);e.addEventListener("pause",function(){mejs.MediaFeatures.isiPhone||j.show()}, +false);e.addEventListener("waiting",function(){g.show();b.find(".mejs-time-buffering").show()},false);e.addEventListener("loadeddata",function(){g.show();b.find(".mejs-time-buffering").show()},false);e.addEventListener("canplay",function(){g.hide();b.find(".mejs-time-buffering").hide()},false);e.addEventListener("error",function(){g.hide();b.find(".mejs-time-buffering").hide();k.show();k.find("mejs-overlay-error").html("Error loading this resource")},false)}},buildkeyboard:function(a,b,c,e){this.globalBind("keydown", +function(d){if(a.hasFocus&&a.options.enableKeyboard)for(var g=0,k=a.options.keyActions.length;g<k;g++)for(var j=a.options.keyActions[g],m=0,q=j.keys.length;m<q;m++)if(d.keyCode==j.keys[m]){d.preventDefault();j.action(a,e,d.keyCode);return false}return true});this.globalBind("click",function(d){if(f(d.target).closest(".mejs-container").length==0)a.hasFocus=false})},findTracks:function(){var a=this,b=a.$media.find("track");a.tracks=[];b.each(function(c,e){e=f(e);a.tracks.push({srclang:e.attr("srclang")? +e.attr("srclang").toLowerCase():"",src:e.attr("src"),kind:e.attr("kind"),label:e.attr("label")||"",entries:[],isLoaded:false})})},changeSkin:function(a){this.container[0].className="mejs-container "+a;this.setPlayerSize(this.width,this.height);this.setControlsSize()},play:function(){this.media.play()},pause:function(){this.media.pause()},load:function(){this.media.load()},setMuted:function(a){this.media.setMuted(a)},setCurrentTime:function(a){this.media.setCurrentTime(a)},getCurrentTime:function(){return this.media.currentTime}, +setVolume:function(a){this.media.setVolume(a)},getVolume:function(){return this.media.volume},setSrc:function(a){this.media.setSrc(a)},remove:function(){var a,b;for(a in this.options.features){b=this.options.features[a];if(this["clean"+b])try{this["clean"+b](this)}catch(c){}}this.media.pluginType==="native"?this.$media.prop("controls",true):this.media.remove();this.isDynamic||this.$node.insertBefore(this.container);delete mejs.players[this.id];this.container.remove();this.globalUnbind();delete this.node.player}}; +(function(){function a(c,e){var d={d:[],w:[]};f.each((c||"").split(" "),function(g,k){var j=k+"."+e;if(j.indexOf(".")===0){d.d.push(j);d.w.push(j)}else d[b.test(k)?"w":"d"].push(j)});d.d=d.d.join(" ");d.w=d.w.join(" ");return d}var b=/^((after|before)print|(before)?unload|hashchange|message|o(ff|n)line|page(hide|show)|popstate|resize|storage)\b/;mejs.MediaElementPlayer.prototype.globalBind=function(c,e,d){c=a(c,this.id);c.d&&f(document).bind(c.d,e,d);c.w&&f(window).bind(c.w,e,d)};mejs.MediaElementPlayer.prototype.globalUnbind= +function(c,e){c=a(c,this.id);c.d&&f(document).unbind(c.d,e);c.w&&f(window).unbind(c.w,e)}})();if(typeof jQuery!="undefined")jQuery.fn.mediaelementplayer=function(a){a===false?this.each(function(){var b=jQuery(this).data("mediaelementplayer");b&&b.remove();jQuery(this).removeData("mediaelementplayer")}):this.each(function(){jQuery(this).data("mediaelementplayer",new mejs.MediaElementPlayer(this,a))});return this};f(document).ready(function(){f(".mejs-player").mediaelementplayer()});window.MediaElementPlayer= +mejs.MediaElementPlayer})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{playpauseText:mejs.i18n.t("Play/Pause")});f.extend(MediaElementPlayer.prototype,{buildplaypause:function(a,b,c,e){var d=f('<div class="mejs-button mejs-playpause-button mejs-play" ><button type="button" aria-controls="'+this.id+'" title="'+this.options.playpauseText+'" aria-label="'+this.options.playpauseText+'"></button></div>').appendTo(b).click(function(g){g.preventDefault();e.paused?e.play():e.pause();return false});e.addEventListener("play",function(){d.removeClass("mejs-play").addClass("mejs-pause")}, +false);e.addEventListener("playing",function(){d.removeClass("mejs-play").addClass("mejs-pause")},false);e.addEventListener("pause",function(){d.removeClass("mejs-pause").addClass("mejs-play")},false);e.addEventListener("paused",function(){d.removeClass("mejs-pause").addClass("mejs-play")},false)}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{stopText:"Stop"});f.extend(MediaElementPlayer.prototype,{buildstop:function(a,b,c,e){f('<div class="mejs-button mejs-stop-button mejs-stop"><button type="button" aria-controls="'+this.id+'" title="'+this.options.stopText+'" aria-label="'+this.options.stopText+'"></button></div>').appendTo(b).click(function(){e.paused||e.pause();if(e.currentTime>0){e.setCurrentTime(0);e.pause();b.find(".mejs-time-current").width("0px");b.find(".mejs-time-handle").css("left", +"0px");b.find(".mejs-time-float-current").html(mejs.Utility.secondsToTimeCode(0));b.find(".mejs-currenttime").html(mejs.Utility.secondsToTimeCode(0));c.find(".mejs-poster").show()}})}})})(mejs.$); +(function(f){f.extend(MediaElementPlayer.prototype,{buildprogress:function(a,b,c,e){f('<div class="mejs-time-rail"><span class="mejs-time-total"><span class="mejs-time-buffering"></span><span class="mejs-time-loaded"></span><span class="mejs-time-current"></span><span class="mejs-time-handle"></span><span class="mejs-time-float"><span class="mejs-time-float-current">00:00</span><span class="mejs-time-float-corner"></span></span></span></div>').appendTo(b);b.find(".mejs-time-buffering").hide();var d= +this,g=b.find(".mejs-time-total");c=b.find(".mejs-time-loaded");var k=b.find(".mejs-time-current"),j=b.find(".mejs-time-handle"),m=b.find(".mejs-time-float"),q=b.find(".mejs-time-float-current"),p=function(h){h=h.pageX;var l=g.offset(),r=g.outerWidth(true),n=0,o=n=0;if(e.duration){if(h<l.left)h=l.left;else if(h>r+l.left)h=r+l.left;o=h-l.left;n=o/r;n=n<=0.02?0:n*e.duration;t&&n!==e.currentTime&&e.setCurrentTime(n);if(!mejs.MediaFeatures.hasTouch){m.css("left",o);q.html(mejs.Utility.secondsToTimeCode(n)); +m.show()}}},t=false;g.bind("mousedown",function(h){if(h.which===1){t=true;p(h);d.globalBind("mousemove.dur",function(l){p(l)});d.globalBind("mouseup.dur",function(){t=false;m.hide();d.globalUnbind(".dur")});return false}}).bind("mouseenter",function(){d.globalBind("mousemove.dur",function(h){p(h)});mejs.MediaFeatures.hasTouch||m.show()}).bind("mouseleave",function(){if(!t){d.globalUnbind(".dur");m.hide()}});e.addEventListener("progress",function(h){a.setProgressRail(h);a.setCurrentRail(h)},false); +e.addEventListener("timeupdate",function(h){a.setProgressRail(h);a.setCurrentRail(h)},false);d.loaded=c;d.total=g;d.current=k;d.handle=j},setProgressRail:function(a){var b=a!=undefined?a.target:this.media,c=null;if(b&&b.buffered&&b.buffered.length>0&&b.buffered.end&&b.duration)c=b.buffered.end(0)/b.duration;else if(b&&b.bytesTotal!=undefined&&b.bytesTotal>0&&b.bufferedBytes!=undefined)c=b.bufferedBytes/b.bytesTotal;else if(a&&a.lengthComputable&&a.total!=0)c=a.loaded/a.total;if(c!==null){c=Math.min(1, +Math.max(0,c));this.loaded&&this.total&&this.loaded.width(this.total.width()*c)}},setCurrentRail:function(){if(this.media.currentTime!=undefined&&this.media.duration)if(this.total&&this.handle){var a=Math.round(this.total.width()*this.media.currentTime/this.media.duration),b=a-Math.round(this.handle.outerWidth(true)/2);this.current.width(a);this.handle.css("left",b)}}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{duration:-1,timeAndDurationSeparator:" <span> / </span> "});f.extend(MediaElementPlayer.prototype,{buildcurrent:function(a,b,c,e){f('<div class="mejs-time"><span class="mejs-currenttime">'+(a.options.alwaysShowHours?"00:":"")+(a.options.showTimecodeFrameCount?"00:00:00":"00:00")+"</span></div>").appendTo(b);this.currenttime=this.controls.find(".mejs-currenttime");e.addEventListener("timeupdate",function(){a.updateCurrent()},false)},buildduration:function(a, +b,c,e){if(b.children().last().find(".mejs-currenttime").length>0)f(this.options.timeAndDurationSeparator+'<span class="mejs-duration">'+(this.options.duration>0?mejs.Utility.secondsToTimeCode(this.options.duration,this.options.alwaysShowHours||this.media.duration>3600,this.options.showTimecodeFrameCount,this.options.framesPerSecond||25):(a.options.alwaysShowHours?"00:":"")+(a.options.showTimecodeFrameCount?"00:00:00":"00:00"))+"</span>").appendTo(b.find(".mejs-time"));else{b.find(".mejs-currenttime").parent().addClass("mejs-currenttime-container"); +f('<div class="mejs-time mejs-duration-container"><span class="mejs-duration">'+(this.options.duration>0?mejs.Utility.secondsToTimeCode(this.options.duration,this.options.alwaysShowHours||this.media.duration>3600,this.options.showTimecodeFrameCount,this.options.framesPerSecond||25):(a.options.alwaysShowHours?"00:":"")+(a.options.showTimecodeFrameCount?"00:00:00":"00:00"))+"</span></div>").appendTo(b)}this.durationD=this.controls.find(".mejs-duration");e.addEventListener("timeupdate",function(){a.updateDuration()}, +false)},updateCurrent:function(){if(this.currenttime)this.currenttime.html(mejs.Utility.secondsToTimeCode(this.media.currentTime,this.options.alwaysShowHours||this.media.duration>3600,this.options.showTimecodeFrameCount,this.options.framesPerSecond||25))},updateDuration:function(){this.container.toggleClass("mejs-long-video",this.media.duration>3600);if(this.durationD&&(this.options.duration>0||this.media.duration))this.durationD.html(mejs.Utility.secondsToTimeCode(this.options.duration>0?this.options.duration: +this.media.duration,this.options.alwaysShowHours,this.options.showTimecodeFrameCount,this.options.framesPerSecond||25))}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{muteText:mejs.i18n.t("Mute Toggle"),hideVolumeOnTouchDevices:true,audioVolume:"horizontal",videoVolume:"vertical"});f.extend(MediaElementPlayer.prototype,{buildvolume:function(a,b,c,e){if(!(mejs.MediaFeatures.hasTouch&&this.options.hideVolumeOnTouchDevices)){var d=this,g=d.isVideo?d.options.videoVolume:d.options.audioVolume,k=g=="horizontal"?f('<div class="mejs-button mejs-volume-button mejs-mute"><button type="button" aria-controls="'+d.id+'" title="'+d.options.muteText+ +'" aria-label="'+d.options.muteText+'"></button></div><div class="mejs-horizontal-volume-slider"><div class="mejs-horizontal-volume-total"></div><div class="mejs-horizontal-volume-current"></div><div class="mejs-horizontal-volume-handle"></div></div>').appendTo(b):f('<div class="mejs-button mejs-volume-button mejs-mute"><button type="button" aria-controls="'+d.id+'" title="'+d.options.muteText+'" aria-label="'+d.options.muteText+'"></button><div class="mejs-volume-slider"><div class="mejs-volume-total"></div><div class="mejs-volume-current"></div><div class="mejs-volume-handle"></div></div></div>').appendTo(b), +j=d.container.find(".mejs-volume-slider, .mejs-horizontal-volume-slider"),m=d.container.find(".mejs-volume-total, .mejs-horizontal-volume-total"),q=d.container.find(".mejs-volume-current, .mejs-horizontal-volume-current"),p=d.container.find(".mejs-volume-handle, .mejs-horizontal-volume-handle"),t=function(n,o){if(!j.is(":visible")&&typeof o=="undefined"){j.show();t(n,true);j.hide()}else{n=Math.max(0,n);n=Math.min(n,1);n==0?k.removeClass("mejs-mute").addClass("mejs-unmute"):k.removeClass("mejs-unmute").addClass("mejs-mute"); +if(g=="vertical"){var s=m.height(),u=m.position(),v=s-s*n;p.css("top",Math.round(u.top+v-p.height()/2));q.height(s-v);q.css("top",u.top+v)}else{s=m.width();u=m.position();s=s*n;p.css("left",Math.round(u.left+s-p.width()/2));q.width(Math.round(s))}}},h=function(n){var o=null,s=m.offset();if(g=="vertical"){o=m.height();parseInt(m.css("top").replace(/px/,""),10);o=(o-(n.pageY-s.top))/o;if(s.top==0||s.left==0)return}else{o=m.width();o=(n.pageX-s.left)/o}o=Math.max(0,o);o=Math.min(o,1);t(o);o==0?e.setMuted(true): +e.setMuted(false);e.setVolume(o)},l=false,r=false;k.hover(function(){j.show();r=true},function(){r=false;!l&&g=="vertical"&&j.hide()});j.bind("mouseover",function(){r=true}).bind("mousedown",function(n){h(n);d.globalBind("mousemove.vol",function(o){h(o)});d.globalBind("mouseup.vol",function(){l=false;d.globalUnbind(".vol");!r&&g=="vertical"&&j.hide()});l=true;return false});k.find("button").click(function(){e.setMuted(!e.muted)});e.addEventListener("volumechange",function(){if(!l)if(e.muted){t(0); +k.removeClass("mejs-mute").addClass("mejs-unmute")}else{t(e.volume);k.removeClass("mejs-unmute").addClass("mejs-mute")}},false);if(d.container.is(":visible")){t(a.options.startVolume);a.options.startVolume===0&&e.setMuted(true);e.pluginType==="native"&&e.setVolume(a.options.startVolume)}}}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{usePluginFullScreen:true,newWindowCallback:function(){return""},fullscreenText:mejs.i18n.t("Fullscreen")});f.extend(MediaElementPlayer.prototype,{isFullScreen:false,isNativeFullScreen:false,docStyleOverflow:null,isInIframe:false,buildfullscreen:function(a,b,c,e){if(a.isVideo){a.isInIframe=window.location!=window.parent.location;if(mejs.MediaFeatures.hasTrueNativeFullScreen){c=function(){if(mejs.MediaFeatures.isFullScreen()){a.isNativeFullScreen=true;a.setControlsSize()}else{a.isNativeFullScreen= +false;a.exitFullScreen()}};mejs.MediaFeatures.hasMozNativeFullScreen?a.globalBind(mejs.MediaFeatures.fullScreenEventName,c):a.container.bind(mejs.MediaFeatures.fullScreenEventName,c)}var d=this,g=f('<div class="mejs-button mejs-fullscreen-button"><button type="button" aria-controls="'+d.id+'" title="'+d.options.fullscreenText+'" aria-label="'+d.options.fullscreenText+'"></button></div>').appendTo(b);if(d.media.pluginType==="native"||!d.options.usePluginFullScreen&&!mejs.MediaFeatures.isFirefox)g.click(function(){mejs.MediaFeatures.hasTrueNativeFullScreen&& +mejs.MediaFeatures.isFullScreen()||a.isFullScreen?a.exitFullScreen():a.enterFullScreen()});else{var k=null;if(function(){var h=document.createElement("x"),l=document.documentElement,r=window.getComputedStyle;if(!("pointerEvents"in h.style))return false;h.style.pointerEvents="auto";h.style.pointerEvents="x";l.appendChild(h);r=r&&r(h,"").pointerEvents==="auto";l.removeChild(h);return!!r}()&&!mejs.MediaFeatures.isOpera){var j=false,m=function(){if(j){for(var h in q)q[h].hide();g.css("pointer-events", +"");d.controls.css("pointer-events","");d.media.removeEventListener("click",d.clickToPlayPauseCallback);j=false}},q={};b=["top","left","right","bottom"];var p,t=function(){var h=g.offset().left-d.container.offset().left,l=g.offset().top-d.container.offset().top,r=g.outerWidth(true),n=g.outerHeight(true),o=d.container.width(),s=d.container.height();for(p in q)q[p].css({position:"absolute",top:0,left:0});q.top.width(o).height(l);q.left.width(h).height(n).css({top:l});q.right.width(o-h-r).height(n).css({top:l, +left:h+r});q.bottom.width(o).height(s-n-l).css({top:l+n})};d.globalBind("resize",function(){t()});p=0;for(c=b.length;p<c;p++)q[b[p]]=f('<div class="mejs-fullscreen-hover" />').appendTo(d.container).mouseover(m).hide();g.on("mouseover",function(){if(!d.isFullScreen){var h=g.offset(),l=a.container.offset();e.positionFullscreenButton(h.left-l.left,h.top-l.top,false);g.css("pointer-events","none");d.controls.css("pointer-events","none");d.media.addEventListener("click",d.clickToPlayPauseCallback);for(p in q)q[p].show(); +t();j=true}});e.addEventListener("fullscreenchange",function(){d.isFullScreen=!d.isFullScreen;d.isFullScreen?d.media.removeEventListener("click",d.clickToPlayPauseCallback):d.media.addEventListener("click",d.clickToPlayPauseCallback);m()});d.globalBind("mousemove",function(h){if(j){var l=g.offset();if(h.pageY<l.top||h.pageY>l.top+g.outerHeight(true)||h.pageX<l.left||h.pageX>l.left+g.outerWidth(true)){g.css("pointer-events","");d.controls.css("pointer-events","");j=false}}})}else g.on("mouseover", +function(){if(k!==null){clearTimeout(k);delete k}var h=g.offset(),l=a.container.offset();e.positionFullscreenButton(h.left-l.left,h.top-l.top,true)}).on("mouseout",function(){if(k!==null){clearTimeout(k);delete k}k=setTimeout(function(){e.hideFullscreenButton()},1500)})}a.fullscreenBtn=g;d.globalBind("keydown",function(h){if((mejs.MediaFeatures.hasTrueNativeFullScreen&&mejs.MediaFeatures.isFullScreen()||d.isFullScreen)&&h.keyCode==27)a.exitFullScreen()})}},cleanfullscreen:function(a){a.exitFullScreen()}, +containerSizeTimeout:null,enterFullScreen:function(){var a=this;if(!(a.media.pluginType!=="native"&&(mejs.MediaFeatures.isFirefox||a.options.usePluginFullScreen))){docStyleOverflow=document.documentElement.style.overflow;document.documentElement.style.overflow="hidden";normalHeight=a.container.height();normalWidth=a.container.width();if(a.media.pluginType==="native")if(mejs.MediaFeatures.hasTrueNativeFullScreen){mejs.MediaFeatures.requestFullScreen(a.container[0]);a.isInIframe&&setTimeout(function c(){if(a.isNativeFullScreen)f(window).width()!== +screen.width?a.exitFullScreen():setTimeout(c,500)},500)}else if(mejs.MediaFeatures.hasSemiNativeFullScreen){a.media.webkitEnterFullscreen();return}if(a.isInIframe){var b=a.options.newWindowCallback(this);if(b!=="")if(mejs.MediaFeatures.hasTrueNativeFullScreen)setTimeout(function(){if(!a.isNativeFullScreen){a.pause();window.open(b,a.id,"top=0,left=0,width="+screen.availWidth+",height="+screen.availHeight+",resizable=yes,scrollbars=no,status=no,toolbar=no")}},250);else{a.pause();window.open(b,a.id, +"top=0,left=0,width="+screen.availWidth+",height="+screen.availHeight+",resizable=yes,scrollbars=no,status=no,toolbar=no");return}}a.container.addClass("mejs-container-fullscreen").width("100%").height("100%");a.containerSizeTimeout=setTimeout(function(){a.container.css({width:"100%",height:"100%"});a.setControlsSize()},500);if(a.media.pluginType==="native")a.$media.width("100%").height("100%");else{a.container.find(".mejs-shim").width("100%").height("100%");a.media.setVideoSize(f(window).width(), +f(window).height())}a.layers.children("div").width("100%").height("100%");a.fullscreenBtn&&a.fullscreenBtn.removeClass("mejs-fullscreen").addClass("mejs-unfullscreen");a.setControlsSize();a.isFullScreen=true}},exitFullScreen:function(){clearTimeout(this.containerSizeTimeout);if(this.media.pluginType!=="native"&&mejs.MediaFeatures.isFirefox)this.media.setFullscreen(false);else{if(mejs.MediaFeatures.hasTrueNativeFullScreen&&(mejs.MediaFeatures.isFullScreen()||this.isFullScreen))mejs.MediaFeatures.cancelFullScreen(); +document.documentElement.style.overflow=docStyleOverflow;this.container.removeClass("mejs-container-fullscreen").width(normalWidth).height(normalHeight);if(this.media.pluginType==="native")this.$media.width(normalWidth).height(normalHeight);else{this.container.find(".mejs-shim").width(normalWidth).height(normalHeight);this.media.setVideoSize(normalWidth,normalHeight)}this.layers.children("div").width(normalWidth).height(normalHeight);this.fullscreenBtn.removeClass("mejs-unfullscreen").addClass("mejs-fullscreen"); +this.setControlsSize();this.isFullScreen=false}}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{startLanguage:"",tracksText:mejs.i18n.t("Captions/Subtitles"),hideCaptionsButtonWhenEmpty:true,toggleCaptionsButtonWhenOnlyOne:false,slidesSelector:""});f.extend(MediaElementPlayer.prototype,{hasChapters:false,buildtracks:function(a,b,c,e){if(a.tracks.length!=0){a.chapters=f('<div class="mejs-chapters mejs-layer"></div>').prependTo(c).hide();a.captions=f('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position mejs-captions-position-hover"><span class="mejs-captions-text"></span></div></div>').prependTo(c).hide();a.captionsText= +a.captions.find(".mejs-captions-text");a.captionsButton=f('<div class="mejs-button mejs-captions-button"><button type="button" aria-controls="'+this.id+'" title="'+this.options.tracksText+'" aria-label="'+this.options.tracksText+'"></button><div class="mejs-captions-selector"><ul><li><input type="radio" name="'+a.id+'_captions" id="'+a.id+'_captions_none" value="none" checked="checked" /><label for="'+a.id+'_captions_none">'+mejs.i18n.t("None")+"</label></li></ul></div></div>").appendTo(b);for(b= +c=0;b<a.tracks.length;b++)a.tracks[b].kind=="subtitles"&&c++;this.options.toggleCaptionsButtonWhenOnlyOne&&c==1?a.captionsButton.on("click",function(){a.setTrack(a.selectedTrack==null?a.tracks[0].srclang:"none")}):a.captionsButton.hover(function(){f(this).find(".mejs-captions-selector").css("visibility","visible")},function(){f(this).find(".mejs-captions-selector").css("visibility","hidden")}).on("click","input[type=radio]",function(){lang=this.value;a.setTrack(lang)});a.options.alwaysShowControls? +a.container.find(".mejs-captions-position").addClass("mejs-captions-position-hover"):a.container.bind("controlsshown",function(){a.container.find(".mejs-captions-position").addClass("mejs-captions-position-hover")}).bind("controlshidden",function(){e.paused||a.container.find(".mejs-captions-position").removeClass("mejs-captions-position-hover")});a.trackToLoad=-1;a.selectedTrack=null;a.isLoadingTrack=false;for(b=0;b<a.tracks.length;b++)a.tracks[b].kind=="subtitles"&&a.addTrackButton(a.tracks[b].srclang, +a.tracks[b].label);a.loadNextTrack();e.addEventListener("timeupdate",function(){a.displayCaptions()},false);if(a.options.slidesSelector!=""){a.slidesContainer=f(a.options.slidesSelector);e.addEventListener("timeupdate",function(){a.displaySlides()},false)}e.addEventListener("loadedmetadata",function(){a.displayChapters()},false);a.container.hover(function(){if(a.hasChapters){a.chapters.css("visibility","visible");a.chapters.fadeIn(200).height(a.chapters.find(".mejs-chapter").outerHeight())}},function(){a.hasChapters&& +!e.paused&&a.chapters.fadeOut(200,function(){f(this).css("visibility","hidden");f(this).css("display","block")})});a.node.getAttribute("autoplay")!==null&&a.chapters.css("visibility","hidden")}},setTrack:function(a){var b;if(a=="none"){this.selectedTrack=null;this.captionsButton.removeClass("mejs-captions-enabled")}else for(b=0;b<this.tracks.length;b++)if(this.tracks[b].srclang==a){this.selectedTrack==null&&this.captionsButton.addClass("mejs-captions-enabled");this.selectedTrack=this.tracks[b];this.captions.attr("lang", +this.selectedTrack.srclang);this.displayCaptions();break}},loadNextTrack:function(){this.trackToLoad++;if(this.trackToLoad<this.tracks.length){this.isLoadingTrack=true;this.loadTrack(this.trackToLoad)}else{this.isLoadingTrack=false;this.checkForTracks()}},loadTrack:function(a){var b=this,c=b.tracks[a];f.ajax({url:c.src,dataType:"text",success:function(e){c.entries=typeof e=="string"&&/<tt\s+xml/ig.exec(e)?mejs.TrackFormatParser.dfxp.parse(e):mejs.TrackFormatParser.webvvt.parse(e);c.isLoaded=true; +b.enableTrackButton(c.srclang,c.label);b.loadNextTrack();c.kind=="chapters"&&b.media.addEventListener("play",function(){b.media.duration>0&&b.displayChapters(c)},false);c.kind=="slides"&&b.setupSlides(c)},error:function(){b.loadNextTrack()}})},enableTrackButton:function(a,b){if(b==="")b=mejs.language.codes[a]||a;this.captionsButton.find("input[value="+a+"]").prop("disabled",false).siblings("label").html(b);this.options.startLanguage==a&&f("#"+this.id+"_captions_"+a).click();this.adjustLanguageBox()}, +addTrackButton:function(a,b){if(b==="")b=mejs.language.codes[a]||a;this.captionsButton.find("ul").append(f('<li><input type="radio" name="'+this.id+'_captions" id="'+this.id+"_captions_"+a+'" value="'+a+'" disabled="disabled" /><label for="'+this.id+"_captions_"+a+'">'+b+" (loading)</label></li>"));this.adjustLanguageBox();this.container.find(".mejs-captions-translations option[value="+a+"]").remove()},adjustLanguageBox:function(){this.captionsButton.find(".mejs-captions-selector").height(this.captionsButton.find(".mejs-captions-selector ul").outerHeight(true)+ +this.captionsButton.find(".mejs-captions-translations").outerHeight(true))},checkForTracks:function(){var a=false;if(this.options.hideCaptionsButtonWhenEmpty){for(i=0;i<this.tracks.length;i++)if(this.tracks[i].kind=="subtitles"){a=true;break}if(!a){this.captionsButton.hide();this.setControlsSize()}}},displayCaptions:function(){if(typeof this.tracks!="undefined"){var a,b=this.selectedTrack;if(b!=null&&b.isLoaded)for(a=0;a<b.entries.times.length;a++)if(this.media.currentTime>=b.entries.times[a].start&& +this.media.currentTime<=b.entries.times[a].stop){this.captionsText.html(b.entries.text[a]);this.captions.show().height(0);return}this.captions.hide()}},setupSlides:function(a){this.slides=a;this.slides.entries.imgs=[this.slides.entries.text.length];this.showSlide(0)},showSlide:function(a){if(!(typeof this.tracks=="undefined"||typeof this.slidesContainer=="undefined")){var b=this,c=b.slides.entries.text[a],e=b.slides.entries.imgs[a];if(typeof e=="undefined"||typeof e.fadeIn=="undefined")b.slides.entries.imgs[a]= +e=f('<img src="'+c+'">').on("load",function(){e.appendTo(b.slidesContainer).hide().fadeIn().siblings(":visible").fadeOut()});else if(!e.is(":visible")&&!e.is(":animated")){console.log("showing existing slide");e.fadeIn().siblings(":visible").fadeOut()}}},displaySlides:function(){if(typeof this.slides!="undefined"){var a=this.slides,b;for(b=0;b<a.entries.times.length;b++)if(this.media.currentTime>=a.entries.times[b].start&&this.media.currentTime<=a.entries.times[b].stop){this.showSlide(b);break}}}, +displayChapters:function(){var a;for(a=0;a<this.tracks.length;a++)if(this.tracks[a].kind=="chapters"&&this.tracks[a].isLoaded){this.drawChapters(this.tracks[a]);this.hasChapters=true;break}},drawChapters:function(a){var b=this,c,e,d=e=0;b.chapters.empty();for(c=0;c<a.entries.times.length;c++){e=a.entries.times[c].stop-a.entries.times[c].start;e=Math.floor(e/b.media.duration*100);if(e+d>100||c==a.entries.times.length-1&&e+d<100)e=100-d;b.chapters.append(f('<div class="mejs-chapter" rel="'+a.entries.times[c].start+ +'" style="left: '+d.toString()+"%;width: "+e.toString()+'%;"><div class="mejs-chapter-block'+(c==a.entries.times.length-1?" mejs-chapter-block-last":"")+'"><span class="ch-title">'+a.entries.text[c]+'</span><span class="ch-time">'+mejs.Utility.secondsToTimeCode(a.entries.times[c].start)+"–"+mejs.Utility.secondsToTimeCode(a.entries.times[c].stop)+"</span></div></div>"));d+=e}b.chapters.find("div.mejs-chapter").click(function(){b.media.setCurrentTime(parseFloat(f(this).attr("rel")));b.media.paused&& +b.media.play()});b.chapters.show()}});mejs.language={codes:{af:"Afrikaans",sq:"Albanian",ar:"Arabic",be:"Belarusian",bg:"Bulgarian",ca:"Catalan",zh:"Chinese","zh-cn":"Chinese Simplified","zh-tw":"Chinese Traditional",hr:"Croatian",cs:"Czech",da:"Danish",nl:"Dutch",en:"English",et:"Estonian",tl:"Filipino",fi:"Finnish",fr:"French",gl:"Galician",de:"German",el:"Greek",ht:"Haitian Creole",iw:"Hebrew",hi:"Hindi",hu:"Hungarian",is:"Icelandic",id:"Indonesian",ga:"Irish",it:"Italian",ja:"Japanese",ko:"Korean", +lv:"Latvian",lt:"Lithuanian",mk:"Macedonian",ms:"Malay",mt:"Maltese",no:"Norwegian",fa:"Persian",pl:"Polish",pt:"Portuguese",ro:"Romanian",ru:"Russian",sr:"Serbian",sk:"Slovak",sl:"Slovenian",es:"Spanish",sw:"Swahili",sv:"Swedish",tl:"Tagalog",th:"Thai",tr:"Turkish",uk:"Ukrainian",vi:"Vietnamese",cy:"Welsh",yi:"Yiddish"}};mejs.TrackFormatParser={webvvt:{pattern_identifier:/^([a-zA-z]+-)?[0-9]+$/,pattern_timecode:/^([0-9]{2}:[0-9]{2}:[0-9]{2}([,.][0-9]{1,3})?) --\> ([0-9]{2}:[0-9]{2}:[0-9]{2}([,.][0-9]{3})?)(.*)$/, +parse:function(a){var b=0;a=mejs.TrackFormatParser.split2(a,/\r?\n/);for(var c={text:[],times:[]},e,d;b<a.length;b++)if(this.pattern_identifier.exec(a[b])){b++;if((e=this.pattern_timecode.exec(a[b]))&&b<a.length){b++;d=a[b];for(b++;a[b]!==""&&b<a.length;){d=d+"\n"+a[b];b++}d=f.trim(d).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig,"<a href='$1' target='_blank'>$1</a>");c.text.push(d);c.times.push({start:mejs.Utility.convertSMPTEtoSeconds(e[1])==0?0.2:mejs.Utility.convertSMPTEtoSeconds(e[1]), +stop:mejs.Utility.convertSMPTEtoSeconds(e[3]),settings:e[5]})}}return c}},dfxp:{parse:function(a){a=f(a).filter("tt");var b=0;b=a.children("div").eq(0);var c=b.find("p");b=a.find("#"+b.attr("style"));var e,d;a={text:[],times:[]};if(b.length){d=b.removeAttr("id").get(0).attributes;if(d.length){e={};for(b=0;b<d.length;b++)e[d[b].name.split(":")[1]]=d[b].value}}for(b=0;b<c.length;b++){var g;d={start:null,stop:null,style:null};if(c.eq(b).attr("begin"))d.start=mejs.Utility.convertSMPTEtoSeconds(c.eq(b).attr("begin")); +if(!d.start&&c.eq(b-1).attr("end"))d.start=mejs.Utility.convertSMPTEtoSeconds(c.eq(b-1).attr("end"));if(c.eq(b).attr("end"))d.stop=mejs.Utility.convertSMPTEtoSeconds(c.eq(b).attr("end"));if(!d.stop&&c.eq(b+1).attr("begin"))d.stop=mejs.Utility.convertSMPTEtoSeconds(c.eq(b+1).attr("begin"));if(e){g="";for(var k in e)g+=k+":"+e[k]+";"}if(g)d.style=g;if(d.start==0)d.start=0.2;a.times.push(d);d=f.trim(c.eq(b).html()).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, +"<a href='$1' target='_blank'>$1</a>");a.text.push(d);if(a.times.start==0)a.times.start=2}return a}},split2:function(a,b){return a.split(b)}};if("x\n\ny".split(/\n/gi).length!=3)mejs.TrackFormatParser.split2=function(a,b){var c=[],e="",d;for(d=0;d<a.length;d++){e+=a.substring(d,d+1);if(b.test(e)){c.push(e.replace(b,""));e=""}}c.push(e);return c}})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{contextMenuItems:[{render:function(a){if(typeof a.enterFullScreen=="undefined")return null;return a.isFullScreen?mejs.i18n.t("Turn off Fullscreen"):mejs.i18n.t("Go Fullscreen")},click:function(a){a.isFullScreen?a.exitFullScreen():a.enterFullScreen()}},{render:function(a){return a.media.muted?mejs.i18n.t("Unmute"):mejs.i18n.t("Mute")},click:function(a){a.media.muted?a.setMuted(false):a.setMuted(true)}},{isSeparator:true},{render:function(){return mejs.i18n.t("Download Video")}, +click:function(a){window.location.href=a.media.currentSrc}}]});f.extend(MediaElementPlayer.prototype,{buildcontextmenu:function(a){a.contextMenu=f('<div class="mejs-contextmenu"></div>').appendTo(f("body")).hide();a.container.bind("contextmenu",function(b){if(a.isContextMenuEnabled){b.preventDefault();a.renderContextMenu(b.clientX-1,b.clientY-1);return false}});a.container.bind("click",function(){a.contextMenu.hide()});a.contextMenu.bind("mouseleave",function(){a.startContextMenuTimer()})},cleancontextmenu:function(a){a.contextMenu.remove()}, +isContextMenuEnabled:true,enableContextMenu:function(){this.isContextMenuEnabled=true},disableContextMenu:function(){this.isContextMenuEnabled=false},contextMenuTimeout:null,startContextMenuTimer:function(){var a=this;a.killContextMenuTimer();a.contextMenuTimer=setTimeout(function(){a.hideContextMenu();a.killContextMenuTimer()},750)},killContextMenuTimer:function(){var a=this.contextMenuTimer;if(a!=null){clearTimeout(a);delete a}},hideContextMenu:function(){this.contextMenu.hide()},renderContextMenu:function(a, +b){for(var c=this,e="",d=c.options.contextMenuItems,g=0,k=d.length;g<k;g++)if(d[g].isSeparator)e+='<div class="mejs-contextmenu-separator"></div>';else{var j=d[g].render(c);if(j!=null)e+='<div class="mejs-contextmenu-item" data-itemindex="'+g+'" id="element-'+Math.random()*1E6+'">'+j+"</div>"}c.contextMenu.empty().append(f(e)).css({top:b,left:a}).show();c.contextMenu.find(".mejs-contextmenu-item").each(function(){var m=f(this),q=parseInt(m.data("itemindex"),10),p=c.options.contextMenuItems[q];typeof p.show!= +"undefined"&&p.show(m,c);m.click(function(){typeof p.click!="undefined"&&p.click(c);c.contextMenu.hide()})});setTimeout(function(){c.killControlsTimer("rev3")},100)}})})(mejs.$); +(function(f){f.extend(mejs.MepDefaults,{postrollCloseText:mejs.i18n.t("Close")});f.extend(MediaElementPlayer.prototype,{buildpostroll:function(a,b,c){var e=this.container.find('link[rel="postroll"]').attr("href");if(typeof e!=="undefined"){a.postroll=f('<div class="mejs-postroll-layer mejs-layer"><a class="mejs-postroll-close" onclick="$(this).parent().hide();return false;">'+this.options.postrollCloseText+'</a><div class="mejs-postroll-layer-content"></div></div>').prependTo(c).hide();this.media.addEventListener("ended", +function(){f.ajax({dataType:"html",url:e,success:function(d){c.find(".mejs-postroll-layer-content").html(d)}});a.postroll.show()},false)}}})})(mejs.$); + diff --git a/src/main/javascript/script.jsm b/src/main/javascript/script.jsm @@ -0,0 +1,22 @@ +# jquery. +libs/jquery-1.8.3.min.js + +# eltimn +libs/jquery.bsAlerts.min.js +libs/jquery.bsFormAlerts.min.js +libs/liftAjax.js + +# bootstrap +libs/bootstrap-2.2.2.min.js + +# jquery plugins +libs/jquery.ui.widget.js +libs/jquery.iframe-transport.js +libs/jquery.fileupload.js +libs/jquery.timeago.js + +libs/mediaelement-and-player.min.js + +# customs +customs/fileupload.js +customs/other.js diff --git a/src/main/less/bootstrap/accordion.less b/src/main/less/bootstrap/accordion.less @@ -0,0 +1,34 @@ +// +// Accordion +// -------------------------------------------------- + + +// Parent container +.accordion { + margin-bottom: @baseLineHeight; +} + +// Group == heading + body +.accordion-group { + margin-bottom: 2px; + border: 1px solid #e5e5e5; + .border-radius(@baseBorderRadius); +} +.accordion-heading { + border-bottom: 0; +} +.accordion-heading .accordion-toggle { + display: block; + padding: 8px 15px; +} + +// General toggle styles +.accordion-toggle { + cursor: pointer; +} + +// Inner needs the styles because you can't animate properly with any styles on the element +.accordion-inner { + padding: 9px 15px; + border-top: 1px solid #e5e5e5; +} diff --git a/src/main/less/bootstrap/alerts.less b/src/main/less/bootstrap/alerts.less @@ -0,0 +1,79 @@ +// +// Alerts +// -------------------------------------------------- + + +// Base styles +// ------------------------- + +.alert { + padding: 8px 35px 8px 14px; + margin-bottom: @baseLineHeight; + text-shadow: 0 1px 0 rgba(255,255,255,.5); + background-color: @warningBackground; + border: 1px solid @warningBorder; + .border-radius(@baseBorderRadius); +} +.alert, +.alert h4 { + // Specified for the h4 to prevent conflicts of changing @headingsColor + color: @warningText; +} +.alert h4 { + margin: 0; +} + +// Adjust close link position +.alert .close { + position: relative; + top: -2px; + right: -21px; + line-height: @baseLineHeight; +} + + +// Alternate styles +// ------------------------- + +.alert-success { + background-color: @successBackground; + border-color: @successBorder; + color: @successText; +} +.alert-success h4 { + color: @successText; +} +.alert-danger, +.alert-error { + background-color: @errorBackground; + border-color: @errorBorder; + color: @errorText; +} +.alert-danger h4, +.alert-error h4 { + color: @errorText; +} +.alert-info { + background-color: @infoBackground; + border-color: @infoBorder; + color: @infoText; +} +.alert-info h4 { + color: @infoText; +} + + +// Block alerts +// ------------------------- + +.alert-block { + padding-top: 14px; + padding-bottom: 14px; +} +.alert-block > p, +.alert-block > ul { + margin-bottom: 0; +} +.alert-block p + p { + margin-top: 5px; +} diff --git a/src/main/less/bootstrap/bootstrap.less b/src/main/less/bootstrap/bootstrap.less @@ -0,0 +1,63 @@ +/*! + * Bootstrap v2.2.2 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + +// CSS Reset +@import "reset.less"; + +// Core variables and mixins +@import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "mixins.less"; + +// Grid system and page structure +@import "scaffolding.less"; +@import "grid.less"; +@import "layouts.less"; + +// Base CSS +@import "type.less"; +@import "code.less"; +@import "forms.less"; +@import "tables.less"; + +// Components: common +@import "sprites.less"; +@import "dropdowns.less"; +@import "wells.less"; +@import "component-animations.less"; +@import "close.less"; + +// Components: Buttons & Alerts +@import "buttons.less"; +@import "button-groups.less"; +@import "alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less + +// Components: Nav +@import "navs.less"; +@import "navbar.less"; +@import "breadcrumbs.less"; +@import "pagination.less"; +@import "pager.less"; + +// Components: Popovers +@import "modals.less"; +@import "tooltip.less"; +@import "popovers.less"; + +// Components: Misc +@import "thumbnails.less"; +@import "media.less"; +@import "labels-badges.less"; +@import "progress-bars.less"; +@import "accordion.less"; +@import "carousel.less"; +@import "hero-unit.less"; + +// Utility classes +@import "utilities.less"; // Has to be last to override when necessary diff --git a/src/main/less/bootstrap/breadcrumbs.less b/src/main/less/bootstrap/breadcrumbs.less @@ -0,0 +1,24 @@ +// +// Breadcrumbs +// -------------------------------------------------- + + +.breadcrumb { + padding: 8px 15px; + margin: 0 0 @baseLineHeight; + list-style: none; + background-color: #f5f5f5; + .border-radius(@baseBorderRadius); + > li { + display: inline-block; + .ie7-inline-block(); + text-shadow: 0 1px 0 @white; + > .divider { + padding: 0 5px; + color: #ccc; + } + } + > .active { + color: @grayLight; + } +} diff --git a/src/main/less/bootstrap/button-groups.less b/src/main/less/bootstrap/button-groups.less @@ -0,0 +1,227 @@ +// +// Button groups +// -------------------------------------------------- + + +// Make the div behave like a button +.btn-group { + position: relative; + display: inline-block; + .ie7-inline-block(); + font-size: 0; // remove as part 1 of font-size inline-block hack + vertical-align: middle; // match .btn alignment given font-size hack above + white-space: nowrap; // prevent buttons from wrapping when in tight spaces (e.g., the table on the tests page) + .ie7-restore-left-whitespace(); +} + +// Space out series of button groups +.btn-group + .btn-group { + margin-left: 5px; +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + font-size: 0; // Hack to remove whitespace that results from using inline-block + margin-top: @baseLineHeight / 2; + margin-bottom: @baseLineHeight / 2; + > .btn + .btn, + > .btn-group + .btn, + > .btn + .btn-group { + margin-left: 5px; + } +} + +// Float them, remove border radius, then re-add to first and last elements +.btn-group > .btn { + position: relative; + .border-radius(0); +} +.btn-group > .btn + .btn { + margin-left: -1px; +} +.btn-group > .btn, +.btn-group > .dropdown-menu, +.btn-group > .popover { + font-size: @baseFontSize; // redeclare as part 2 of font-size inline-block hack +} + +// Reset fonts for other sizes +.btn-group > .btn-mini { + font-size: @fontSizeMini; +} +.btn-group > .btn-small { + font-size: @fontSizeSmall; +} +.btn-group > .btn-large { + font-size: @fontSizeLarge; +} + +// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match +.btn-group > .btn:first-child { + margin-left: 0; + .border-top-left-radius(@baseBorderRadius); + .border-bottom-left-radius(@baseBorderRadius); +} +// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it +.btn-group > .btn:last-child, +.btn-group > .dropdown-toggle { + .border-top-right-radius(@baseBorderRadius); + .border-bottom-right-radius(@baseBorderRadius); +} +// Reset corners for large buttons +.btn-group > .btn.large:first-child { + margin-left: 0; + .border-top-left-radius(@borderRadiusLarge); + .border-bottom-left-radius(@borderRadiusLarge); +} +.btn-group > .btn.large:last-child, +.btn-group > .large.dropdown-toggle { + .border-top-right-radius(@borderRadiusLarge); + .border-bottom-right-radius(@borderRadiusLarge); +} + +// On hover/focus/active, bring the proper btn to front +.btn-group > .btn:hover, +.btn-group > .btn:focus, +.btn-group > .btn:active, +.btn-group > .btn.active { + z-index: 2; +} + +// On active and open, don't show outline +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + + + +// Split button dropdowns +// ---------------------- + +// Give the line between buttons some depth +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; + .box-shadow(~"inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); + *padding-top: 5px; + *padding-bottom: 5px; +} +.btn-group > .btn-mini + .dropdown-toggle { + padding-left: 5px; + padding-right: 5px; + *padding-top: 2px; + *padding-bottom: 2px; +} +.btn-group > .btn-small + .dropdown-toggle { + *padding-top: 5px; + *padding-bottom: 4px; +} +.btn-group > .btn-large + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; + *padding-top: 7px; + *padding-bottom: 7px; +} + +.btn-group.open { + + // The clickable button for toggling the menu + // Remove the gradient and set the same inset shadow as the :active state + .dropdown-toggle { + background-image: none; + .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); + } + + // Keep the hover's background when dropdown is open + .btn.dropdown-toggle { + background-color: @btnBackgroundHighlight; + } + .btn-primary.dropdown-toggle { + background-color: @btnPrimaryBackgroundHighlight; + } + .btn-warning.dropdown-toggle { + background-color: @btnWarningBackgroundHighlight; + } + .btn-danger.dropdown-toggle { + background-color: @btnDangerBackgroundHighlight; + } + .btn-success.dropdown-toggle { + background-color: @btnSuccessBackgroundHighlight; + } + .btn-info.dropdown-toggle { + background-color: @btnInfoBackgroundHighlight; + } + .btn-inverse.dropdown-toggle { + background-color: @btnInverseBackgroundHighlight; + } +} + + +// Reposition the caret +.btn .caret { + margin-top: 8px; + margin-left: 0; +} +// Carets in other button sizes +.btn-mini .caret, +.btn-small .caret, +.btn-large .caret { + margin-top: 6px; +} +.btn-large .caret { + border-left-width: 5px; + border-right-width: 5px; + border-top-width: 5px; +} +// Upside down carets for .dropup +.dropup .btn-large .caret { + border-bottom-width: 5px; +} + + + +// Account for other colors +.btn-primary, +.btn-warning, +.btn-danger, +.btn-info, +.btn-success, +.btn-inverse { + .caret { + border-top-color: @white; + border-bottom-color: @white; + } +} + + + +// Vertical button groups +// ---------------------- + +.btn-group-vertical { + display: inline-block; // makes buttons only take up the width they need + .ie7-inline-block(); +} +.btn-group-vertical > .btn { + display: block; + float: none; + max-width: 100%; + .border-radius(0); +} +.btn-group-vertical > .btn + .btn { + margin-left: 0; + margin-top: -1px; +} +.btn-group-vertical > .btn:first-child { + .border-radius(@baseBorderRadius @baseBorderRadius 0 0); +} +.btn-group-vertical > .btn:last-child { + .border-radius(0 0 @baseBorderRadius @baseBorderRadius); +} +.btn-group-vertical > .btn-large:first-child { + .border-radius(@borderRadiusLarge @borderRadiusLarge 0 0); +} +.btn-group-vertical > .btn-large:last-child { + .border-radius(0 0 @borderRadiusLarge @borderRadiusLarge); +} diff --git a/src/main/less/bootstrap/buttons.less b/src/main/less/bootstrap/buttons.less @@ -0,0 +1,230 @@ +// +// Buttons +// -------------------------------------------------- + + +// Base styles +// -------------------------------------------------- + +// Core +.btn { + display: inline-block; + .ie7-inline-block(); + padding: 4px 12px; + margin-bottom: 0; // For input.btn + font-size: @baseFontSize; + line-height: @baseLineHeight; + text-align: center; + vertical-align: middle; + cursor: pointer; + .buttonBackground(@btnBackground, @btnBackgroundHighlight, @grayDark, 0 1px 1px rgba(255,255,255,.75)); + border: 1px solid @btnBorder; + *border: 0; // Remove the border to prevent IE7's black border on input:focus + border-bottom-color: darken(@btnBorder, 10%); + .border-radius(@baseBorderRadius); + .ie7-restore-left-whitespace(); // Give IE7 some love + .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); + + // Hover state + &:hover { + color: @grayDark; + text-decoration: none; + background-position: 0 -15px; + + // transition is only when going to hover, otherwise the background + // behind the gradient (there for IE<=9 fallback) gets mismatched + .transition(background-position .1s linear); + } + + // Focus state for keyboard and accessibility + &:focus { + .tab-focus(); + } + + // Active state + &.active, + &:active { + background-image: none; + outline: 0; + .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); + } + + // Disabled state + &.disabled, + &[disabled] { + cursor: default; + background-image: none; + .opacity(65); + .box-shadow(none); + } + +} + + + +// Button Sizes +// -------------------------------------------------- + +// Large +.btn-large { + padding: @paddingLarge; + font-size: @fontSizeLarge; + .border-radius(@borderRadiusLarge); +} +.btn-large [class^="icon-"], +.btn-large [class*=" icon-"] { + margin-top: 4px; +} + +// Small +.btn-small { + padding: @paddingSmall; + font-size: @fontSizeSmall; + .border-radius(@borderRadiusSmall); +} +.btn-small [class^="icon-"], +.btn-small [class*=" icon-"] { + margin-top: 0; +} +.btn-mini [class^="icon-"], +.btn-mini [class*=" icon-"] { + margin-top: -1px; +} + +// Mini +.btn-mini { + padding: @paddingMini; + font-size: @fontSizeMini; + .border-radius(@borderRadiusSmall); +} + + +// Block button +// ------------------------- + +.btn-block { + display: block; + width: 100%; + padding-left: 0; + padding-right: 0; + .box-sizing(border-box); +} + +// Vertically space out multiple block buttons +.btn-block + .btn-block { + margin-top: 5px; +} + +// Specificity overrides +input[type="submit"], +input[type="reset"], +input[type="button"] { + &.btn-block { + width: 100%; + } +} + + + +// Alternate buttons +// -------------------------------------------------- + +// Provide *some* extra contrast for those who can get it +.btn-primary.active, +.btn-warning.active, +.btn-danger.active, +.btn-success.active, +.btn-info.active, +.btn-inverse.active { + color: rgba(255,255,255,.75); +} + +// Set the backgrounds +// ------------------------- +.btn { + // reset here as of 2.0.3 due to Recess property order + border-color: #c5c5c5; + border-color: rgba(0,0,0,.15) rgba(0,0,0,.15) rgba(0,0,0,.25); +} +.btn-primary { + .buttonBackground(@btnPrimaryBackground, @btnPrimaryBackgroundHighlight); +} +// Warning appears are orange +.btn-warning { + .buttonBackground(@btnWarningBackground, @btnWarningBackgroundHighlight); +} +// Danger and error appear as red +.btn-danger { + .buttonBackground(@btnDangerBackground, @btnDangerBackgroundHighlight); +} +// Success appears as green +.btn-success { + .buttonBackground(@btnSuccessBackground, @btnSuccessBackgroundHighlight); +} +// Info appears as a neutral blue +.btn-info { + .buttonBackground(@btnInfoBackground, @btnInfoBackgroundHighlight); +} +// Inverse appears as dark gray +.btn-inverse { + .buttonBackground(@btnInverseBackground, @btnInverseBackgroundHighlight); +} + + +// Cross-browser Jank +// -------------------------------------------------- + +button.btn, +input[type="submit"].btn { + + // Firefox 3.6 only I believe + &::-moz-focus-inner { + padding: 0; + border: 0; + } + + // IE7 has some default padding on button controls + *padding-top: 3px; + *padding-bottom: 3px; + + &.btn-large { + *padding-top: 7px; + *padding-bottom: 7px; + } + &.btn-small { + *padding-top: 3px; + *padding-bottom: 3px; + } + &.btn-mini { + *padding-top: 1px; + *padding-bottom: 1px; + } +} + + +// Link buttons +// -------------------------------------------------- + +// Make a button look and behave like a link +.btn-link, +.btn-link:active, +.btn-link[disabled] { + background-color: transparent; + background-image: none; + .box-shadow(none); +} +.btn-link { + border-color: transparent; + cursor: pointer; + color: @linkColor; + .border-radius(0); +} +.btn-link:hover { + color: @linkColorHover; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover { + color: @grayDark; + text-decoration: none; +} diff --git a/src/main/less/bootstrap/carousel.less b/src/main/less/bootstrap/carousel.less @@ -0,0 +1,131 @@ +// +// Carousel +// -------------------------------------------------- + + +.carousel { + position: relative; + margin-bottom: @baseLineHeight; + line-height: 1; +} + +.carousel-inner { + overflow: hidden; + width: 100%; + position: relative; +} + +.carousel-inner { + + > .item { + display: none; + position: relative; + .transition(.6s ease-in-out left); + } + + // Account for jankitude on images + > .item > img { + display: block; + line-height: 1; + } + + > .active, + > .next, + > .prev { display: block; } + + > .active { + left: 0; + } + + > .next, + > .prev { + position: absolute; + top: 0; + width: 100%; + } + + > .next { + left: 100%; + } + > .prev { + left: -100%; + } + > .next.left, + > .prev.right { + left: 0; + } + + > .active.left { + left: -100%; + } + > .active.right { + left: 100%; + } + +} + +// Left/right controls for nav +// --------------------------- + +.carousel-control { + position: absolute; + top: 40%; + left: 15px; + width: 40px; + height: 40px; + margin-top: -20px; + font-size: 60px; + font-weight: 100; + line-height: 30px; + color: @white; + text-align: center; + background: @grayDarker; + border: 3px solid @white; + .border-radius(23px); + .opacity(50); + + // we can't have this transition here + // because webkit cancels the carousel + // animation if you trip this while + // in the middle of another animation + // ;_; + // .transition(opacity .2s linear); + + // Reposition the right one + &.right { + left: auto; + right: 15px; + } + + // Hover state + &:hover { + color: @white; + text-decoration: none; + .opacity(90); + } +} + + +// Caption for text below images +// ----------------------------- + +.carousel-caption { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: 15px; + background: @grayDark; + background: rgba(0,0,0,.75); +} +.carousel-caption h4, +.carousel-caption p { + color: @white; + line-height: @baseLineHeight; +} +.carousel-caption h4 { + margin: 0 0 5px; +} +.carousel-caption p { + margin-bottom: 0; +} diff --git a/src/main/less/bootstrap/close.less b/src/main/less/bootstrap/close.less @@ -0,0 +1,31 @@ +// +// Close icons +// -------------------------------------------------- + + +.close { + float: right; + font-size: 20px; + font-weight: bold; + line-height: @baseLineHeight; + color: @black; + text-shadow: 0 1px 0 rgba(255,255,255,1); + .opacity(20); + &:hover { + color: @black; + text-decoration: none; + cursor: pointer; + .opacity(40); + } +} + +// Additional properties for button version +// iOS requires the button element instead of an anchor tag. +// If you want the anchor version, it requires `href="#"`. +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} +\ No newline at end of file diff --git a/src/main/less/bootstrap/code.less b/src/main/less/bootstrap/code.less @@ -0,0 +1,61 @@ +// +// Code (inline and blocK) +// -------------------------------------------------- + + +// Inline and block code styles +code, +pre { + padding: 0 3px 2px; + #font > #family > .monospace; + font-size: @baseFontSize - 2; + color: @grayDark; + .border-radius(3px); +} + +// Inline code +code { + padding: 2px 4px; + color: #d14; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + white-space: nowrap; +} + +// Blocks of code +pre { + display: block; + padding: (@baseLineHeight - 1) / 2; + margin: 0 0 @baseLineHeight / 2; + font-size: @baseFontSize - 1; // 14px to 13px + line-height: @baseLineHeight; + word-break: break-all; + word-wrap: break-word; + white-space: pre; + white-space: pre-wrap; + background-color: #f5f5f5; + border: 1px solid #ccc; // fallback for IE7-8 + border: 1px solid rgba(0,0,0,.15); + .border-radius(@baseBorderRadius); + + // Make prettyprint styles more spaced out for readability + &.prettyprint { + margin-bottom: @baseLineHeight; + } + + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + color: inherit; + white-space: pre; + white-space: pre-wrap; + background-color: transparent; + border: 0; + } +} + +// Enable scrollable blocks of code +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +\ No newline at end of file diff --git a/src/main/less/bootstrap/component-animations.less b/src/main/less/bootstrap/component-animations.less @@ -0,0 +1,22 @@ +// +// Component animations +// -------------------------------------------------- + + +.fade { + opacity: 0; + .transition(opacity .15s linear); + &.in { + opacity: 1; + } +} + +.collapse { + position: relative; + height: 0; + overflow: hidden; + .transition(height .35s ease); + &.in { + height: auto; + } +} diff --git a/src/main/less/bootstrap/dropdowns.less b/src/main/less/bootstrap/dropdowns.less @@ -0,0 +1,233 @@ +// +// Dropdown menus +// -------------------------------------------------- + + +// Use the .menu class on any <li> element within the topbar or ul.tabs and you'll get some superfancy dropdowns +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle { + // The caret makes the toggle a bit too tall in IE7 + *margin-bottom: -3px; +} +.dropdown-toggle:active, +.open .dropdown-toggle { + outline: 0; +} + +// Dropdown arrow/caret +// -------------------- +.caret { + display: inline-block; + width: 0; + height: 0; + vertical-align: top; + border-top: 4px solid @black; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + content: ""; +} + +// Place the caret +.dropdown .caret { + margin-top: 8px; + margin-left: 2px; +} + +// The dropdown menu (ul) +// ---------------------- +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: @zindexDropdown; + display: none; // none by default, but block on "open" of the menu + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; // override default ul + list-style: none; + background-color: @dropdownBackground; + border: 1px solid #ccc; // Fallback for IE7-8 + border: 1px solid @dropdownBorder; + *border-right-width: 2px; + *border-bottom-width: 2px; + .border-radius(6px); + .box-shadow(0 5px 10px rgba(0,0,0,.2)); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + + // Aligns the dropdown menu to right + &.pull-right { + right: 0; + left: auto; + } + + // Dividers (basically an hr) within the dropdown + .divider { + .nav-divider(@dropdownDividerTop, @dropdownDividerBottom); + } + + // Links within the dropdown menu + li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: @baseLineHeight; + color: @dropdownLinkColor; + white-space: nowrap; + } +} + +// Hover state +// ----------- +.dropdown-menu li > a:hover, +.dropdown-menu li > a:focus, +.dropdown-submenu:hover > a { + text-decoration: none; + color: @dropdownLinkColorHover; + #gradient > .vertical(@dropdownLinkBackgroundHover, darken(@dropdownLinkBackgroundHover, 5%)); +} + +// Active state +// ------------ +.dropdown-menu .active > a, +.dropdown-menu .active > a:hover { + color: @dropdownLinkColorActive; + text-decoration: none; + outline: 0; + #gradient > .vertical(@dropdownLinkBackgroundActive, darken(@dropdownLinkBackgroundActive, 5%)); +} + +// Disabled state +// -------------- +// Gray out text and ensure the hover state remains gray +.dropdown-menu .disabled > a, +.dropdown-menu .disabled > a:hover { + color: @grayLight; +} +// Nuke hover effects +.dropdown-menu .disabled > a:hover { + text-decoration: none; + background-color: transparent; + background-image: none; // Remove CSS gradient + .reset-filter(); + cursor: default; +} + +// Open state for the dropdown +// --------------------------- +.open { + // IE7's z-index only goes to the nearest positioned ancestor, which would + // make the menu appear below buttons that appeared later on the page + *z-index: @zindexDropdown; + + & > .dropdown-menu { + display: block; + } +} + +// Right aligned dropdowns +// --------------------------- +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// ------------------------------------------------------ +// Just add .dropup after the standard .dropdown class and you're set, bro. +// TODO: abstract this so that the navbar fixed styles are not placed here? +.dropup, +.navbar-fixed-bottom .dropdown { + // Reverse the caret + .caret { + border-top: 0; + border-bottom: 4px solid @black; + content: ""; + } + // Different positioning for bottom up menu + .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; + } +} + +// Sub menus +// --------------------------- +.dropdown-submenu { + position: relative; +} +// Default dropdowns +.dropdown-submenu > .dropdown-menu { + top: 0; + left: 100%; + margin-top: -6px; + margin-left: -1px; + .border-radius(0 6px 6px 6px); +} +.dropdown-submenu:hover > .dropdown-menu { + display: block; +} + +// Dropups +.dropup .dropdown-submenu > .dropdown-menu { + top: auto; + bottom: 0; + margin-top: 0; + margin-bottom: -2px; + .border-radius(5px 5px 5px 0); +} + +// Caret to indicate there is a submenu +.dropdown-submenu > a:after { + display: block; + content: " "; + float: right; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + border-width: 5px 0 5px 5px; + border-left-color: darken(@dropdownBackground, 20%); + margin-top: 5px; + margin-right: -10px; +} +.dropdown-submenu:hover > a:after { + border-left-color: @dropdownLinkColorHover; +} + +// Left aligned submenus +.dropdown-submenu.pull-left { + // Undo the float + // Yes, this is awkward since .pull-left adds a float, but it sticks to our conventions elsewhere. + float: none; + + // Positioning the submenu + > .dropdown-menu { + left: -100%; + margin-left: 10px; + .border-radius(6px 0 6px 6px); + } +} + +// Tweak nav headers +// ----------------- +// Increase padding from 15px to 20px on sides +.dropdown .dropdown-menu .nav-header { + padding-left: 20px; + padding-right: 20px; +} + +// Typeahead +// --------- +.typeahead { + z-index: 1051; + margin-top: 2px; // give it some space to breathe + .border-radius(@baseBorderRadius); +} diff --git a/src/main/less/bootstrap/forms.less b/src/main/less/bootstrap/forms.less @@ -0,0 +1,687 @@ +// +// Forms +// -------------------------------------------------- + + +// GENERAL STYLES +// -------------- + +// Make all forms have space below them +form { + margin: 0 0 @baseLineHeight; +} + +fieldset { + padding: 0; + margin: 0; + border: 0; +} + +// Groups of fields with labels on top (legends) +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: @baseLineHeight; + font-size: @baseFontSize * 1.5; + line-height: @baseLineHeight * 2; + color: @grayDark; + border: 0; + border-bottom: 1px solid #e5e5e5; + + // Small + small { + font-size: @baseLineHeight * .75; + color: @grayLight; + } +} + +// Set font for forms +label, +input, +button, +select, +textarea { + #font > .shorthand(@baseFontSize,normal,@baseLineHeight); // Set size, weight, line-height here +} +input, +button, +select, +textarea { + font-family: @baseFontFamily; // And only set font-family here for those that need it (note the missing label element) +} + +// Identify controls by their labels +label { + display: block; + margin-bottom: 5px; +} + +// Form controls +// ------------------------- + +// Shared size and type resets +select, +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + display: inline-block; + height: @baseLineHeight; + padding: 4px 6px; + margin-bottom: @baseLineHeight / 2; + font-size: @baseFontSize; + line-height: @baseLineHeight; + color: @gray; + .border-radius(@inputBorderRadius); + vertical-align: middle; +} + +// Reset appearance properties for textual inputs and textarea +// Declare width for legacy (can't be on input[type=*] selectors or it's too specific) +input, +textarea, +.uneditable-input { + width: 206px; // plus 12px padding and 2px border +} +// Reset height since textareas have rows +textarea { + height: auto; +} +// Everything else +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + background-color: @inputBackground; + border: 1px solid @inputBorder; + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); + .transition(~"border linear .2s, box-shadow linear .2s"); + + // Focus state + &:focus { + border-color: rgba(82,168,236,.8); + outline: 0; + outline: thin dotted \9; /* IE6-9 */ + .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6)"); + } +} + +// Position radios and checkboxes better +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + *margin-top: 0; /* IE7 */ + margin-top: 1px \9; /* IE8-9 */ + line-height: normal; +} + +// Reset width of input images, buttons, radios, checkboxes +input[type="file"], +input[type="image"], +input[type="submit"], +input[type="reset"], +input[type="button"], +input[type="radio"], +input[type="checkbox"] { + width: auto; // Override of generic input selector +} + +// Set the height of select and file controls to match text inputs +select, +input[type="file"] { + height: @inputHeight; /* In IE7, the height of the select element cannot be changed by height, only font-size */ + *margin-top: 4px; /* For IE7, add top margin to align select with labels */ + line-height: @inputHeight; +} + +// Make select elements obey height by applying a border +select { + width: 220px; // default input width + 10px of padding that doesn't get applied + border: 1px solid @inputBorder; + background-color: @inputBackground; // Chrome on Linux and Mobile Safari need background-color +} + +// Make multiple select elements height not fixed +select[multiple], +select[size] { + height: auto; +} + +// Focus for select, file, radio, and checkbox +select:focus, +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + .tab-focus(); +} + + +// Uneditable inputs +// ------------------------- + +// Make uneditable inputs look inactive +.uneditable-input, +.uneditable-textarea { + color: @grayLight; + background-color: darken(@inputBackground, 1%); + border-color: @inputBorder; + .box-shadow(inset 0 1px 2px rgba(0,0,0,.025)); + cursor: not-allowed; +} + +// For text that needs to appear as an input but should not be an input +.uneditable-input { + overflow: hidden; // prevent text from wrapping, but still cut it off like an input does + white-space: nowrap; +} + +// Make uneditable textareas behave like a textarea +.uneditable-textarea { + width: auto; + height: auto; +} + + +// Placeholder +// ------------------------- + +// Placeholder text gets special styles because when browsers invalidate entire lines if it doesn't understand a selector +input, +textarea { + .placeholder(); +} + + +// CHECKBOXES & RADIOS +// ------------------- + +// Indent the labels to position radios/checkboxes as hanging +.radio, +.checkbox { + min-height: @baseLineHeight; // clear the floating input if there is no label text + padding-left: 20px; +} +.radio input[type="radio"], +.checkbox input[type="checkbox"] { + float: left; + margin-left: -20px; +} + +// Move the options list down to align with labels +.controls > .radio:first-child, +.controls > .checkbox:first-child { + padding-top: 5px; // has to be padding because margin collaspes +} + +// Radios and checkboxes on same line +// TODO v3: Convert .inline to .control-inline +.radio.inline, +.checkbox.inline { + display: inline-block; + padding-top: 5px; + margin-bottom: 0; + vertical-align: middle; +} +.radio.inline + .radio.inline, +.checkbox.inline + .checkbox.inline { + margin-left: 10px; // space out consecutive inline controls +} + + + +// INPUT SIZES +// ----------- + +// General classes for quick sizes +.input-mini { width: 60px; } +.input-small { width: 90px; } +.input-medium { width: 150px; } +.input-large { width: 210px; } +.input-xlarge { width: 270px; } +.input-xxlarge { width: 530px; } + +// Grid style input sizes +input[class*="span"], +select[class*="span"], +textarea[class*="span"], +.uneditable-input[class*="span"], +// Redeclare since the fluid row class is more specific +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"] { + float: none; + margin-left: 0; +} +// Ensure input-prepend/append never wraps +.input-append input[class*="span"], +.input-append .uneditable-input[class*="span"], +.input-prepend input[class*="span"], +.input-prepend .uneditable-input[class*="span"], +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"], +.row-fluid .input-prepend [class*="span"], +.row-fluid .input-append [class*="span"] { + display: inline-block; +} + + + +// GRID SIZING FOR INPUTS +// ---------------------- + +// Grid sizes +#grid > .input(@gridColumnWidth, @gridGutterWidth); + +// Control row for multiple inputs per line +.controls-row { + .clearfix(); // Clear the float from controls +} + +// Float to collapse white-space for proper grid alignment +.controls-row [class*="span"], +// Redeclare the fluid grid collapse since we undo the float for inputs +.row-fluid .controls-row [class*="span"] { + float: left; +} +// Explicity set top padding on all checkboxes/radios, not just first-child +.controls-row .checkbox[class*="span"], +.controls-row .radio[class*="span"] { + padding-top: 5px; +} + + + + +// DISABLED STATE +// -------------- + +// Disabled and read-only inputs +input[disabled], +select[disabled], +textarea[disabled], +input[readonly], +select[readonly], +textarea[readonly] { + cursor: not-allowed; + background-color: @inputDisabledBackground; +} +// Explicitly reset the colors here +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"][readonly], +input[type="checkbox"][readonly] { + background-color: transparent; +} + + + + +// FORM FIELD FEEDBACK STATES +// -------------------------- + +// Warning +.control-group.warning { + .formFieldState(@warningText, @warningText, @warningBackground); +} +// Error +.control-group.error { + .formFieldState(@errorText, @errorText, @errorBackground); +} +// Success +.control-group.success { + .formFieldState(@successText, @successText, @successBackground); +} +// Success +.control-group.info { + .formFieldState(@infoText, @infoText, @infoBackground); +} + +// HTML5 invalid states +// Shares styles with the .control-group.error above +input:focus:invalid, +textarea:focus:invalid, +select:focus:invalid { + color: #b94a48; + border-color: #ee5f5b; + &:focus { + border-color: darken(#ee5f5b, 10%); + @shadow: 0 0 6px lighten(#ee5f5b, 20%); + .box-shadow(@shadow); + } +} + + + +// FORM ACTIONS +// ------------ + +.form-actions { + padding: (@baseLineHeight - 1) 20px @baseLineHeight; + margin-top: @baseLineHeight; + margin-bottom: @baseLineHeight; + background-color: @formActionsBackground; + border-top: 1px solid #e5e5e5; + .clearfix(); // Adding clearfix to allow for .pull-right button containers +} + + + +// HELP TEXT +// --------- + +.help-block, +.help-inline { + color: lighten(@textColor, 15%); // lighten the text some for contrast +} + +.help-block { + display: block; // account for any element using help-block + margin-bottom: @baseLineHeight / 2; +} + +.help-inline { + display: inline-block; + .ie7-inline-block(); + vertical-align: middle; + padding-left: 5px; +} + + + +// INPUT GROUPS +// ------------ + +// Allow us to put symbols and text within the input field for a cleaner look +.input-append, +.input-prepend { + margin-bottom: 5px; + font-size: 0; // white space collapse hack + white-space: nowrap; // Prevent span and input from separating + + // Reset the white space collapse hack + input, + select, + .uneditable-input, + .dropdown-menu { + font-size: @baseFontSize; + } + + input, + select, + .uneditable-input { + position: relative; // placed here by default so that on :focus we can place the input above the .add-on for full border and box-shadow goodness + margin-bottom: 0; // prevent bottom margin from screwing up alignment in stacked forms + *margin-left: 0; + vertical-align: top; + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + // Make input on top when focused so blue border and shadow always show + &:focus { + z-index: 2; + } + } + .add-on { + display: inline-block; + width: auto; + height: @baseLineHeight; + min-width: 16px; + padding: 4px 5px; + font-size: @baseFontSize; + font-weight: normal; + line-height: @baseLineHeight; + text-align: center; + text-shadow: 0 1px 0 @white; + background-color: @grayLighter; + border: 1px solid #ccc; + } + .add-on, + .btn, + .btn-group > .dropdown-toggle { + vertical-align: top; + .border-radius(0); + } + .active { + background-color: lighten(@green, 30); + border-color: @green; + } +} + +.input-prepend { + .add-on, + .btn { + margin-right: -1px; + } + .add-on:first-child, + .btn:first-child { + // FYI, `.btn:first-child` accounts for a button group that's prepended + .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); + } +} + +.input-append { + input, + select, + .uneditable-input { + .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); + + .btn-group .btn:last-child { + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } + } + .add-on, + .btn, + .btn-group { + margin-left: -1px; + } + .add-on:last-child, + .btn:last-child, + .btn-group:last-child > .dropdown-toggle { + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } +} + +// Remove all border-radius for inputs with both prepend and append +.input-prepend.input-append { + input, + select, + .uneditable-input { + .border-radius(0); + + .btn-group .btn { + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } + } + .add-on:first-child, + .btn:first-child { + margin-right: -1px; + .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); + } + .add-on:last-child, + .btn:last-child { + margin-left: -1px; + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } + .btn-group:first-child { + margin-left: 0; + } +} + + + + +// SEARCH FORM +// ----------- + +input.search-query { + padding-right: 14px; + padding-right: 4px \9; + padding-left: 14px; + padding-left: 4px \9; /* IE7-8 doesn't have border-radius, so don't indent the padding */ + margin-bottom: 0; // Remove the default margin on all inputs + .border-radius(15px); +} + +/* Allow for input prepend/append in search forms */ +.form-search .input-append .search-query, +.form-search .input-prepend .search-query { + .border-radius(0); // Override due to specificity +} +.form-search .input-append .search-query { + .border-radius(14px 0 0 14px); +} +.form-search .input-append .btn { + .border-radius(0 14px 14px 0); +} +.form-search .input-prepend .search-query { + .border-radius(0 14px 14px 0); +} +.form-search .input-prepend .btn { + .border-radius(14px 0 0 14px); +} + + + + +// HORIZONTAL & VERTICAL FORMS +// --------------------------- + +// Common properties +// ----------------- + +.form-search, +.form-inline, +.form-horizontal { + input, + textarea, + select, + .help-inline, + .uneditable-input, + .input-prepend, + .input-append { + display: inline-block; + .ie7-inline-block(); + margin-bottom: 0; + vertical-align: middle; + } + // Re-hide hidden elements due to specifity + .hide { + display: none; + } +} +.form-search label, +.form-inline label, +.form-search .btn-group, +.form-inline .btn-group { + display: inline-block; +} +// Remove margin for input-prepend/-append +.form-search .input-append, +.form-inline .input-append, +.form-search .input-prepend, +.form-inline .input-prepend { + margin-bottom: 0; +} +// Inline checkbox/radio labels (remove padding on left) +.form-search .radio, +.form-search .checkbox, +.form-inline .radio, +.form-inline .checkbox { + padding-left: 0; + margin-bottom: 0; + vertical-align: middle; +} +// Remove float and margin, set to inline-block +.form-search .radio input[type="radio"], +.form-search .checkbox input[type="checkbox"], +.form-inline .radio input[type="radio"], +.form-inline .checkbox input[type="checkbox"] { + float: left; + margin-right: 3px; + margin-left: 0; +} + + +// Margin to space out fieldsets +.control-group { + margin-bottom: @baseLineHeight / 2; +} + +// Legend collapses margin, so next element is responsible for spacing +legend + .control-group { + margin-top: @baseLineHeight; + -webkit-margin-top-collapse: separate; +} + +// Horizontal-specific styles +// -------------------------- + +.form-horizontal { + // Increase spacing between groups + .control-group { + margin-bottom: @baseLineHeight; + .clearfix(); + } + // Float the labels left + .control-label { + float: left; + width: @horizontalComponentOffset - 20; + padding-top: 5px; + text-align: right; + } + // Move over all input controls and content + .controls { + // Super jank IE7 fix to ensure the inputs in .input-append and input-prepend + // don't inherit the margin of the parent, in this case .controls + *display: inline-block; + *padding-left: 20px; + margin-left: @horizontalComponentOffset; + *margin-left: 0; + &:first-child { + *padding-left: @horizontalComponentOffset; + } + } + // Remove bottom margin on block level help text since that's accounted for on .control-group + .help-block { + margin-bottom: 0; + } + // And apply it only to .help-block instances that follow a form control + input, + select, + textarea, + .uneditable-input, + .input-prepend, + .input-append { + + .help-block { + margin-top: @baseLineHeight / 2; + } + } + // Move over buttons in .form-actions to align with .controls + .form-actions { + padding-left: @horizontalComponentOffset; + } +} diff --git a/src/main/less/bootstrap/grid.less b/src/main/less/bootstrap/grid.less @@ -0,0 +1,21 @@ +// +// Grid system +// -------------------------------------------------- + + +// Fixed (940px) +#grid > .core(@gridColumnWidth, @gridGutterWidth); + +// Fluid (940px) +#grid > .fluid(@fluidGridColumnWidth, @fluidGridGutterWidth); + +// Reset utility classes due to specificity +[class*="span"].hide, +.row-fluid [class*="span"].hide { + display: none; +} + +[class*="span"].pull-right, +.row-fluid [class*="span"].pull-right { + float: right; +} diff --git a/src/main/less/bootstrap/hero-unit.less b/src/main/less/bootstrap/hero-unit.less @@ -0,0 +1,25 @@ +// +// Hero unit +// -------------------------------------------------- + + +.hero-unit { + padding: 60px; + margin-bottom: 30px; + font-size: 18px; + font-weight: 200; + line-height: @baseLineHeight * 1.5; + color: @heroUnitLeadColor; + background-color: @heroUnitBackground; + .border-radius(6px); + h1 { + margin-bottom: 0; + font-size: 60px; + line-height: 1; + color: @heroUnitHeadingColor; + letter-spacing: -1px; + } + li { + line-height: @baseLineHeight * 1.5; // Reset since we specify in type.less + } +} diff --git a/src/main/less/bootstrap/labels-badges.less b/src/main/less/bootstrap/labels-badges.less @@ -0,0 +1,82 @@ +// +// Labels and badges +// -------------------------------------------------- + + +// Base classes +.label, +.badge { + display: inline-block; + padding: 2px 4px; + font-size: @baseFontSize * .846; + font-weight: bold; + line-height: 14px; // ensure proper line-height if floated + color: @white; + vertical-align: baseline; + white-space: nowrap; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + background-color: @grayLight; +} +// Set unique padding and border-radii +.label { + .border-radius(3px); +} +.badge { + padding-left: 9px; + padding-right: 9px; + .border-radius(9px); +} + +// Empty labels/badges collapse +.label, +.badge { + &:empty { + display: none; + } +} + +// Hover state, but only for links +a { + &.label:hover, + &.badge:hover { + color: @white; + text-decoration: none; + cursor: pointer; + } +} + +// Colors +// Only give background-color difference to links (and to simplify, we don't qualifty with `a` but [href] attribute) +.label, +.badge { + // Important (red) + &-important { background-color: @errorText; } + &-important[href] { background-color: darken(@errorText, 10%); } + // Warnings (orange) + &-warning { background-color: @orange; } + &-warning[href] { background-color: darken(@orange, 10%); } + // Success (green) + &-success { background-color: @successText; } + &-success[href] { background-color: darken(@successText, 10%); } + // Info (turquoise) + &-info { background-color: @infoText; } + &-info[href] { background-color: darken(@infoText, 10%); } + // Inverse (black) + &-inverse { background-color: @grayDark; } + &-inverse[href] { background-color: darken(@grayDark, 10%); } +} + +// Quick fix for labels/badges in buttons +.btn { + .label, + .badge { + position: relative; + top: -1px; + } +} +.btn-mini { + .label, + .badge { + top: 0; + } +} diff --git a/src/main/less/bootstrap/layouts.less b/src/main/less/bootstrap/layouts.less @@ -0,0 +1,16 @@ +// +// Layouts +// -------------------------------------------------- + + +// Container (centered, fixed-width layouts) +.container { + .container-fixed(); +} + +// Fluid layouts (left aligned, with sidebar, min- & max-width content) +.container-fluid { + padding-right: @gridGutterWidth; + padding-left: @gridGutterWidth; + .clearfix(); +} +\ No newline at end of file diff --git a/src/main/less/bootstrap/media.less b/src/main/less/bootstrap/media.less @@ -0,0 +1,55 @@ +// Media objects +// Source: http://stubbornella.org/content/?p=497 +// -------------------------------------------------- + + +// Common styles +// ------------------------- + +// Clear the floats +.media, +.media-body { + overflow: hidden; + *overflow: visible; + zoom: 1; +} + +// Proper spacing between instances of .media +.media, +.media .media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} + +// For images and videos, set to block +.media-object { + display: block; +} + +// Reset margins on headings for tighter default spacing +.media-heading { + margin: 0 0 5px; +} + + +// Media image alignment +// ------------------------- + +.media .pull-left { + margin-right: 10px; +} +.media .pull-right { + margin-left: 10px; +} + + +// Media list variation +// ------------------------- + +// Undo default ul/ol styles +.media-list { + margin-left: 0; + list-style: none; +} diff --git a/src/main/less/bootstrap/mixins.less b/src/main/less/bootstrap/mixins.less @@ -0,0 +1,686 @@ +// +// Mixins +// -------------------------------------------------- + + +// UTILITY MIXINS +// -------------------------------------------------- + +// Clearfix +// -------- +// For clearing floats like a boss h5bp.com/q +.clearfix { + *zoom: 1; + &:before, + &:after { + display: table; + content: ""; + // Fixes Opera/contenteditable bug: + // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 + line-height: 0; + } + &:after { + clear: both; + } +} + +// Webkit-style focus +// ------------------ +.tab-focus() { + // Default + outline: thin dotted #333; + // Webkit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +// Center-align a block level element +// ---------------------------------- +.center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} + +// IE7 inline-block +// ---------------- +.ie7-inline-block() { + *display: inline; /* IE7 inline-block hack */ + *zoom: 1; +} + +// IE7 likes to collapse whitespace on either side of the inline-block elements. +// Ems because we're attempting to match the width of a space character. Left +// version is for form buttons, which typically come after other elements, and +// right version is for icons, which come before. Applying both is ok, but it will +// mean that space between those elements will be .6em (~2 space characters) in IE7, +// instead of the 1 space in other browsers. +.ie7-restore-left-whitespace() { + *margin-left: .3em; + + &:first-child { + *margin-left: 0; + } +} + +.ie7-restore-right-whitespace() { + *margin-right: .3em; +} + +// Sizing shortcuts +// ------------------------- +.size(@height, @width) { + width: @width; + height: @height; +} +.square(@size) { + .size(@size, @size); +} + +// Placeholder text +// ------------------------- +.placeholder(@color: @placeholderText) { + &:-moz-placeholder { + color: @color; + } + &:-ms-input-placeholder { + color: @color; + } + &::-webkit-input-placeholder { + color: @color; + } +} + +// Text overflow +// ------------------------- +// Requires inline-block or block for proper styling +.text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// CSS image replacement +// ------------------------- +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + + +// FONTS +// -------------------------------------------------- + +#font { + #family { + .serif() { + font-family: @serifFontFamily; + } + .sans-serif() { + font-family: @sansFontFamily; + } + .monospace() { + font-family: @monoFontFamily; + } + } + .shorthand(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + font-size: @size; + font-weight: @weight; + line-height: @lineHeight; + } + .serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .serif; + #font > .shorthand(@size, @weight, @lineHeight); + } + .sans-serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .sans-serif; + #font > .shorthand(@size, @weight, @lineHeight); + } + .monospace(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .monospace; + #font > .shorthand(@size, @weight, @lineHeight); + } +} + + +// FORMS +// -------------------------------------------------- + +// Block level inputs +.input-block-level { + display: block; + width: 100%; + min-height: @inputHeight; // Make inputs at least the height of their button counterpart (base line-height + padding + border) + .box-sizing(border-box); // Makes inputs behave like true block-level elements +} + + + +// Mixin for form field states +.formFieldState(@textColor: #555, @borderColor: #ccc, @backgroundColor: #f5f5f5) { + // Set the text color + .control-label, + .help-block, + .help-inline { + color: @textColor; + } + // Style inputs accordingly + .checkbox, + .radio, + input, + select, + textarea { + color: @textColor; + } + input, + select, + textarea { + border-color: @borderColor; + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work + &:focus { + border-color: darken(@borderColor, 10%); + @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@borderColor, 20%); + .box-shadow(@shadow); + } + } + // Give a small background color for input-prepend/-append + .input-prepend .add-on, + .input-append .add-on { + color: @textColor; + background-color: @backgroundColor; + border-color: @textColor; + } +} + + + +// CSS3 PROPERTIES +// -------------------------------------------------- + +// Border Radius +.border-radius(@radius) { + -webkit-border-radius: @radius; + -moz-border-radius: @radius; + border-radius: @radius; +} + +// Single Corner Border Radius +.border-top-left-radius(@radius) { + -webkit-border-top-left-radius: @radius; + -moz-border-radius-topleft: @radius; + border-top-left-radius: @radius; +} +.border-top-right-radius(@radius) { + -webkit-border-top-right-radius: @radius; + -moz-border-radius-topright: @radius; + border-top-right-radius: @radius; +} +.border-bottom-right-radius(@radius) { + -webkit-border-bottom-right-radius: @radius; + -moz-border-radius-bottomright: @radius; + border-bottom-right-radius: @radius; +} +.border-bottom-left-radius(@radius) { + -webkit-border-bottom-left-radius: @radius; + -moz-border-radius-bottomleft: @radius; + border-bottom-left-radius: @radius; +} + +// Single Side Border Radius +.border-top-radius(@radius) { + .border-top-right-radius(@radius); + .border-top-left-radius(@radius); +} +.border-right-radius(@radius) { + .border-top-right-radius(@radius); + .border-bottom-right-radius(@radius); +} +.border-bottom-radius(@radius) { + .border-bottom-right-radius(@radius); + .border-bottom-left-radius(@radius); +} +.border-left-radius(@radius) { + .border-top-left-radius(@radius); + .border-bottom-left-radius(@radius); +} + +// Drop shadows +.box-shadow(@shadow) { + -webkit-box-shadow: @shadow; + -moz-box-shadow: @shadow; + box-shadow: @shadow; +} + +// Transitions +.transition(@transition) { + -webkit-transition: @transition; + -moz-transition: @transition; + -o-transition: @transition; + transition: @transition; +} +.transition-delay(@transition-delay) { + -webkit-transition-delay: @transition-delay; + -moz-transition-delay: @transition-delay; + -o-transition-delay: @transition-delay; + transition-delay: @transition-delay; +} + +// Transformations +.rotate(@degrees) { + -webkit-transform: rotate(@degrees); + -moz-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); + -o-transform: rotate(@degrees); + transform: rotate(@degrees); +} +.scale(@ratio) { + -webkit-transform: scale(@ratio); + -moz-transform: scale(@ratio); + -ms-transform: scale(@ratio); + -o-transform: scale(@ratio); + transform: scale(@ratio); +} +.translate(@x, @y) { + -webkit-transform: translate(@x, @y); + -moz-transform: translate(@x, @y); + -ms-transform: translate(@x, @y); + -o-transform: translate(@x, @y); + transform: translate(@x, @y); +} +.skew(@x, @y) { + -webkit-transform: skew(@x, @y); + -moz-transform: skew(@x, @y); + -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twitter/bootstrap/issues/4885 + -o-transform: skew(@x, @y); + transform: skew(@x, @y); + -webkit-backface-visibility: hidden; // See https://github.com/twitter/bootstrap/issues/5319 +} +.translate3d(@x, @y, @z) { + -webkit-transform: translate3d(@x, @y, @z); + -moz-transform: translate3d(@x, @y, @z); + -o-transform: translate3d(@x, @y, @z); + transform: translate3d(@x, @y, @z); +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden +// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples +.backface-visibility(@visibility){ + -webkit-backface-visibility: @visibility; + -moz-backface-visibility: @visibility; + backface-visibility: @visibility; +} + +// Background clipping +// Heads up: FF 3.6 and under need "padding" instead of "padding-box" +.background-clip(@clip) { + -webkit-background-clip: @clip; + -moz-background-clip: @clip; + background-clip: @clip; +} + +// Background sizing +.background-size(@size) { + -webkit-background-size: @size; + -moz-background-size: @size; + -o-background-size: @size; + background-size: @size; +} + + +// Box sizing +.box-sizing(@boxmodel) { + -webkit-box-sizing: @boxmodel; + -moz-box-sizing: @boxmodel; + box-sizing: @boxmodel; +} + +// User select +// For selecting text on the page +.user-select(@select) { + -webkit-user-select: @select; + -moz-user-select: @select; + -ms-user-select: @select; + -o-user-select: @select; + user-select: @select; +} + +// Resize anything +.resizable(@direction) { + resize: @direction; // Options: horizontal, vertical, both + overflow: auto; // Safari fix +} + +// CSS3 Content Columns +.content-columns(@columnCount, @columnGap: @gridGutterWidth) { + -webkit-column-count: @columnCount; + -moz-column-count: @columnCount; + column-count: @columnCount; + -webkit-column-gap: @columnGap; + -moz-column-gap: @columnGap; + column-gap: @columnGap; +} + +// Optional hyphenation +.hyphens(@mode: auto) { + word-wrap: break-word; + -webkit-hyphens: @mode; + -moz-hyphens: @mode; + -ms-hyphens: @mode; + -o-hyphens: @mode; + hyphens: @mode; +} + +// Opacity +.opacity(@opacity) { + opacity: @opacity / 100; + filter: ~"alpha(opacity=@{opacity})"; +} + + + +// BACKGROUNDS +// -------------------------------------------------- + +// Add an alphatransparency value to any background or border color (via Elyse Holladay) +#translucent { + .background(@color: @white, @alpha: 1) { + background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + } + .border(@color: @white, @alpha: 1) { + border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + .background-clip(padding-box); + } +} + +// Gradient Bar Colors for buttons and alerts +.gradientBar(@primaryColor, @secondaryColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) { + color: @textColor; + text-shadow: @textShadow; + #gradient > .vertical(@primaryColor, @secondaryColor); + border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); + border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); +} + +// Gradients +#gradient { + .horizontal(@startColor: #555, @endColor: #333) { + background-color: @endColor; + background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10 + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@startColor),argb(@endColor))); // IE9 and down + } + .vertical(@startColor: #555, @endColor: #333) { + background-color: mix(@startColor, @endColor, 60%); + background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10 + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down + } + .directional(@startColor: #555, @endColor: #333, @deg: 45deg) { + background-color: @endColor; + background-repeat: repeat-x; + background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(@deg, @startColor, @endColor); // Standard, IE10 + } + .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + background-color: mix(@midColor, @endColor, 80%); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); + background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor); + background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback + } + .radial(@innerColor: #555, @outerColor: #333) { + background-color: @outerColor; + background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@innerColor), to(@outerColor)); + background-image: -webkit-radial-gradient(circle, @innerColor, @outerColor); + background-image: -moz-radial-gradient(circle, @innerColor, @outerColor); + background-image: -o-radial-gradient(circle, @innerColor, @outerColor); + background-repeat: no-repeat; + } + .striped(@color: #555, @angle: 45deg) { + background-color: @color; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + } +} +// Reset filters for IE +.reset-filter() { + filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); +} + + + +// COMPONENT MIXINS +// -------------------------------------------------- + +// Horizontal dividers +// ------------------------- +// Dividers (basically an hr) within dropdowns and nav lists +.nav-divider(@top: #e5e5e5, @bottom: @white) { + // IE7 needs a set width since we gave a height. Restricting just + // to IE7 to keep the 1px left/right space in other browsers. + // It is unclear where IE is getting the extra space that we need + // to negative-margin away, but so it goes. + *width: 100%; + height: 1px; + margin: ((@baseLineHeight / 2) - 1) 1px; // 8px 1px + *margin: -5px 0 5px; + overflow: hidden; + background-color: @top; + border-bottom: 1px solid @bottom; +} + +// Button backgrounds +// ------------------ +.buttonBackground(@startColor, @endColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) { + // gradientBar will set the background to a pleasing blend of these, to support IE<=9 + .gradientBar(@startColor, @endColor, @textColor, @textShadow); + *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + .reset-filter(); + + // in these cases the gradient won't cover the background, so we override + &:hover, &:active, &.active, &.disabled, &[disabled] { + color: @textColor; + background-color: @endColor; + *background-color: darken(@endColor, 5%); + } + + // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves + &:active, + &.active { + background-color: darken(@endColor, 10%) e("\9"); + } +} + +// Navbar vertical align +// ------------------------- +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbarVerticalAlign(30px);` to calculate the appropriate top margin. +.navbarVerticalAlign(@elementHeight) { + margin-top: (@navbarHeight - @elementHeight) / 2; +} + + + +// Grid System +// ----------- + +// Centered container element +.container-fixed() { + margin-right: auto; + margin-left: auto; + .clearfix(); +} + +// Table columns +.tableColumns(@columnSpan: 1) { + float: none; // undo default grid column styles + width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 16; // 16 is total padding on left and right of table cells + margin-left: 0; // undo default grid column styles +} + +// Make a Grid +// Use .makeRow and .makeColumn to assign semantic layouts grid system behavior +.makeRow() { + margin-left: @gridGutterWidth * -1; + .clearfix(); +} +.makeColumn(@columns: 1, @offset: 0) { + float: left; + margin-left: (@gridColumnWidth * @offset) + (@gridGutterWidth * (@offset - 1)) + (@gridGutterWidth * 2); + width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); +} + +// The Grid +#grid { + + .core (@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + (~".span@{index}") { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .offsetX (@index) when (@index > 0) { + (~".offset@{index}") { .offset(@index); } + .offsetX(@index - 1); + } + .offsetX (0) {} + + .offset (@columns) { + margin-left: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns + 1)); + } + + .span (@columns) { + width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); + } + + .row { + margin-left: @gridGutterWidth * -1; + .clearfix(); + } + + [class*="span"] { + float: left; + min-height: 1px; // prevent collapsing columns + margin-left: @gridGutterWidth; + } + + // Set the container width, and override it for fixed navbars in media queries + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { .span(@gridColumns); } + + // generate .spanX and .offsetX + .spanX (@gridColumns); + .offsetX (@gridColumns); + + } + + .fluid (@fluidGridColumnWidth, @fluidGridGutterWidth) { + + .spanX (@index) when (@index > 0) { + (~".span@{index}") { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .offsetX (@index) when (@index > 0) { + (~'.offset@{index}') { .offset(@index); } + (~'.offset@{index}:first-child') { .offsetFirstChild(@index); } + .offsetX(@index - 1); + } + .offsetX (0) {} + + .offset (@columns) { + margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth*2); + *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + (@fluidGridGutterWidth*2) - (.5 / @gridRowWidth * 100 * 1%); + } + + .offsetFirstChild (@columns) { + margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth); + *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); + } + + .span (@columns) { + width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)); + *width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%); + } + + .row-fluid { + width: 100%; + .clearfix(); + [class*="span"] { + .input-block-level(); + float: left; + margin-left: @fluidGridGutterWidth; + *margin-left: @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); + } + [class*="span"]:first-child { + margin-left: 0; + } + + // Space grid-sized controls properly if multiple per line + .controls-row [class*="span"] + [class*="span"] { + margin-left: @fluidGridGutterWidth; + } + + // generate .spanX and .offsetX + .spanX (@gridColumns); + .offsetX (@gridColumns); + } + + } + + .input(@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + (~"input.span@{index}, textarea.span@{index}, .uneditable-input.span@{index}") { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .span(@columns) { + width: ((@gridColumnWidth) * @columns) + (@gridGutterWidth * (@columns - 1)) - 14; + } + + input, + textarea, + .uneditable-input { + margin-left: 0; // override margin-left from core grid system + } + + // Space grid-sized controls properly if multiple per line + .controls-row [class*="span"] + [class*="span"] { + margin-left: @gridGutterWidth; + } + + // generate .spanX + .spanX (@gridColumns); + + } + +} diff --git a/src/main/less/bootstrap/modals.less b/src/main/less/bootstrap/modals.less @@ -0,0 +1,95 @@ +// +// Modals +// -------------------------------------------------- + +// Background +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: @zindexModalBackdrop; + background-color: @black; + // Fade for backdrop + &.fade { opacity: 0; } +} + +.modal-backdrop, +.modal-backdrop.fade.in { + .opacity(80); +} + +// Base modal +.modal { + position: fixed; + top: 10%; + left: 50%; + z-index: @zindexModal; + width: 560px; + margin-left: -280px; + background-color: @white; + border: 1px solid #999; + border: 1px solid rgba(0,0,0,.3); + *border: 1px solid #999; /* IE6-7 */ + .border-radius(6px); + .box-shadow(0 3px 7px rgba(0,0,0,0.3)); + .background-clip(padding-box); + // Remove focus outline from opened modal + outline: none; + + &.fade { + .transition(e('opacity .3s linear, top .3s ease-out')); + top: -25%; + } + &.fade.in { top: 10%; } +} +.modal-header { + padding: 9px 15px; + border-bottom: 1px solid #eee; + // Close icon + .close { margin-top: 2px; } + // Heading + h3 { + margin: 0; + line-height: 30px; + } +} + +// Body (where all modal content resides) +.modal-body { + position: relative; + overflow-y: auto; + max-height: 400px; + padding: 15px; +} +// Remove bottom margin if need be +.modal-form { + margin-bottom: 0; +} + +// Footer (for actions) +.modal-footer { + padding: 14px 15px 15px; + margin-bottom: 0; + text-align: right; // right align buttons + background-color: #f5f5f5; + border-top: 1px solid #ddd; + .border-radius(0 0 6px 6px); + .box-shadow(inset 0 1px 0 @white); + .clearfix(); // clear it in case folks use .pull-* classes on buttons + + // Properly space out buttons + .btn + .btn { + margin-left: 5px; + margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs + } + // but override that for button groups + .btn-group .btn + .btn { + margin-left: -1px; + } + // and override it for block buttons as well + .btn-block + .btn-block { + margin-left: 0; + } +} diff --git a/src/main/less/bootstrap/navbar.less b/src/main/less/bootstrap/navbar.less @@ -0,0 +1,493 @@ +// +// Navbars (Redux) +// -------------------------------------------------- + + +// COMMON STYLES +// ------------- + +// Base class and wrapper +.navbar { + overflow: visible; + margin-bottom: @baseLineHeight; + + // Fix for IE7's bad z-indexing so dropdowns don't appear below content that follows the navbar + *position: relative; + *z-index: 2; +} + +// Inner for background effects +// Gradient is applied to its own element because overflow visible is not honored by IE when filter is present +.navbar-inner { + min-height: @navbarHeight; + padding-left: 20px; + padding-right: 20px; + #gradient > .vertical(@navbarBackgroundHighlight, @navbarBackground); + border: 1px solid @navbarBorder; + .border-radius(@baseBorderRadius); + .box-shadow(0 1px 4px rgba(0,0,0,.065)); + + // Prevent floats from breaking the navbar + .clearfix(); +} + +// Set width to auto for default container +// We then reset it for fixed navbars in the #gridSystem mixin +.navbar .container { + width: auto; +} + +// Override the default collapsed state +.nav-collapse.collapse { + height: auto; + overflow: visible; +} + + +// Brand: website or project name +// ------------------------- +.navbar .brand { + position: absolute; + width: 100%; + left: 0; + text-align: center; + margin: auto; + padding: ((@navbarHeight - @baseLineHeight) / 2) 20px ((@navbarHeight - @baseLineHeight) / 2); + a { + font-size: 20px; + font-weight: 200; + color: @navbarBrandColor; + text-shadow: 0 1px 0 @navbarBackgroundHighlight; + &:hover, &:focus { + text-decoration: none; + } + } +} + +// Plain text in topbar +// ------------------------- +.navbar-text { + margin-bottom: 0; + line-height: @navbarHeight; + color: @navbarText; +} + +// Janky solution for now to account for links outside the .nav +// ------------------------- +.navbar-link { + color: @navbarLinkColor; + &:hover { + color: @navbarLinkColorHover; + } +} + +// Dividers in navbar +// ------------------------- +.navbar .divider-vertical { + height: @navbarHeight; + margin: 0 9px; + border-left: 1px solid @navbarBackground; + border-right: 1px solid @navbarBackgroundHighlight; +} + +// Buttons in navbar +// ------------------------- +.navbar .btn, +.navbar .btn-group { + .navbarVerticalAlign(30px); // Vertically center in navbar +} +.navbar .btn-group .btn, +.navbar .input-prepend .btn, +.navbar .input-append .btn { + margin-top: 0; // then undo the margin here so we don't accidentally double it +} + +// Navbar forms +// ------------------------- +.navbar-form { + margin-bottom: 0; // remove default bottom margin + .clearfix(); + input, + select, + .radio, + .checkbox { + .navbarVerticalAlign(30px); // Vertically center in navbar + } + input, + select, + .btn { + display: inline-block; + margin-bottom: 0; + } + input[type="image"], + input[type="checkbox"], + input[type="radio"] { + margin-top: 3px; + } + .input-append, + .input-prepend { + margin-top: 5px; + white-space: nowrap; // preven two items from separating within a .navbar-form that has .pull-left + input { + margin-top: 0; // remove the margin on top since it's on the parent + } + } +} + +// Navbar search +// ------------------------- +.navbar-search { + position: relative; + float: left; + .navbarVerticalAlign(30px); // Vertically center in navbar + margin-bottom: 0; + .search-query { + margin-bottom: 0; + padding: 4px 14px; + #font > .sans-serif(13px, normal, 1); + .border-radius(15px); // redeclare because of specificity of the type attribute + } +} + + + +// Static navbar +// ------------------------- + +.navbar-static-top { + position: static; + margin-bottom: 0; // remove 18px margin for default navbar + .navbar-inner { + .border-radius(0); + } +} + + + +// Fixed navbar +// ------------------------- + +// Shared (top/bottom) styles +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: @zindexFixedNavbar; + margin-bottom: 0; // remove 18px margin for default navbar +} +.navbar-fixed-top .navbar-inner, +.navbar-static-top .navbar-inner { + border-width: 0 0 1px; +} +.navbar-fixed-bottom .navbar-inner { + border-width: 1px 0 0; +} +.navbar-fixed-top .navbar-inner, +.navbar-fixed-bottom .navbar-inner { + padding-left: 0; + padding-right: 0; + .border-radius(0); +} + +// Reset container width +// Required here as we reset the width earlier on and the grid mixins don't override early enough +.navbar-static-top .container, +.navbar-fixed-top .container, +.navbar-fixed-bottom .container { + #grid > .core > .span(@gridColumns); +} + +// Fixed to top +.navbar-fixed-top { + top: 0; +} +.navbar-fixed-top, +.navbar-static-top { + .navbar-inner { + .box-shadow(~"0 1px 10px rgba(0,0,0,.1)"); + } +} + +// Fixed to bottom +.navbar-fixed-bottom { + bottom: 0; + .navbar-inner { + .box-shadow(~"0 -1px 10px rgba(0,0,0,.1)"); + } +} + + + +// NAVIGATION +// ---------- + +.navbar .nav { + position: relative; + left: 0; + display: block; + float: left; + margin: 0 10px 0 0; +} +.navbar .nav.pull-right { + float: right; // redeclare due to specificity + margin-right: 0; // remove margin on float right nav +} +.navbar .nav > li { + float: left; +} + +// Links +.navbar .nav > li > a { + float: none; + // Vertically center the text given @navbarHeight + padding: ((@navbarHeight - @baseLineHeight) / 2) 15px ((@navbarHeight - @baseLineHeight) / 2); + color: @navbarLinkColor; + text-decoration: none; + text-shadow: 0 1px 0 @navbarBackgroundHighlight; +} +.navbar .nav .dropdown-toggle .caret { + margin-top: 8px; + +} + +// Hover +.navbar .nav > li > a:focus, +.navbar .nav > li > a:hover { + background-color: @navbarLinkBackgroundHover; // "transparent" is default to differentiate :hover from .active + color: @navbarLinkColorHover; + text-decoration: none; +} + +// Active nav items +.navbar .nav > .active > a, +.navbar .nav > .active > a:hover, +.navbar .nav > .active > a:focus { + color: @navbarLinkColorActive; + text-decoration: none; + background-color: @navbarLinkBackgroundActive; + .box-shadow(inset 0 3px 8px rgba(0,0,0,.125)); +} + +// Navbar button for toggling navbar items in responsive layouts +// These definitions need to come after '.navbar .btn' +.navbar .btn-navbar { + display: none; + float: right; + padding: 7px 10px; + margin-left: 5px; + margin-right: 5px; + .buttonBackground(darken(@navbarBackgroundHighlight, 5%), darken(@navbarBackground, 5%)); + .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075)"); +} +.navbar .btn-navbar .icon-bar { + display: block; + width: 18px; + height: 2px; + background-color: #f5f5f5; + .border-radius(1px); + .box-shadow(0 1px 0 rgba(0,0,0,.25)); +} +.btn-navbar .icon-bar + .icon-bar { + margin-top: 3px; +} + + + +// Dropdown menus +// -------------- + +// Menu position and menu carets +.navbar .nav > li > .dropdown-menu { + &:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-bottom-color: @dropdownBorder; + position: absolute; + top: -7px; + left: 9px; + } + &:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid @dropdownBackground; + position: absolute; + top: -6px; + left: 10px; + } +} +// Menu position and menu caret support for dropups via extra dropup class +.navbar-fixed-bottom .nav > li > .dropdown-menu { + &:before { + border-top: 7px solid #ccc; + border-top-color: @dropdownBorder; + border-bottom: 0; + bottom: -7px; + top: auto; + } + &:after { + border-top: 6px solid @dropdownBackground; + border-bottom: 0; + bottom: -6px; + top: auto; + } +} + +// Caret should match text color on hover +.navbar .nav li.dropdown > a:hover .caret { + border-top-color: @navbarLinkColorActive; + border-bottom-color: @navbarLinkColorActive; +} + +// Remove background color from open dropdown +.navbar .nav li.dropdown.open > .dropdown-toggle, +.navbar .nav li.dropdown.active > .dropdown-toggle, +.navbar .nav li.dropdown.open.active > .dropdown-toggle { + background-color: @navbarLinkBackgroundActive; + color: @navbarLinkColorActive; +} +.navbar .nav li.dropdown > .dropdown-toggle .caret { + border-top-color: @navbarLinkColor; + border-bottom-color: @navbarLinkColor; +} +.navbar .nav li.dropdown.open > .dropdown-toggle .caret, +.navbar .nav li.dropdown.active > .dropdown-toggle .caret, +.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret { + border-top-color: @navbarLinkColorActive; + border-bottom-color: @navbarLinkColorActive; +} + +// Right aligned menus need alt position +.navbar .pull-right > li > .dropdown-menu, +.navbar .nav > li > .dropdown-menu.pull-right { + left: auto; + right: 0; + &:before { + left: auto; + right: 12px; + } + &:after { + left: auto; + right: 13px; + } + .dropdown-menu { + left: auto; + right: 100%; + margin-left: 0; + margin-right: -1px; + .border-radius(6px 0 6px 6px); + } +} + + +// Inverted navbar +// ------------------------- + +.navbar-inverse { + + .navbar-inner { + #gradient > .vertical(@navbarInverseBackgroundHighlight, @navbarInverseBackground); + border-color: @navbarInverseBorder; + } + + .brand, + .nav > li > a { + color: @navbarInverseLinkColor; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + &:hover { + color: @navbarInverseLinkColorHover; + } + } + + .brand { + color: @navbarInverseBrandColor; + } + + .navbar-text { + color: @navbarInverseText; + } + + .nav > li > a:focus, + .nav > li > a:hover { + background-color: @navbarInverseLinkBackgroundHover; + color: @navbarInverseLinkColorHover; + } + + .nav .active > a, + .nav .active > a:hover, + .nav .active > a:focus { + color: @navbarInverseLinkColorActive; + background-color: @navbarInverseLinkBackgroundActive; + } + + // Inline text links + .navbar-link { + color: @navbarInverseLinkColor; + &:hover { + color: @navbarInverseLinkColorHover; + } + } + + // Dividers in navbar + .divider-vertical { + border-left-color: @navbarInverseBackground; + border-right-color: @navbarInverseBackgroundHighlight; + } + + // Dropdowns + .nav li.dropdown.open > .dropdown-toggle, + .nav li.dropdown.active > .dropdown-toggle, + .nav li.dropdown.open.active > .dropdown-toggle { + background-color: @navbarInverseLinkBackgroundActive; + color: @navbarInverseLinkColorActive; + } + .nav li.dropdown > a:hover .caret { + border-top-color: @navbarInverseLinkColorActive; + border-bottom-color: @navbarInverseLinkColorActive; + } + .nav li.dropdown > .dropdown-toggle .caret { + border-top-color: @navbarInverseLinkColor; + border-bottom-color: @navbarInverseLinkColor; + } + .nav li.dropdown.open > .dropdown-toggle .caret, + .nav li.dropdown.active > .dropdown-toggle .caret, + .nav li.dropdown.open.active > .dropdown-toggle .caret { + border-top-color: @navbarInverseLinkColorActive; + border-bottom-color: @navbarInverseLinkColorActive; + } + + // Navbar search + .navbar-search { + .search-query { + color: @white; + background-color: @navbarInverseSearchBackground; + border-color: @navbarInverseSearchBorder; + .box-shadow(~"inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15)"); + .transition(none); + .placeholder(@navbarInverseSearchPlaceholderColor); + + // Focus states (we use .focused since IE7-8 and down doesn't support :focus) + &:focus, + &.focused { + padding: 5px 15px; + color: @grayDark; + text-shadow: 0 1px 0 @white; + background-color: @navbarInverseSearchBackgroundFocus; + border: 0; + .box-shadow(0 0 3px rgba(0,0,0,.15)); + outline: 0; + } + } + } + + // Navbar collapse button + .btn-navbar { + .buttonBackground(darken(@navbarInverseBackgroundHighlight, 5%), darken(@navbarInverseBackground, 5%)); + } + +} diff --git a/src/main/less/bootstrap/navs.less b/src/main/less/bootstrap/navs.less @@ -0,0 +1,391 @@ +// +// Navs +// -------------------------------------------------- + + +// BASE CLASS +// ---------- + +.nav { + margin-left: 0; + margin-bottom: @baseLineHeight; + list-style: none; +} + +// Make links block level +.nav > li > a { + display: block; +} +.nav > li > a:hover { + text-decoration: none; + background-color: @grayLighter; +} + +// Prevent IE8 from misplacing imgs +// See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989 +.nav > li > a > img { + max-width: none; +} + +// Redeclare pull classes because of specifity +.nav > .pull-right { + float: right; +} + +// Nav headers (for dropdowns and lists) +.nav-header { + display: block; + padding: 3px 15px; + font-size: 11px; + font-weight: bold; + line-height: @baseLineHeight; + color: @grayLight; + text-shadow: 0 1px 0 rgba(255,255,255,.5); + text-transform: uppercase; +} +// Space them out when they follow another list item (link) +.nav li + .nav-header { + margin-top: 9px; +} + + + +// NAV LIST +// -------- + +.nav-list { + padding-left: 15px; + padding-right: 15px; + margin-bottom: 0; +} +.nav-list > li > a, +.nav-list .nav-header { + margin-left: -15px; + margin-right: -15px; + text-shadow: 0 1px 0 rgba(255,255,255,.5); +} +.nav-list > li > a { + padding: 3px 15px; +} +.nav-list > .active > a, +.nav-list > .active > a:hover { + color: @white; + text-shadow: 0 -1px 0 rgba(0,0,0,.2); + background-color: @linkColor; +} +.nav-list [class^="icon-"], +.nav-list [class*=" icon-"] { + margin-right: 2px; +} +// Dividers (basically an hr) within the dropdown +.nav-list .divider { + .nav-divider(); +} + + + +// TABS AND PILLS +// ------------- + +// Common styles +.nav-tabs, +.nav-pills { + .clearfix(); +} +.nav-tabs > li, +.nav-pills > li { + float: left; +} +.nav-tabs > li > a, +.nav-pills > li > a { + padding-right: 12px; + padding-left: 12px; + margin-right: 2px; + line-height: 14px; // keeps the overall height an even number +} + +// TABS +// ---- + +// Give the tabs something to sit on +.nav-tabs { + border-bottom: 1px solid #ddd; +} +// Make the list-items overlay the bottom border +.nav-tabs > li { + margin-bottom: -1px; +} +// Actual tabs (as links) +.nav-tabs > li > a { + padding-top: 8px; + padding-bottom: 8px; + line-height: @baseLineHeight; + border: 1px solid transparent; + .border-radius(4px 4px 0 0); + &:hover { + border-color: @grayLighter @grayLighter #ddd; + } +} +// Active state, and it's :hover to override normal :hover +.nav-tabs > .active > a, +.nav-tabs > .active > a:hover { + color: @gray; + background-color: @bodyBackground; + border: 1px solid #ddd; + border-bottom-color: transparent; + cursor: default; +} + + +// PILLS +// ----- + +// Links rendered as pills +.nav-pills > li > a { + padding-top: 8px; + padding-bottom: 8px; + margin-top: 2px; + margin-bottom: 2px; + .border-radius(5px); +} + +// Active state +.nav-pills > .active > a, +.nav-pills > .active > a:hover { + color: @white; + background-color: @linkColor; +} + + + +// STACKED NAV +// ----------- + +// Stacked tabs and pills +.nav-stacked > li { + float: none; +} +.nav-stacked > li > a { + margin-right: 0; // no need for the gap between nav items +} + +// Tabs +.nav-tabs.nav-stacked { + border-bottom: 0; +} +.nav-tabs.nav-stacked > li > a { + border: 1px solid #ddd; + .border-radius(0); +} +.nav-tabs.nav-stacked > li:first-child > a { + .border-top-radius(4px); +} +.nav-tabs.nav-stacked > li:last-child > a { + .border-bottom-radius(4px); +} +.nav-tabs.nav-stacked > li > a:hover { + border-color: #ddd; + z-index: 2; +} + +// Pills +.nav-pills.nav-stacked > li > a { + margin-bottom: 3px; +} +.nav-pills.nav-stacked > li:last-child > a { + margin-bottom: 1px; // decrease margin to match sizing of stacked tabs +} + + + +// DROPDOWNS +// --------- + +.nav-tabs .dropdown-menu { + .border-radius(0 0 6px 6px); // remove the top rounded corners here since there is a hard edge above the menu +} +.nav-pills .dropdown-menu { + .border-radius(6px); // make rounded corners match the pills +} + +// Default dropdown links +// ------------------------- +// Make carets use linkColor to start +.nav .dropdown-toggle .caret { + border-top-color: @linkColor; + border-bottom-color: @linkColor; + margin-top: 6px; +} +.nav .dropdown-toggle:hover .caret { + border-top-color: @linkColorHover; + border-bottom-color: @linkColorHover; +} +/* move down carets for tabs */ +.nav-tabs .dropdown-toggle .caret { + margin-top: 8px; +} + +// Active dropdown links +// ------------------------- +.nav .active .dropdown-toggle .caret { + border-top-color: #fff; + border-bottom-color: #fff; +} +.nav-tabs .active .dropdown-toggle .caret { + border-top-color: @gray; + border-bottom-color: @gray; +} + +// Active:hover dropdown links +// ------------------------- +.nav > .dropdown.active > a:hover { + cursor: pointer; +} + +// Open dropdowns +// ------------------------- +.nav-tabs .open .dropdown-toggle, +.nav-pills .open .dropdown-toggle, +.nav > li.dropdown.open.active > a:hover { + color: @white; + background-color: @grayLight; + border-color: @grayLight; +} +.nav li.dropdown.open .caret, +.nav li.dropdown.open.active .caret, +.nav li.dropdown.open a:hover .caret { + border-top-color: @white; + border-bottom-color: @white; + .opacity(100); +} + +// Dropdowns in stacked tabs +.tabs-stacked .open > a:hover { + border-color: @grayLight; +} + + + +// TABBABLE +// -------- + + +// COMMON STYLES +// ------------- + +// Clear any floats +.tabbable { + .clearfix(); +} +.tab-content { + overflow: auto; // prevent content from running below tabs +} + +// Remove border on bottom, left, right +.tabs-below > .nav-tabs, +.tabs-right > .nav-tabs, +.tabs-left > .nav-tabs { + border-bottom: 0; +} + +// Show/hide tabbable areas +.tab-content > .tab-pane, +.pill-content > .pill-pane { + display: none; +} +.tab-content > .active, +.pill-content > .active { + display: block; +} + + +// BOTTOM +// ------ + +.tabs-below > .nav-tabs { + border-top: 1px solid #ddd; +} +.tabs-below > .nav-tabs > li { + margin-top: -1px; + margin-bottom: 0; +} +.tabs-below > .nav-tabs > li > a { + .border-radius(0 0 4px 4px); + &:hover { + border-bottom-color: transparent; + border-top-color: #ddd; + } +} +.tabs-below > .nav-tabs > .active > a, +.tabs-below > .nav-tabs > .active > a:hover { + border-color: transparent #ddd #ddd #ddd; +} + +// LEFT & RIGHT +// ------------ + +// Common styles +.tabs-left > .nav-tabs > li, +.tabs-right > .nav-tabs > li { + float: none; +} +.tabs-left > .nav-tabs > li > a, +.tabs-right > .nav-tabs > li > a { + min-width: 74px; + margin-right: 0; + margin-bottom: 3px; +} + +// Tabs on the left +.tabs-left > .nav-tabs { + float: left; + margin-right: 19px; + border-right: 1px solid #ddd; +} +.tabs-left > .nav-tabs > li > a { + margin-right: -1px; + .border-radius(4px 0 0 4px); +} +.tabs-left > .nav-tabs > li > a:hover { + border-color: @grayLighter #ddd @grayLighter @grayLighter; +} +.tabs-left > .nav-tabs .active > a, +.tabs-left > .nav-tabs .active > a:hover { + border-color: #ddd transparent #ddd #ddd; + *border-right-color: @white; +} + +// Tabs on the right +.tabs-right > .nav-tabs { + float: right; + margin-left: 19px; + border-left: 1px solid #ddd; +} +.tabs-right > .nav-tabs > li > a { + margin-left: -1px; + .border-radius(0 4px 4px 0); +} +.tabs-right > .nav-tabs > li > a:hover { + border-color: @grayLighter @grayLighter @grayLighter #ddd; +} +.tabs-right > .nav-tabs .active > a, +.tabs-right > .nav-tabs .active > a:hover { + border-color: #ddd #ddd #ddd transparent; + *border-left-color: @white; +} + + + +// DISABLED STATES +// --------------- + +// Gray out text +.nav > .disabled > a { + color: @grayLight; +} +// Nuke hover effects +.nav > .disabled > a:hover { + text-decoration: none; + background-color: transparent; + cursor: default; +} diff --git a/src/main/less/bootstrap/pager.less b/src/main/less/bootstrap/pager.less @@ -0,0 +1,41 @@ +// +// Pager pagination +// -------------------------------------------------- + + +.pager { + margin: @baseLineHeight 0; + list-style: none; + text-align: center; + .clearfix(); +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + .border-radius(15px); +} +.pager li > a:hover { + text-decoration: none; + background-color: #f5f5f5; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > span { + color: @grayLight; + background-color: #fff; + cursor: default; +} +\ No newline at end of file diff --git a/src/main/less/bootstrap/pagination.less b/src/main/less/bootstrap/pagination.less @@ -0,0 +1,121 @@ +// +// Pagination (multiple pages) +// -------------------------------------------------- + +// Space out pagination from surrounding content +.pagination { + margin: @baseLineHeight 0; +} + +.pagination ul { + // Allow for text-based alignment + display: inline-block; + .ie7-inline-block(); + // Reset default ul styles + margin-left: 0; + margin-bottom: 0; + // Visuals + .border-radius(@baseBorderRadius); + .box-shadow(0 1px 2px rgba(0,0,0,.05)); +} +.pagination ul > li { + display: inline; // Remove list-style and block-level defaults +} +.pagination ul > li > a, +.pagination ul > li > span { + float: left; // Collapse white-space + padding: 4px 12px; + line-height: @baseLineHeight; + text-decoration: none; + background-color: @paginationBackground; + border: 1px solid @paginationBorder; + border-left-width: 0; +} +.pagination ul > li > a:hover, +.pagination ul > .active > a, +.pagination ul > .active > span { + background-color: @paginationActiveBackground; +} +.pagination ul > .active > a, +.pagination ul > .active > span { + color: @grayLight; + cursor: default; +} +.pagination ul > .disabled > span, +.pagination ul > .disabled > a, +.pagination ul > .disabled > a:hover { + color: @grayLight; + background-color: transparent; + cursor: default; +} +.pagination ul > li:first-child > a, +.pagination ul > li:first-child > span { + border-left-width: 1px; + .border-left-radius(@baseBorderRadius); +} +.pagination ul > li:last-child > a, +.pagination ul > li:last-child > span { + .border-right-radius(@baseBorderRadius); +} + + +// Alignment +// -------------------------------------------------- + +.pagination-centered { + text-align: center; +} +.pagination-right { + text-align: right; +} + + +// Sizing +// -------------------------------------------------- + +// Large +.pagination-large { + ul > li > a, + ul > li > span { + padding: @paddingLarge; + font-size: @fontSizeLarge; + } + ul > li:first-child > a, + ul > li:first-child > span { + .border-left-radius(@borderRadiusLarge); + } + ul > li:last-child > a, + ul > li:last-child > span { + .border-right-radius(@borderRadiusLarge); + } +} + +// Small and mini +.pagination-mini, +.pagination-small { + ul > li:first-child > a, + ul > li:first-child > span { + .border-left-radius(@borderRadiusSmall); + } + ul > li:last-child > a, + ul > li:last-child > span { + .border-right-radius(@borderRadiusSmall); + } +} + +// Small +.pagination-small { + ul > li > a, + ul > li > span { + padding: @paddingSmall; + font-size: @fontSizeSmall; + } +} +// Mini +.pagination-mini { + ul > li > a, + ul > li > span { + padding: @paddingMini; + font-size: @fontSizeMini; + } +} diff --git a/src/main/less/bootstrap/popovers.less b/src/main/less/bootstrap/popovers.less @@ -0,0 +1,129 @@ +// +// Popovers +// -------------------------------------------------- + + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: @zindexPopover; + display: none; + width: 236px; + padding: 1px; + text-align: left; // Reset given new insertion method + background-color: @popoverBackground; + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0,0,0,.2); + .border-radius(6px); + .box-shadow(0 5px 10px rgba(0,0,0,.2)); + + // Overrides for proper insertion + white-space: normal; + + // Offset the popover to account for the popover arrow + &.top { margin-top: -10px; } + &.right { margin-left: 10px; } + &.bottom { margin-top: 10px; } + &.left { margin-left: -10px; } +} + +.popover-title { + margin: 0; // reset heading margin + padding: 8px 14px; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: @popoverTitleBackground; + border-bottom: 1px solid darken(@popoverTitleBackground, 5%); + .border-radius(5px 5px 0 0); +} + +.popover-content { + padding: 9px 14px; +} + +// Arrows +// +// .arrow is outer, .arrow:after is inner + +.popover .arrow, +.popover .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover .arrow { + border-width: @popoverArrowOuterWidth; +} +.popover .arrow:after { + border-width: @popoverArrowWidth; + content: ""; +} + +.popover { + &.top .arrow { + left: 50%; + margin-left: -@popoverArrowOuterWidth; + border-bottom-width: 0; + border-top-color: #999; // IE8 fallback + border-top-color: @popoverArrowOuterColor; + bottom: -@popoverArrowOuterWidth; + &:after { + bottom: 1px; + margin-left: -@popoverArrowWidth; + border-bottom-width: 0; + border-top-color: @popoverArrowColor; + } + } + &.right .arrow { + top: 50%; + left: -@popoverArrowOuterWidth; + margin-top: -@popoverArrowOuterWidth; + border-left-width: 0; + border-right-color: #999; // IE8 fallback + border-right-color: @popoverArrowOuterColor; + &:after { + left: 1px; + bottom: -@popoverArrowWidth; + border-left-width: 0; + border-right-color: @popoverArrowColor; + } + } + &.bottom .arrow { + left: 50%; + margin-left: -@popoverArrowOuterWidth; + border-top-width: 0; + border-bottom-color: #999; // IE8 fallback + border-bottom-color: @popoverArrowOuterColor; + top: -@popoverArrowOuterWidth; + &:after { + top: 1px; + margin-left: -@popoverArrowWidth; + border-top-width: 0; + border-bottom-color: @popoverArrowColor; + } + } + + &.left .arrow { + top: 50%; + right: -@popoverArrowOuterWidth; + margin-top: -@popoverArrowOuterWidth; + border-right-width: 0; + border-left-color: #999; // IE8 fallback + border-left-color: @popoverArrowOuterColor; + &:after { + right: 1px; + border-right-width: 0; + border-left-color: @popoverArrowColor; + bottom: -@popoverArrowWidth; + } + } + +} diff --git a/src/main/less/bootstrap/progress-bars.less b/src/main/less/bootstrap/progress-bars.less @@ -0,0 +1,122 @@ +// +// Progress bars +// -------------------------------------------------- + + +// ANIMATIONS +// ---------- + +// Webkit +@-webkit-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// Firefox +@-moz-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// IE9 +@-ms-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// Opera +@-o-keyframes progress-bar-stripes { + from { background-position: 0 0; } + to { background-position: 40px 0; } +} + +// Spec +@keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + + + +// THE BARS +// -------- + +// Outer container +.progress { + overflow: hidden; + height: @baseLineHeight; + margin-bottom: @baseLineHeight; + #gradient > .vertical(#f5f5f5, #f9f9f9); + .box-shadow(inset 0 1px 2px rgba(0,0,0,.1)); + .border-radius(@baseBorderRadius); +} + +// Bar of progress +.progress .bar { + width: 0%; + height: 100%; + color: @white; + float: left; + font-size: 12px; + text-align: center; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + #gradient > .vertical(#149bdf, #0480be); + .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15)); + .box-sizing(border-box); + .transition(width .6s ease); +} +.progress .bar + .bar { + .box-shadow(~"inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15)"); +} + +// Striped bars +.progress-striped .bar { + #gradient > .striped(#149bdf); + .background-size(40px 40px); +} + +// Call animation for the active one +.progress.active .bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -moz-animation: progress-bar-stripes 2s linear infinite; + -ms-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} + + + +// COLORS +// ------ + +// Danger (red) +.progress-danger .bar, .progress .bar-danger { + #gradient > .vertical(#ee5f5b, #c43c35); +} +.progress-danger.progress-striped .bar, .progress-striped .bar-danger { + #gradient > .striped(#ee5f5b); +} + +// Success (green) +.progress-success .bar, .progress .bar-success { + #gradient > .vertical(#62c462, #57a957); +} +.progress-success.progress-striped .bar, .progress-striped .bar-success { + #gradient > .striped(#62c462); +} + +// Info (teal) +.progress-info .bar, .progress .bar-info { + #gradient > .vertical(#5bc0de, #339bb9); +} +.progress-info.progress-striped .bar, .progress-striped .bar-info { + #gradient > .striped(#5bc0de); +} + +// Warning (orange) +.progress-warning .bar, .progress .bar-warning { + #gradient > .vertical(lighten(@orange, 15%), @orange); +} +.progress-warning.progress-striped .bar, .progress-striped .bar-warning { + #gradient > .striped(lighten(@orange, 15%)); +} diff --git a/src/main/less/bootstrap/reset.less b/src/main/less/bootstrap/reset.less @@ -0,0 +1,216 @@ +// +// Reset CSS +// Adapted from http://github.com/necolas/normalize.css +// -------------------------------------------------- + + +// Display in IE6-9 and FF3 +// ------------------------- + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section { + display: block; +} + +// Display block in IE6-9 and FF3 +// ------------------------- + +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +// Prevents modern browsers from displaying 'audio' without controls +// ------------------------- + +audio:not([controls]) { + display: none; +} + +// Base settings +// ------------------------- + +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +// Focus states +a:focus { + .tab-focus(); +} +// Hover & Active +a:hover, +a:active { + outline: 0; +} + +// Prevents sub and sup affecting line-height in all browsers +// ------------------------- + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} + +// Img border in a's and image quality +// ------------------------- + +img { + /* Responsive images (ensure images don't scale beyond their parents) */ + max-width: 100%; /* Part 1: Set a maxium relative to the parent */ + width: auto\9; /* IE7-8 need help adjusting responsive images */ + height: auto; /* Part 2: Scale the height according to the width, otherwise you get stretching */ + + vertical-align: middle; + border: 0; + -ms-interpolation-mode: bicubic; +} + +// Prevent max-width from affecting Google Maps +#map_canvas img, +.google-maps img { + max-width: none; +} + +// Forms +// ------------------------- + +// Font size in all browsers, margin changes, misc consistency +button, +input, +select, +textarea { + margin: 0; + font-size: 100%; + vertical-align: middle; +} +button, +input { + *overflow: visible; // Inner spacing ie IE6/7 + line-height: normal; // FF3/4 have !important on line-height in UA stylesheet +} +button::-moz-focus-inner, +input::-moz-focus-inner { // Inner padding and border oddities in FF3/4 + padding: 0; + border: 0; +} +button, +html input[type="button"], // Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; // Corrects inability to style clickable `input` types in iOS. + cursor: pointer; // Improves usability and consistency of cursor style between image-type `input` and others. +} +label, +select, +button, +input[type="button"], +input[type="reset"], +input[type="submit"], +input[type="radio"], +input[type="checkbox"] { + cursor: pointer; // Improves usability and consistency of cursor style between image-type `input` and others. +} +input[type="search"] { // Appearance in Safari/Chrome + .box-sizing(content-box); + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-decoration, +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5 +} +textarea { + overflow: auto; // Remove vertical scrollbar in IE6-9 + vertical-align: top; // Readability and alignment cross-browser +} + + +// Printing +// ------------------------- +// Source: https://github.com/h5bp/html5-boilerplate/blob/master/css/main.css + +@media print { + + * { + text-shadow: none !important; + color: #000 !important; // Black prints faster: h5bp.com/s + background: transparent !important; + box-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " (" attr(href) ")"; + } + + abbr[title]:after { + content: " (" attr(title) ")"; + } + + // Don't show links for images, or javascript/internal links + .ir a:after, + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + thead { + display: table-header-group; // h5bp.com/t + } + + tr, + img { + page-break-inside: avoid; + } + + img { + max-width: 100% !important; + } + + @page { + margin: 0.5cm; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } +} +\ No newline at end of file diff --git a/src/main/less/bootstrap/responsive-1200px-min.less b/src/main/less/bootstrap/responsive-1200px-min.less @@ -0,0 +1,28 @@ +// +// Responsive: Large desktop and up +// -------------------------------------------------- + + +@media (min-width: 1200px) { + + // Fixed grid + #grid > .core(@gridColumnWidth1200, @gridGutterWidth1200); + + // Fluid grid + #grid > .fluid(@fluidGridColumnWidth1200, @fluidGridGutterWidth1200); + + // Input grid + #grid > .input(@gridColumnWidth1200, @gridGutterWidth1200); + + // Thumbnails + .thumbnails { + margin-left: -@gridGutterWidth1200; + } + .thumbnails > li { + margin-left: @gridGutterWidth1200; + } + .row-fluid .thumbnails { + margin-left: 0; + } + +} diff --git a/src/main/less/bootstrap/responsive-767px-max.less b/src/main/less/bootstrap/responsive-767px-max.less @@ -0,0 +1,193 @@ +// +// Responsive: Landscape phone to desktop/tablet +// -------------------------------------------------- + + +@media (max-width: 767px) { + + // Padding to set content in a bit + body { + padding-left: 20px; + padding-right: 20px; + } + // Negative indent the now static "fixed" navbar + .navbar-fixed-top, + .navbar-fixed-bottom, + .navbar-static-top { + margin-left: -20px; + margin-right: -20px; + } + // Remove padding on container given explicit padding set on body + .container-fluid { + padding: 0; + } + + // TYPOGRAPHY + // ---------- + // Reset horizontal dl + .dl-horizontal { + dt { + float: none; + clear: none; + width: auto; + text-align: left; + } + dd { + margin-left: 0; + } + } + + // GRID & CONTAINERS + // ----------------- + // Remove width from containers + .container { + width: auto; + } + // Fluid rows + .row-fluid { + width: 100%; + } + // Undo negative margin on rows and thumbnails + .row, + .thumbnails { + margin-left: 0; + } + .thumbnails > li { + float: none; + margin-left: 0; // Reset the default margin for all li elements when no .span* classes are present + } + // Make all grid-sized elements block level again + [class*="span"], + .uneditable-input[class*="span"], // Makes uneditable inputs full-width when using grid sizing + .row-fluid [class*="span"] { + float: none; + display: block; + width: 100%; + margin-left: 0; + .box-sizing(border-box); + } + .span12, + .row-fluid .span12 { + width: 100%; + .box-sizing(border-box); + } + .row-fluid [class*="offset"]:first-child { + margin-left: 0; + } + + // FORM FIELDS + // ----------- + // Make span* classes full width + .input-large, + .input-xlarge, + .input-xxlarge, + input[class*="span"], + select[class*="span"], + textarea[class*="span"], + .uneditable-input { + .input-block-level(); + } + // But don't let it screw up prepend/append inputs + .input-prepend input, + .input-append input, + .input-prepend input[class*="span"], + .input-append input[class*="span"] { + display: inline-block; // redeclare so they don't wrap to new lines + width: auto; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 0; + } + + // Modals + .modal { + position: fixed; + top: 20px; + left: 20px; + right: 20px; + width: auto; + margin: 0; + &.fade { top: -100px; } + &.fade.in { top: 20px; } + } + +} + + + +// UP TO LANDSCAPE PHONE +// --------------------- + +@media (max-width: 480px) { + + // Smooth out the collapsing/expanding nav + .nav-collapse { + -webkit-transform: translate3d(0, 0, 0); // activate the GPU + } + + // Block level the page header small tag for readability + .page-header h1 small { + display: block; + line-height: @baseLineHeight; + } + + // Update checkboxes for iOS + input[type="checkbox"], + input[type="radio"] { + border: 1px solid #ccc; + } + + // Remove the horizontal form styles + .form-horizontal { + .control-label { + float: none; + width: auto; + padding-top: 0; + text-align: left; + } + // Move over all input controls and content + .controls { + margin-left: 0; + } + // Move the options list down to align with labels + .control-list { + padding-top: 0; // has to be padding because margin collaspes + } + // Move over buttons in .form-actions to align with .controls + .form-actions { + padding-left: 10px; + padding-right: 10px; + } + } + + // Medias + // Reset float and spacing to stack + .media .pull-left, + .media .pull-right { + float: none; + display: block; + margin-bottom: 10px; + } + // Remove side margins since we stack instead of indent + .media-object { + margin-right: 0; + margin-left: 0; + } + + // Modals + .modal { + top: 10px; + left: 10px; + right: 10px; + } + .modal-header .close { + padding: 10px; + margin: -10px; + } + + // Carousel + .carousel-caption { + position: static; + } + +} diff --git a/src/main/less/bootstrap/responsive-768px-979px.less b/src/main/less/bootstrap/responsive-768px-979px.less @@ -0,0 +1,19 @@ +// +// Responsive: Tablet to desktop +// -------------------------------------------------- + + +@media (min-width: 768px) and (max-width: 979px) { + + // Fixed grid + #grid > .core(@gridColumnWidth768, @gridGutterWidth768); + + // Fluid grid + #grid > .fluid(@fluidGridColumnWidth768, @fluidGridGutterWidth768); + + // Input grid + #grid > .input(@gridColumnWidth768, @gridGutterWidth768); + + // No need to reset .thumbnails here since it's the same @gridGutterWidth + +} diff --git a/src/main/less/bootstrap/responsive-navbar.less b/src/main/less/bootstrap/responsive-navbar.less @@ -0,0 +1,185 @@ +// +// Responsive: Navbar +// -------------------------------------------------- + + +// TABLETS AND BELOW +// ----------------- +@media (max-width: @navbarCollapseWidth) { + + // UNFIX THE TOPBAR + // ---------------- + // Remove any padding from the body + body { + padding-top: 0; + } + // Unfix the navbars + .navbar-fixed-top, + .navbar-fixed-bottom { + position: static; + } + .navbar-fixed-top { + margin-bottom: @baseLineHeight; + } + .navbar-fixed-bottom { + margin-top: @baseLineHeight; + } + .navbar-fixed-top .navbar-inner, + .navbar-fixed-bottom .navbar-inner { + padding: 5px; + } + .navbar .container { + width: auto; + padding: 0; + } + // Account for brand name + .navbar .brand { + padding-left: 10px; + padding-right: 10px; + margin: 0 0 0 -5px; + } + + // COLLAPSIBLE NAVBAR + // ------------------ + // Nav collapse clears brand + .nav-collapse { + clear: both; + } + // Block-level the nav + .nav-collapse .nav { + float: none; + margin: 0 0 (@baseLineHeight / 2); + } + .nav-collapse .nav > li { + float: none; + } + .nav-collapse .nav > li > a { + margin-bottom: 2px; + } + .nav-collapse .nav > .divider-vertical { + display: none; + } + .nav-collapse .nav .nav-header { + color: @navbarText; + text-shadow: none; + } + // Nav and dropdown links in navbar + .nav-collapse .nav > li > a, + .nav-collapse .dropdown-menu a { + padding: 9px 15px; + font-weight: bold; + color: @navbarLinkColor; + .border-radius(3px); + } + // Buttons + .nav-collapse .btn { + padding: 4px 10px 4px; + font-weight: normal; + .border-radius(@baseBorderRadius); + } + .nav-collapse .dropdown-menu li + li a { + margin-bottom: 2px; + } + .nav-collapse .nav > li > a:hover, + .nav-collapse .dropdown-menu a:hover { + background-color: @navbarBackground; + } + .navbar-inverse .nav-collapse .nav > li > a, + .navbar-inverse .nav-collapse .dropdown-menu a { + color: @navbarInverseLinkColor; + } + .navbar-inverse .nav-collapse .nav > li > a:hover, + .navbar-inverse .nav-collapse .dropdown-menu a:hover { + background-color: @navbarInverseBackground; + } + // Buttons in the navbar + .nav-collapse.in .btn-group { + margin-top: 5px; + padding: 0; + } + // Dropdowns in the navbar + .nav-collapse .dropdown-menu { + position: static; + top: auto; + left: auto; + float: none; + display: none; + max-width: none; + margin: 0 15px; + padding: 0; + background-color: transparent; + border: none; + .border-radius(0); + .box-shadow(none); + } + .nav-collapse .open > .dropdown-menu { + display: block; + } + + .nav-collapse .dropdown-menu:before, + .nav-collapse .dropdown-menu:after { + display: none; + } + .nav-collapse .dropdown-menu .divider { + display: none; + } + .nav-collapse .nav > li > .dropdown-menu { + &:before, + &:after { + display: none; + } + } + // Forms in navbar + .nav-collapse .navbar-form, + .nav-collapse .navbar-search { + float: none; + padding: (@baseLineHeight / 2) 15px; + margin: (@baseLineHeight / 2) 0; + border-top: 1px solid @navbarBackground; + border-bottom: 1px solid @navbarBackground; + .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1)"); + } + .navbar-inverse .nav-collapse .navbar-form, + .navbar-inverse .nav-collapse .navbar-search { + border-top-color: @navbarInverseBackground; + border-bottom-color: @navbarInverseBackground; + } + // Pull right (secondary) nav content + .navbar .nav-collapse .nav.pull-right { + float: none; + margin-left: 0; + } + // Hide everything in the navbar save .brand and toggle button */ + .nav-collapse, + .nav-collapse.collapse { + overflow: hidden; + height: 0; + } + // Navbar button + .navbar .btn-navbar { + display: block; + } + + // STATIC NAVBAR + // ------------- + .navbar-static .navbar-inner { + padding-left: 10px; + padding-right: 10px; + } + + +} + + +// DEFAULT DESKTOP +// --------------- + +@media (min-width: @navbarCollapseDesktopWidth) { + + // Required to make the collapsing navbar work on regular desktops + .nav-collapse.collapse { + height: auto !important; + overflow: visible !important; + } + +} diff --git a/src/main/less/bootstrap/responsive-utilities.less b/src/main/less/bootstrap/responsive-utilities.less @@ -0,0 +1,43 @@ +// +// Responsive: Utility classes +// -------------------------------------------------- + + +// Hide from screenreaders and browsers +// Credit: HTML5 Boilerplate +.hidden { + display: none; + visibility: hidden; +} + +// Visibility utilities + +// For desktops +.visible-phone { display: none !important; } +.visible-tablet { display: none !important; } +.hidden-phone { } +.hidden-tablet { } +.hidden-desktop { display: none !important; } +.visible-desktop { display: inherit !important; } + +// Tablets & small desktops only +@media (min-width: 768px) and (max-width: 979px) { + // Hide everything else + .hidden-desktop { display: inherit !important; } + .visible-desktop { display: none !important ; } + // Show + .visible-tablet { display: inherit !important; } + // Hide + .hidden-tablet { display: none !important; } +} + +// Phones only +@media (max-width: 767px) { + // Hide everything else + .hidden-desktop { display: inherit !important; } + .visible-desktop { display: none !important; } + // Show + .visible-phone { display: inherit !important; } // Use inherit to restore previous behavior + // Hide + .hidden-phone { display: none !important; } +} diff --git a/src/main/less/bootstrap/responsive.less b/src/main/less/bootstrap/responsive.less @@ -0,0 +1,57 @@ +/*! + * Bootstrap Responsive v2.2.2 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + + +// Responsive.less +// For phone and tablet devices +// ------------------------------------------------------------- + + +// IE10 Metro responsive +// Required for Windows 8 Metro split-screen snapping with IE10 +// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/ + +@-ms-viewport{ + width: device-width; +} + + +// REPEAT VARIABLES & MIXINS +// ------------------------- +// Required since we compile the responsive stuff separately + +@import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "mixins.less"; + + +// RESPONSIVE CLASSES +// ------------------ + +@import "responsive-utilities.less"; + + +// MEDIA QUERIES +// ------------------ + +// Large desktops +@import "responsive-1200px-min.less"; + +// Tablets to regular desktops +@import "responsive-768px-979px.less"; + +// Phones to portrait tablets and narrow desktops +@import "responsive-767px-max.less"; + + +// RESPONSIVE NAVBAR +// ------------------ + +// From 979px and below, show a button to toggle navbar contents +@import "responsive-navbar.less"; diff --git a/src/main/less/bootstrap/scaffolding.less b/src/main/less/bootstrap/scaffolding.less @@ -0,0 +1,52 @@ +// +// Scaffolding +// -------------------------------------------------- + + +// Body reset +// ------------------------- + +body { + margin: 0; + font-family: @baseFontFamily; + font-size: @baseFontSize; + line-height: @baseLineHeight; + color: @textColor; + background-color: @bodyBackground; +} + + +// Links +// ------------------------- + +a { + color: @linkColor; + text-decoration: none; +} +a:hover { + color: @linkColorHover; + text-decoration: underline; +} + + +// Images +// ------------------------- + +// Rounded corners +.img-rounded { + .border-radius(6px); +} + +// Add polaroid-esque trim +.img-polaroid { + padding: 4px; + background-color: #fff; + border: 1px solid #ccc; + border: 1px solid rgba(0,0,0,.2); + .box-shadow(0 1px 3px rgba(0,0,0,.1)); +} + +// Perfect circle +.img-circle { + .border-radius(500px); // crank the border-radius so it works with most reasonably sized images +} diff --git a/src/main/less/bootstrap/sprites.less b/src/main/less/bootstrap/sprites.less @@ -0,0 +1,193 @@ +// +// Sprites +// -------------------------------------------------- + + +// ICONS +// ----- + +// All icons receive the styles of the <i> tag with a base class +// of .i and are then given a unique class to add width, height, +// and background-position. Your resulting HTML will look like +// <i class="icon-inbox"></i>. + +// For the white version of the icons, just add the .icon-white class: +// <i class="icon-inbox icon-white"></i> + +[class^="icon-"], +[class*=" icon-"] { + display: inline-block; + width: 14px; + height: 14px; + .ie7-restore-right-whitespace(); + line-height: 14px; + vertical-align: text-top; + background-image: url("@{iconSpritePath}"); + background-position: 14px 14px; + background-repeat: no-repeat; + margin-top: 1px; +} + +/* White icons with optional class, or on hover/active states of certain elements */ +.icon-white, +.nav-pills > .active > a > [class^="icon-"], +.nav-pills > .active > a > [class*=" icon-"], +.nav-list > .active > a > [class^="icon-"], +.nav-list > .active > a > [class*=" icon-"], +.navbar-inverse .nav > .active > a > [class^="icon-"], +.navbar-inverse .nav > .active > a > [class*=" icon-"], +.dropdown-menu > li > a:hover > [class^="icon-"], +.dropdown-menu > li > a:hover > [class*=" icon-"], +.dropdown-menu > .active > a > [class^="icon-"], +.dropdown-menu > .active > a > [class*=" icon-"], +.dropdown-submenu:hover > a > [class^="icon-"], +.dropdown-submenu:hover > a > [class*=" icon-"] { + background-image: url("@{iconWhiteSpritePath}"); +} + +.icon-glass { background-position: 0 0; } +.icon-music { background-position: -24px 0; } +.icon-search { background-position: -48px 0; } +.icon-envelope { background-position: -72px 0; } +.icon-heart { background-position: -96px 0; } +.icon-star { background-position: -120px 0; } +.icon-star-empty { background-position: -144px 0; } +.icon-user { background-position: -168px 0; } +.icon-film { background-position: -192px 0; } +.icon-th-large { background-position: -216px 0; } +.icon-th { background-position: -240px 0; } +.icon-th-list { background-position: -264px 0; } +.icon-ok { background-position: -288px 0; } +.icon-remove { background-position: -312px 0; } +.icon-zoom-in { background-position: -336px 0; } +.icon-zoom-out { background-position: -360px 0; } +.icon-off { background-position: -384px 0; } +.icon-signal { background-position: -408px 0; } +.icon-cog { background-position: -432px 0; } +.icon-trash { background-position: -456px 0; } + +.icon-home { background-position: 0 -24px; } +.icon-file { background-position: -24px -24px; } +.icon-time { background-position: -48px -24px; } +.icon-road { background-position: -72px -24px; } +.icon-download-alt { background-position: -96px -24px; } +.icon-download { background-position: -120px -24px; } +.icon-upload { background-position: -144px -24px; } +.icon-inbox { background-position: -168px -24px; } +.icon-play-circle { background-position: -192px -24px; } +.icon-repeat { background-position: -216px -24px; } +.icon-refresh { background-position: -240px -24px; } +.icon-list-alt { background-position: -264px -24px; } +.icon-lock { background-position: -287px -24px; } // 1px off +.icon-flag { background-position: -312px -24px; } +.icon-headphones { background-position: -336px -24px; } +.icon-volume-off { background-position: -360px -24px; } +.icon-volume-down { background-position: -384px -24px; } +.icon-volume-up { background-position: -408px -24px; } +.icon-qrcode { background-position: -432px -24px; } +.icon-barcode { background-position: -456px -24px; } + +.icon-tag { background-position: 0 -48px; } +.icon-tags { background-position: -25px -48px; } // 1px off +.icon-book { background-position: -48px -48px; } +.icon-bookmark { background-position: -72px -48px; } +.icon-print { background-position: -96px -48px; } +.icon-camera { background-position: -120px -48px; } +.icon-font { background-position: -144px -48px; } +.icon-bold { background-position: -167px -48px; } // 1px off +.icon-italic { background-position: -192px -48px; } +.icon-text-height { background-position: -216px -48px; } +.icon-text-width { background-position: -240px -48px; } +.icon-align-left { background-position: -264px -48px; } +.icon-align-center { background-position: -288px -48px; } +.icon-align-right { background-position: -312px -48px; } +.icon-align-justify { background-position: -336px -48px; } +.icon-list { background-position: -360px -48px; } +.icon-indent-left { background-position: -384px -48px; } +.icon-indent-right { background-position: -408px -48px; } +.icon-facetime-video { background-position: -432px -48px; } +.icon-picture { background-position: -456px -48px; } + +.icon-pencil { background-position: 0 -72px; } +.icon-map-marker { background-position: -24px -72px; } +.icon-adjust { background-position: -48px -72px; } +.icon-tint { background-position: -72px -72px; } +.icon-edit { background-position: -96px -72px; } +.icon-share { background-position: -120px -72px; } +.icon-check { background-position: -144px -72px; } +.icon-move { background-position: -168px -72px; } +.icon-step-backward { background-position: -192px -72px; } +.icon-fast-backward { background-position: -216px -72px; } +.icon-backward { background-position: -240px -72px; } +.icon-play { background-position: -264px -72px; } +.icon-pause { background-position: -288px -72px; } +.icon-stop { background-position: -312px -72px; } +.icon-forward { background-position: -336px -72px; } +.icon-fast-forward { background-position: -360px -72px; } +.icon-step-forward { background-position: -384px -72px; } +.icon-eject { background-position: -408px -72px; } +.icon-chevron-left { background-position: -432px -72px; } +.icon-chevron-right { background-position: -456px -72px; } + +.icon-plus-sign { background-position: 0 -96px; } +.icon-minus-sign { background-position: -24px -96px; } +.icon-remove-sign { background-position: -48px -96px; } +.icon-ok-sign { background-position: -72px -96px; } +.icon-question-sign { background-position: -96px -96px; } +.icon-info-sign { background-position: -120px -96px; } +.icon-screenshot { background-position: -144px -96px; } +.icon-remove-circle { background-position: -168px -96px; } +.icon-ok-circle { background-position: -192px -96px; } +.icon-ban-circle { background-position: -216px -96px; } +.icon-arrow-left { background-position: -240px -96px; } +.icon-arrow-right { background-position: -264px -96px; } +.icon-arrow-up { background-position: -289px -96px; } // 1px off +.icon-arrow-down { background-position: -312px -96px; } +.icon-share-alt { background-position: -336px -96px; } +.icon-resize-full { background-position: -360px -96px; } +.icon-resize-small { background-position: -384px -96px; } +.icon-plus { background-position: -408px -96px; } +.icon-minus { background-position: -433px -96px; } +.icon-asterisk { background-position: -456px -96px; } + +.icon-exclamation-sign { background-position: 0 -120px; } +.icon-gift { background-position: -24px -120px; } +.icon-leaf { background-position: -48px -120px; } +.icon-fire { background-position: -72px -120px; } +.icon-eye-open { background-position: -96px -120px; } +.icon-eye-close { background-position: -120px -120px; } +.icon-warning-sign { background-position: -144px -120px; } +.icon-plane { background-position: -168px -120px; } +.icon-calendar { background-position: -192px -120px; } +.icon-random { background-position: -216px -120px; width: 16px; } +.icon-comment { background-position: -240px -120px; } +.icon-magnet { background-position: -264px -120px; } +.icon-chevron-up { background-position: -288px -120px; } +.icon-chevron-down { background-position: -313px -119px; } // 1px, 1px off +.icon-retweet { background-position: -336px -120px; } +.icon-shopping-cart { background-position: -360px -120px; } +.icon-folder-close { background-position: -384px -120px; } +.icon-folder-open { background-position: -408px -120px; width: 16px; } +.icon-resize-vertical { background-position: -432px -119px; } // 1px, 1px off +.icon-resize-horizontal { background-position: -456px -118px; } // 1px, 2px off + +.icon-hdd { background-position: 0 -144px; } +.icon-bullhorn { background-position: -24px -144px; } +.icon-bell { background-position: -48px -144px; } +.icon-certificate { background-position: -72px -144px; } +.icon-thumbs-up { background-position: -96px -144px; } +.icon-thumbs-down { background-position: -120px -144px; } +.icon-hand-right { background-position: -144px -144px; } +.icon-hand-left { background-position: -168px -144px; } +.icon-hand-up { background-position: -192px -144px; } +.icon-hand-down { background-position: -216px -144px; } +.icon-circle-arrow-right { background-position: -240px -144px; } +.icon-circle-arrow-left { background-position: -264px -144px; } +.icon-circle-arrow-up { background-position: -288px -144px; } +.icon-circle-arrow-down { background-position: -312px -144px; } +.icon-globe { background-position: -336px -144px; } +.icon-wrench { background-position: -360px -144px; } +.icon-tasks { background-position: -384px -144px; } +.icon-filter { background-position: -408px -144px; } +.icon-briefcase { background-position: -432px -144px; } +.icon-fullscreen { background-position: -456px -144px; } diff --git a/src/main/less/bootstrap/tables.less b/src/main/less/bootstrap/tables.less @@ -0,0 +1,237 @@ +// +// Tables +// -------------------------------------------------- + + +// BASE TABLES +// ----------------- + +table { + max-width: 100%; + background-color: @tableBackground; + border-collapse: collapse; + border-spacing: 0; +} + +// BASELINE STYLES +// --------------- + +.table { + width: 100%; + margin-bottom: @baseLineHeight; + // Cells + th, + td { + padding: 8px; + line-height: @baseLineHeight; + text-align: left; + vertical-align: top; + border-top: 1px solid @tableBorder; + } + th { + font-weight: bold; + } + // Bottom align for column headings + thead th { + vertical-align: bottom; + } + // Remove top border from thead by default + caption + thead tr:first-child th, + caption + thead tr:first-child td, + colgroup + thead tr:first-child th, + colgroup + thead tr:first-child td, + thead:first-child tr:first-child th, + thead:first-child tr:first-child td { + border-top: 0; + } + // Account for multiple tbody instances + tbody + tbody { + border-top: 2px solid @tableBorder; + } + + // Nesting + .table { + background-color: @bodyBackground; + } +} + + + +// CONDENSED TABLE W/ HALF PADDING +// ------------------------------- + +.table-condensed { + th, + td { + padding: 4px 5px; + } +} + + +// BORDERED VERSION +// ---------------- + +.table-bordered { + border: 1px solid @tableBorder; + border-collapse: separate; // Done so we can round those corners! + *border-collapse: collapse; // IE7 can't round corners anyway + border-left: 0; + .border-radius(@baseBorderRadius); + th, + td { + border-left: 1px solid @tableBorder; + } + // Prevent a double border + caption + thead tr:first-child th, + caption + tbody tr:first-child th, + caption + tbody tr:first-child td, + colgroup + thead tr:first-child th, + colgroup + tbody tr:first-child th, + colgroup + tbody tr:first-child td, + thead:first-child tr:first-child th, + tbody:first-child tr:first-child th, + tbody:first-child tr:first-child td { + border-top: 0; + } + // For first th or td in the first row in the first thead or tbody + thead:first-child tr:first-child > th:first-child, + tbody:first-child tr:first-child > td:first-child { + .border-top-left-radius(@baseBorderRadius); + } + thead:first-child tr:first-child > th:last-child, + tbody:first-child tr:first-child > td:last-child { + .border-top-right-radius(@baseBorderRadius); + } + // For first th or td in the last row in the last thead or tbody + thead:last-child tr:last-child > th:first-child, + tbody:last-child tr:last-child > td:first-child, + tfoot:last-child tr:last-child > td:first-child { + .border-bottom-left-radius(@baseBorderRadius); + } + thead:last-child tr:last-child > th:last-child, + tbody:last-child tr:last-child > td:last-child, + tfoot:last-child tr:last-child > td:last-child { + .border-bottom-right-radius(@baseBorderRadius); + } + + // Clear border-radius for first and last td in the last row in the last tbody for table with tfoot + tfoot + tbody:last-child tr:last-child td:first-child { + .border-bottom-left-radius(0); + } + tfoot + tbody:last-child tr:last-child td:last-child { + .border-bottom-right-radius(0); + } + + + // Special fixes to round the left border on the first td/th + caption + thead tr:first-child th:first-child, + caption + tbody tr:first-child td:first-child, + colgroup + thead tr:first-child th:first-child, + colgroup + tbody tr:first-child td:first-child { + .border-top-left-radius(@baseBorderRadius); + } + caption + thead tr:first-child th:last-child, + caption + tbody tr:first-child td:last-child, + colgroup + thead tr:first-child th:last-child, + colgroup + tbody tr:first-child td:last-child { + .border-top-right-radius(@baseBorderRadius); + } + +} + + + + +// ZEBRA-STRIPING +// -------------- + +// Default zebra-stripe styles (alternating gray and transparent backgrounds) +.table-striped { + tbody { + > tr:nth-child(odd) > td, + > tr:nth-child(odd) > th { + background-color: @tableBackgroundAccent; + } + } +} + + +// HOVER EFFECT +// ------------ +// Placed here since it has to come after the potential zebra striping +.table-hover { + tbody { + tr:hover td, + tr:hover th { + background-color: @tableBackgroundHover; + } + } +} + + +// TABLE CELL SIZING +// ----------------- + +// Reset default grid behavior +table td[class*="span"], +table th[class*="span"], +.row-fluid table td[class*="span"], +.row-fluid table th[class*="span"] { + display: table-cell; + float: none; // undo default grid column styles + margin-left: 0; // undo default grid column styles +} + +// Change the column widths to account for td/th padding +.table td, +.table th { + &.span1 { .tableColumns(1); } + &.span2 { .tableColumns(2); } + &.span3 { .tableColumns(3); } + &.span4 { .tableColumns(4); } + &.span5 { .tableColumns(5); } + &.span6 { .tableColumns(6); } + &.span7 { .tableColumns(7); } + &.span8 { .tableColumns(8); } + &.span9 { .tableColumns(9); } + &.span10 { .tableColumns(10); } + &.span11 { .tableColumns(11); } + &.span12 { .tableColumns(12); } +} + + + +// TABLE BACKGROUNDS +// ----------------- +// Exact selectors below required to override .table-striped + +.table tbody tr { + &.success td { + background-color: @successBackground; + } + &.error td { + background-color: @errorBackground; + } + &.warning td { + background-color: @warningBackground; + } + &.info td { + background-color: @infoBackground; + } +} + +// Hover states for .table-hover +.table-hover tbody tr { + &.success:hover td { + background-color: darken(@successBackground, 5%); + } + &.error:hover td { + background-color: darken(@errorBackground, 5%); + } + &.warning:hover td { + background-color: darken(@warningBackground, 5%); + } + &.info:hover td { + background-color: darken(@infoBackground, 5%); + } +} diff --git a/src/main/less/bootstrap/thumbnails.less b/src/main/less/bootstrap/thumbnails.less @@ -0,0 +1,52 @@ +// +// Thumbnails +// -------------------------------------------------- + + +// Note: `.thumbnails` and `.thumbnails > li` are overriden in responsive files + +// Make wrapper ul behave like the grid +.thumbnails { + margin-left: -@gridGutterWidth; + list-style: none; + .clearfix(); +} +// Fluid rows have no left margin +.row-fluid .thumbnails { + margin-left: 0; +} + +// Float li to make thumbnails appear in a row +.thumbnails > li { + float: left; // Explicity set the float since we don't require .span* classes + margin-bottom: @baseLineHeight; + margin-left: @gridGutterWidth; +} + +// The actual thumbnail (can be `a` or `div`) +.thumbnail { + display: block; + padding: 4px; + line-height: @baseLineHeight; + border: 1px solid #ddd; + .border-radius(@baseBorderRadius); + .box-shadow(0 1px 3px rgba(0,0,0,.055)); + .transition(all .2s ease-in-out); +} +// Add a hover state for linked versions only +a.thumbnail:hover { + border-color: @linkColor; + .box-shadow(0 1px 4px rgba(0,105,214,.25)); +} + +// Images and captions +.thumbnail > img { + display: block; + max-width: 100%; + margin-left: auto; + margin-right: auto; +} +.thumbnail .caption { + padding: 9px; + color: @gray; +} diff --git a/src/main/less/bootstrap/tooltip.less b/src/main/less/bootstrap/tooltip.less @@ -0,0 +1,70 @@ +// +// Tooltips +// -------------------------------------------------- + + +// Base class +.tooltip { + position: absolute; + z-index: @zindexTooltip; + display: block; + visibility: visible; + padding: 5px; + font-size: 11px; + .opacity(0); + &.in { .opacity(80); } + &.top { margin-top: -3px; } + &.right { margin-left: 3px; } + &.bottom { margin-top: 3px; } + &.left { margin-left: -3px; } +} + +// Wrapper for the tooltip content +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: @tooltipColor; + text-align: center; + text-decoration: none; + background-color: @tooltipBackground; + .border-radius(@baseBorderRadius); +} + +// Arrows +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip { + &.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -@tooltipArrowWidth; + border-width: @tooltipArrowWidth @tooltipArrowWidth 0; + border-top-color: @tooltipArrowColor; + } + &.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -@tooltipArrowWidth; + border-width: @tooltipArrowWidth @tooltipArrowWidth @tooltipArrowWidth 0; + border-right-color: @tooltipArrowColor; + } + &.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -@tooltipArrowWidth; + border-width: @tooltipArrowWidth 0 @tooltipArrowWidth @tooltipArrowWidth; + border-left-color: @tooltipArrowColor; + } + &.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -@tooltipArrowWidth; + border-width: 0 @tooltipArrowWidth @tooltipArrowWidth; + border-bottom-color: @tooltipArrowColor; + } +} diff --git a/src/main/less/bootstrap/type.less b/src/main/less/bootstrap/type.less @@ -0,0 +1,235 @@ +// +// Typography +// -------------------------------------------------- + + +// Body text +// ------------------------- + +p { + margin: 0 0 @baseLineHeight / 2; +} +.lead { + margin-bottom: @baseLineHeight; + font-size: @baseFontSize * 1.5; + font-weight: 200; + line-height: @baseLineHeight * 1.5; +} + + +// Emphasis & misc +// ------------------------- + +// Ex: 14px base font * 85% = about 12px +small { font-size: 85%; } + +strong { font-weight: bold; } +em { font-style: italic; } +cite { font-style: normal; } + +// Utility classes +.muted { color: @grayLight; } +a.muted:hover { color: darken(@grayLight, 10%); } + +.text-warning { color: @warningText; } +a.text-warning:hover { color: darken(@warningText, 10%); } + +.text-error { color: @errorText; } +a.text-error:hover { color: darken(@errorText, 10%); } + +.text-info { color: @infoText; } +a.text-info:hover { color: darken(@infoText, 10%); } + +.text-success { color: @successText; } +a.text-success:hover { color: darken(@successText, 10%); } + + +// Headings +// ------------------------- + +h1, h2, h3, h4, h5, h6 { + margin: (@baseLineHeight / 2) 0; + font-family: @headingsFontFamily; + font-weight: @headingsFontWeight; + line-height: @baseLineHeight; + color: @headingsColor; + text-rendering: optimizelegibility; // Fix the character spacing for headings + small { + font-weight: normal; + line-height: 1; + color: @grayLight; + } +} + +h1, +h2, +h3 { line-height: @baseLineHeight * 2; } + +h1 { font-size: @baseFontSize * 2.75; } // ~38px +h2 { font-size: @baseFontSize * 2.25; } // ~32px +h3 { font-size: @baseFontSize * 1.75; } // ~24px +h4 { font-size: @baseFontSize * 1.25; } // ~18px +h5 { font-size: @baseFontSize; } +h6 { font-size: @baseFontSize * 0.85; } // ~12px + +h1 small { font-size: @baseFontSize * 1.75; } // ~24px +h2 small { font-size: @baseFontSize * 1.25; } // ~18px +h3 small { font-size: @baseFontSize; } +h4 small { font-size: @baseFontSize; } + + +// Page header +// ------------------------- + +.page-header { + padding-bottom: (@baseLineHeight / 2) - 1; + margin: @baseLineHeight 0 (@baseLineHeight * 1.5); + border-bottom: 1px solid @grayLighter; +} + + + +// Lists +// -------------------------------------------------- + +// Unordered and Ordered lists +ul, ol { + padding: 0; + margin: 0 0 @baseLineHeight / 2 25px; +} +ul ul, +ul ol, +ol ol, +ol ul { + margin-bottom: 0; +} +li { + line-height: @baseLineHeight; +} + +// Remove default list styles +ul.unstyled, +ol.unstyled { + margin-left: 0; + list-style: none; +} + +// Single-line list items +ul.inline, +ol.inline { + margin-left: 0; + list-style: none; + & > li { + display: inline-block; + padding-left: 5px; + padding-right: 5px; + } +} + +// Description Lists +dl { + margin-bottom: @baseLineHeight; +} +dt, +dd { + line-height: @baseLineHeight; +} +dt { + font-weight: bold; +} +dd { + margin-left: @baseLineHeight / 2; +} +// Horizontal layout (like forms) +.dl-horizontal { + .clearfix(); // Ensure dl clears floats if empty dd elements present + dt { + float: left; + width: @horizontalComponentOffset - 20; + clear: left; + text-align: right; + .text-overflow(); + } + dd { + margin-left: @horizontalComponentOffset; + } +} + +// MISC +// ---- + +// Horizontal rules +hr { + margin: @baseLineHeight 0; + border: 0; + border-top: 1px solid @hrBorder; + border-bottom: 1px solid @white; +} + +// Abbreviations and acronyms +abbr[title], +// Added data-* attribute to help out our tooltip plugin, per https://github.com/twitter/bootstrap/issues/5257 +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted @grayLight; +} +abbr.initialism { + font-size: 90%; + text-transform: uppercase; +} + +// Blockquotes +blockquote { + padding: 0 0 0 15px; + margin: 0 0 @baseLineHeight; + border-left: 5px solid @grayLighter; + p { + margin-bottom: 0; + #font > .shorthand(16px,300,@baseLineHeight * 1.25); + } + small { + display: block; + line-height: @baseLineHeight; + color: @grayLight; + &:before { + content: '\2014 \00A0'; + } + } + + // Float right with text-align: right + &.pull-right { + float: right; + padding-right: 15px; + padding-left: 0; + border-right: 5px solid @grayLighter; + border-left: 0; + p, + small { + text-align: right; + } + small { + &:before { + content: ''; + } + &:after { + content: '\00A0 \2014'; + } + } + } +} + +// Quotes +q:before, +q:after, +blockquote:before, +blockquote:after { + content: ""; +} + +// Addresses +address { + display: block; + margin-bottom: @baseLineHeight; + font-style: normal; + line-height: @baseLineHeight; +} diff --git a/src/main/less/bootstrap/utilities.less b/src/main/less/bootstrap/utilities.less @@ -0,0 +1,30 @@ +// +// Utility classes +// -------------------------------------------------- + + +// Quick floats +.pull-right { + float: right; +} +.pull-left { + float: left; +} + +// Toggling content +.hide { + display: none; +} +.show { + display: block; +} + +// Visibility +.invisible { + visibility: hidden; +} + +// For Affix plugin +.affix { + position: fixed; +} diff --git a/src/main/less/bootstrap/variables.less b/src/main/less/bootstrap/variables.less @@ -0,0 +1,301 @@ +// +// Variables +// -------------------------------------------------- + + +// Global values +// -------------------------------------------------- + + +// Grays +// ------------------------- +@black: #000; +@grayDarker: #222; +@grayDark: #333; +@gray: #555; +@grayLight: #999; +@grayLighter: #eee; +@white: #fff; + + +// Accent colors +// ------------------------- +@blue: #049cdb; +@blueDark: #0064cd; +@green: #46a546; +@red: #9d261d; +@yellow: #ffc40d; +@orange: #f89406; +@pink: #c3325f; +@purple: #7a43b6; + + +// Scaffolding +// ------------------------- +@bodyBackground: @white; +@textColor: @grayDark; + + +// Links +// ------------------------- +@linkColor: #08c; +@linkColorHover: darken(@linkColor, 15%); + + +// Typography +// ------------------------- +@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif; +@serifFontFamily: Georgia, "Times New Roman", Times, serif; +@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace; + +@baseFontSize: 14px; +@baseFontFamily: @sansFontFamily; +@baseLineHeight: 20px; +@altFontFamily: @serifFontFamily; + +@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily +@headingsFontWeight: bold; // instead of browser default, bold +@headingsColor: inherit; // empty to use BS default, @textColor + + +// Component sizing +// ------------------------- +// Based on 14px font-size and 20px line-height + +@fontSizeLarge: @baseFontSize * 1.25; // ~18px +@fontSizeSmall: @baseFontSize * 0.85; // ~12px +@fontSizeMini: @baseFontSize * 0.75; // ~11px + +@paddingLarge: 11px 19px; // 44px +@paddingSmall: 2px 10px; // 26px +@paddingMini: 0 6px; // 22px + +@baseBorderRadius: 4px; +@borderRadiusLarge: 6px; +@borderRadiusSmall: 3px; + + +// Tables +// ------------------------- +@tableBackground: transparent; // overall background-color +@tableBackgroundAccent: #f9f9f9; // for striping +@tableBackgroundHover: #f5f5f5; // for hover +@tableBorder: #ddd; // table and cell border + +// Buttons +// ------------------------- +@btnBackground: @white; +@btnBackgroundHighlight: darken(@white, 10%); +@btnBorder: #bbb; + +@btnPrimaryBackground: @linkColor; +@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 20%); + +@btnInfoBackground: #5bc0de; +@btnInfoBackgroundHighlight: #2f96b4; + +@btnSuccessBackground: #62c462; +@btnSuccessBackgroundHighlight: #51a351; + +@btnWarningBackground: lighten(@orange, 15%); +@btnWarningBackgroundHighlight: @orange; + +@btnDangerBackground: #ee5f5b; +@btnDangerBackgroundHighlight: #bd362f; + +@btnInverseBackground: #444; +@btnInverseBackgroundHighlight: @grayDarker; + + +// Forms +// ------------------------- +@inputBackground: @white; +@inputBorder: #ccc; +@inputBorderRadius: @baseBorderRadius; +@inputDisabledBackground: @grayLighter; +@formActionsBackground: #f5f5f5; +@inputHeight: @baseLineHeight + 10px; // base line-height + 8px vertical padding + 2px top/bottom border + + +// Dropdowns +// ------------------------- +@dropdownBackground: @white; +@dropdownBorder: rgba(0,0,0,.2); +@dropdownDividerTop: #e5e5e5; +@dropdownDividerBottom: @white; + +@dropdownLinkColor: @grayDark; +@dropdownLinkColorHover: @white; +@dropdownLinkColorActive: @white; + +@dropdownLinkBackgroundActive: @linkColor; +@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive; + + + +// COMPONENT VARIABLES +// -------------------------------------------------- + + +// Z-index master list +// ------------------------- +// Used for a bird's eye view of components dependent on the z-axis +// Try to avoid customizing these :) +@zindexDropdown: 1000; +@zindexPopover: 1010; +@zindexTooltip: 1030; +@zindexFixedNavbar: 1030; +@zindexModalBackdrop: 1040; +@zindexModal: 1050; + + +// Sprite icons path +// ------------------------- +@iconSpritePath: "../img/glyphicons-halflings.png"; +@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png"; + + +// Input placeholder text color +// ------------------------- +@placeholderText: @grayLight; + + +// Hr border color +// ------------------------- +@hrBorder: @grayLighter; + + +// Horizontal forms & lists +// ------------------------- +@horizontalComponentOffset: 180px; + + +// Wells +// ------------------------- +@wellBackground: #f5f5f5; + + +// Navbar +// ------------------------- +@navbarCollapseWidth: 979px; +@navbarCollapseDesktopWidth: @navbarCollapseWidth + 1; + +@navbarHeight: 40px; +@navbarBackgroundHighlight: #ffffff; +@navbarBackground: darken(@navbarBackgroundHighlight, 5%); +@navbarBorder: darken(@navbarBackground, 12%); + +@navbarText: #777; +@navbarLinkColor: #777; +@navbarLinkColorHover: @grayDark; +@navbarLinkColorActive: @gray; +@navbarLinkBackgroundHover: transparent; +@navbarLinkBackgroundActive: darken(@navbarBackground, 5%); + +@navbarBrandColor: @navbarLinkColor; + +// Inverted navbar +@navbarInverseBackground: #111111; +@navbarInverseBackgroundHighlight: #222222; +@navbarInverseBorder: #252525; + +@navbarInverseText: @grayLight; +@navbarInverseLinkColor: @grayLight; +@navbarInverseLinkColorHover: @white; +@navbarInverseLinkColorActive: @navbarInverseLinkColorHover; +@navbarInverseLinkBackgroundHover: transparent; +@navbarInverseLinkBackgroundActive: @navbarInverseBackground; + +@navbarInverseSearchBackground: lighten(@navbarInverseBackground, 25%); +@navbarInverseSearchBackgroundFocus: @white; +@navbarInverseSearchBorder: @navbarInverseBackground; +@navbarInverseSearchPlaceholderColor: #ccc; + +@navbarInverseBrandColor: @navbarInverseLinkColor; + + +// Pagination +// ------------------------- +@paginationBackground: #fff; +@paginationBorder: #ddd; +@paginationActiveBackground: #f5f5f5; + + +// Hero unit +// ------------------------- +@heroUnitBackground: @grayLighter; +@heroUnitHeadingColor: inherit; +@heroUnitLeadColor: inherit; + + +// Form states and alerts +// ------------------------- +@warningText: #c09853; +@warningBackground: #fcf8e3; +@warningBorder: darken(spin(@warningBackground, -10), 3%); + +@errorText: #b94a48; +@errorBackground: #f2dede; +@errorBorder: darken(spin(@errorBackground, -10), 3%); + +@successText: #468847; +@successBackground: #dff0d8; +@successBorder: darken(spin(@successBackground, -10), 5%); + +@infoText: #3a87ad; +@infoBackground: #d9edf7; +@infoBorder: darken(spin(@infoBackground, -10), 7%); + + +// Tooltips and popovers +// ------------------------- +@tooltipColor: #fff; +@tooltipBackground: #000; +@tooltipArrowWidth: 5px; +@tooltipArrowColor: @tooltipBackground; + +@popoverBackground: #fff; +@popoverArrowWidth: 10px; +@popoverArrowColor: #fff; +@popoverTitleBackground: darken(@popoverBackground, 3%); + +// Special enhancement for popovers +@popoverArrowOuterWidth: @popoverArrowWidth + 1; +@popoverArrowOuterColor: rgba(0,0,0,.25); + + + +// GRID +// -------------------------------------------------- + + +// Default 940px grid +// ------------------------- +@gridColumns: 12; +@gridColumnWidth: 60px; +@gridGutterWidth: 20px; +@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); + +// 1200px min +@gridColumnWidth1200: 70px; +@gridGutterWidth1200: 30px; +@gridRowWidth1200: (@gridColumns * @gridColumnWidth1200) + (@gridGutterWidth1200 * (@gridColumns - 1)); + +// 768px-979px +@gridColumnWidth768: 42px; +@gridGutterWidth768: 20px; +@gridRowWidth768: (@gridColumns * @gridColumnWidth768) + (@gridGutterWidth768 * (@gridColumns - 1)); + + +// Fluid grid +// ------------------------- +@fluidGridColumnWidth: percentage(@gridColumnWidth/@gridRowWidth); +@fluidGridGutterWidth: percentage(@gridGutterWidth/@gridRowWidth); + +// 1200px min +@fluidGridColumnWidth1200: percentage(@gridColumnWidth1200/@gridRowWidth1200); +@fluidGridGutterWidth1200: percentage(@gridGutterWidth1200/@gridRowWidth1200); + +// 768px-979px +@fluidGridColumnWidth768: percentage(@gridColumnWidth768/@gridRowWidth768); +@fluidGridGutterWidth768: percentage(@gridGutterWidth768/@gridRowWidth768); diff --git a/src/main/less/bootstrap/wells.less b/src/main/less/bootstrap/wells.less @@ -0,0 +1,29 @@ +// +// Wells +// -------------------------------------------------- + + +// Base class +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: @wellBackground; + border: 1px solid darken(@wellBackground, 7%); + .border-radius(@baseBorderRadius); + .box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); + blockquote { + border-color: #ddd; + border-color: rgba(0,0,0,.15); + } +} + +// Sizes +.well-large { + padding: 24px; + .border-radius(@borderRadiusLarge); +} +.well-small { + padding: 9px; + .border-radius(@borderRadiusSmall); +} diff --git a/src/main/less/custom.less b/src/main/less/custom.less @@ -0,0 +1,122 @@ +// Override some defaults +html, body { + padding-bottom: 40px; +} + +.main-content { + background-color: @white; + padding: 20px; + margin: 0 -20px; /* negative indent the amount of the padding to maintain the grid system */ + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,.15); + box-shadow: 0 1px 2px rgba(0,0,0,.15); +} + +.hero-unit { + h2 { + font-size: 36px; + } +} + +// FORM FIELD FEEDBACK STATES +// -------------------------- + +/** + * Default is to change the color of the help text to the same as the alert. + * This returns help text to original color. + */ +.customFormFieldState(@groupColor) { + .help-block, + .help-inline { + color: lighten(@textColor, 15%); // lighten the text some for contrast + } +} + +// Warning +.control-group.warning { + .customFormFieldState(@warningText); +} +// Error +.control-group.error { + .customFormFieldState(@errorText); +} +// Success +.control-group.success { + .customFormFieldState(@successText); +} +// Info +.control-group.info { + .customFormFieldState(@infoText); +} + +/** + * For coloring id notices + */ +.alert-block { + .warning { + color: @warningText; + } + .error { + color: @errorText; + } + .success { + color: @successText; + } + .info { + color: @infoText; + } +} + +#user img { + display: inline-block; + width: 20px; + height: 20px; + vertical-align: top; + margin-right: 6px; + border: none; + cursor: pointer; +} + +#user-header { + line-height: 4em; + + img { + display: inline-block; + vertical-align: middle; + margin-right: 6px; + border: none; + cursor: pointer; + } + + h3 { + display: inline-block; + } +} + +div.gravatar { + float:left; + margin-left:10px; + max-width: 320px; + + img { + padding-top:10px; + } +} + +// fixes gap between navbar and section below it +@media (max-width: @navbarCollapseWidth) { + .navbar-fixed-top { + margin-bottom: 0px; + } +} + +.signup-overlay { + position: absolute; + left: 0; + top: @navbarHeight + 40; + z-index: 1010; + background-color:#fff; +} diff --git a/src/main/less/mediaelementplayer.less b/src/main/less/mediaelementplayer.less @@ -0,0 +1,864 @@ +.mejs-container { + position: relative; + background: #000; + font-family: Helvetica, Arial; + text-align: left; + vertical-align: top; + text-indent: 0; +} + +.me-plugin { + position: absolute; +} + +.mejs-embed, .mejs-embed body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + background: #000; + overflow: hidden; +} + +.mejs-container-fullscreen { + position: fixed; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; + z-index: 1000; +} +.mejs-container-fullscreen .mejs-mediaelement, +.mejs-container-fullscreen video { + width: 100%; + height: 100%; +} + +.mejs-clear { + clear: both; +} + +/* Start: LAYERS */ +.mejs-background { + position: absolute; + top: 0; + left: 0; +} + +.mejs-mediaelement { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.mejs-poster { + position: absolute; + top: 0; + left: 0; + background-size: contain ; + background-position: 50% 50% ; + background-repeat: no-repeat ; +} +:root .mejs-poster img { + display: none ; +} + +.mejs-poster img { + border: 0; + padding: 0; + border: 0; +} + +.mejs-overlay { + position: absolute; + top: 0; + left: 0; +} + +.mejs-overlay-play { + cursor: pointer; +} + +.mejs-overlay-button { + position: absolute; + top: 50%; + left: 50%; + width: 100px; + height: 100px; + margin: -50px 0 0 -50px; + background: url(/img/media/bigplay.svg) no-repeat; +} + +.no-svg .mejs-overlay-button { + background-image: url(/img/media/bigplay.png); +} + +.mejs-overlay:hover .mejs-overlay-button { + background-position: 0 -100px ; +} + +.mejs-overlay-loading { + position: absolute; + top: 50%; + left: 50%; + width: 80px; + height: 80px; + margin: -40px 0 0 -40px; + background: #333; + background: url(/img/media/background.png); + background: rgba(0, 0, 0, 0.9); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(50,50,50,0.9)), to(rgba(0,0,0,0.9))); + background: -webkit-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9)); + background: -moz-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9)); + background: -o-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9)); + background: -ms-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9)); + background: linear-gradient(rgba(50,50,50,0.9), rgba(0,0,0,0.9)); +} + +.mejs-overlay-loading span { + display: block; + width: 80px; + height: 80px; + background: transparent url(/img/media/loading.gif) 50% 50% no-repeat; +} + +/* End: LAYERS */ + +/* Start: CONTROL BAR */ +.mejs-container .mejs-controls { + position: absolute; + list-style-type: none; + margin: 0; + padding: 0; + bottom: 0; + left: 0; + background: url(/img/media/background.png); + background: rgba(0, 0, 0, 0.7); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(50,50,50,0.7)), to(rgba(0,0,0,0.7))); + background: -webkit-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -moz-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -o-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -ms-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: linear-gradient(rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + height: 30px; + width: 100%; +} +.mejs-container .mejs-controls div { + list-style-type: none; + background-image: none; + display: block; + float: left; + margin: 0; + padding: 0; + width: 26px; + height: 26px; + font-size: 11px; + line-height: 11px; + font-family: Helvetica, Arial; + border: 0; +} + +.mejs-controls .mejs-button button { + cursor: pointer; + display: block; + font-size: 0; + line-height: 0; + text-decoration: none; + margin: 7px 5px; + padding: 0; + position: absolute; + height: 16px; + width: 16px; + border: 0; + background: transparent url(/img/media/controls.svg) no-repeat; +} + +.no-svg .mejs-controls .mejs-button button { + background-image: url(/img/media/controls.png); +} + +/* :focus for accessibility */ +.mejs-controls .mejs-button button:focus { + outline: solid 1px yellow; +} + +/* End: CONTROL BAR */ + +/* Start: Time (Current / Duration) */ +.mejs-container .mejs-controls .mejs-time { + color: #fff; + display: block; + height: 17px; + width: auto; + padding: 8px 3px 0 3px ; + overflow: hidden; + text-align: center; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +.mejs-container .mejs-controls .mejs-time span { + color: #fff; + font-size: 11px; + line-height: 12px; + display: block; + float: left; + margin: 1px 2px 0 0; + width: auto; +} +/* End: Time (Current / Duration) */ + +/* Start: Play/Pause/Stop */ +.mejs-controls .mejs-play button { + background-position: 0 0; +} + +.mejs-controls .mejs-pause button { + background-position: 0 -16px; +} + +.mejs-controls .mejs-stop button { + background-position: -112px 0; +} +/* Start: Play/Pause/Stop */ + +/* Start: Progress Bar */ +.mejs-controls div.mejs-time-rail { + width: 200px; + padding-top: 5px; +} + +.mejs-controls .mejs-time-rail span { + display: block; + position: absolute; + width: 180px; + height: 10px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + cursor: pointer; +} + +.mejs-controls .mejs-time-rail .mejs-time-total { + margin: 5px; + background: #333; + background: rgba(50,50,50,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(30,30,30,0.8)), to(rgba(60,60,60,0.8))); + background: -webkit-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -moz-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -o-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -ms-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: linear-gradient(rgba(30,30,30,0.8), rgba(60,60,60,0.8)); +} + +.mejs-controls .mejs-time-rail .mejs-time-buffering { + width: 100%; + background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + -webkit-background-size: 15px 15px; + -moz-background-size: 15px 15px; + -o-background-size: 15px 15px; + background-size: 15px 15px; + -webkit-animation: buffering-stripes 2s linear infinite; + -moz-animation: buffering-stripes 2s linear infinite; + -ms-animation: buffering-stripes 2s linear infinite; + -o-animation: buffering-stripes 2s linear infinite; + animation: buffering-stripes 2s linear infinite; +} + +@-webkit-keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } +@-moz-keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } +@-ms-keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } +@-o-keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } +@keyframes buffering-stripes { from {background-position: 0 0;} to {background-position: 30px 0;} } + +.mejs-controls .mejs-time-rail .mejs-time-loaded { + background: #3caac8; + background: rgba(60,170,200,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(44,124,145,0.8)), to(rgba(78,183,212,0.8))); + background: -webkit-linear-gradient(top, rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + background: -moz-linear-gradient(top, rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + background: -o-linear-gradient(top, rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + background: -ms-linear-gradient(top, rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + background: linear-gradient(rgba(44,124,145,0.8), rgba(78,183,212,0.8)); + width: 0; +} + +.mejs-controls .mejs-time-rail .mejs-time-current { + background: #fff; + background: rgba(255,255,255,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255,255,255,0.9)), to(rgba(200,200,200,0.8))); + background: -webkit-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -moz-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -o-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -ms-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: linear-gradient(rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + width: 0; +} + +.mejs-controls .mejs-time-rail .mejs-time-handle { + display: none; + position: absolute; + margin: 0; + width: 10px; + background: #fff; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + cursor: pointer; + border: solid 2px #333; + top: -2px; + text-align: center; +} + +.mejs-controls .mejs-time-rail .mejs-time-float { + position: absolute; + display: none; + background: #eee; + width: 36px; + height: 17px; + border: solid 1px #333; + top: -26px; + margin-left: -18px; + text-align: center; + color: #111; +} + +.mejs-controls .mejs-time-rail .mejs-time-float-current { + margin: 2px; + width: 30px; + display: block; + text-align: center; + left: 0; +} + +.mejs-controls .mejs-time-rail .mejs-time-float-corner { + position: absolute; + display: block; + width: 0; + height: 0; + line-height: 0; + border: solid 5px #eee; + border-color: #eee transparent transparent transparent; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + top: 15px; + left: 13px; +} + +.mejs-long-video .mejs-controls .mejs-time-rail .mejs-time-float { + width: 48px; +} + +.mejs-long-video .mejs-controls .mejs-time-rail .mejs-time-float-current { + width: 44px; +} + +.mejs-long-video .mejs-controls .mejs-time-rail .mejs-time-float-corner { + left: 18px; +} + +/* +.mejs-controls .mejs-time-rail:hover .mejs-time-handle { + visibility:visible; +} +*/ +/* End: Progress Bar */ + +/* Start: Fullscreen */ +.mejs-controls .mejs-fullscreen-button button { + background-position: -32px 0; +} + +.mejs-controls .mejs-unfullscreen button { + background-position: -32px -16px; +} +/* End: Fullscreen */ + + +/* Start: Mute/Volume */ +.mejs-controls .mejs-volume-button { +} + +.mejs-controls .mejs-mute button { + background-position: -16px -16px; +} + +.mejs-controls .mejs-unmute button { + background-position: -16px 0; +} + +.mejs-controls .mejs-volume-button { + position: relative; +} + +.mejs-controls .mejs-volume-button .mejs-volume-slider { + display: none; + height: 115px; + width: 25px; + background: url(/img/media/background.png); + background: rgba(50, 50, 50, 0.7); + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + top: -115px; + left: 0; + z-index: 1; + position: absolute; + margin: 0; +} + +.mejs-controls .mejs-volume-button:hover { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} + +/* +.mejs-controls .mejs-volume-button:hover .mejs-volume-slider { + display: block; +} +*/ + +.mejs-controls .mejs-volume-button .mejs-volume-slider .mejs-volume-total { + position: absolute; + left: 11px; + top: 8px; + width: 2px; + height: 100px; + background: #ddd; + background: rgba(255, 255, 255, 0.5); + margin: 0; +} + +.mejs-controls .mejs-volume-button .mejs-volume-slider .mejs-volume-current { + position: absolute; + left: 11px; + top: 8px; + width: 2px; + height: 100px; + background: #ddd; + background: rgba(255, 255, 255, 0.9); + margin: 0; +} + +.mejs-controls .mejs-volume-button .mejs-volume-slider .mejs-volume-handle { + position: absolute; + left: 4px; + top: -3px; + width: 16px; + height: 6px; + background: #ddd; + background: rgba(255, 255, 255, 0.9); + cursor: N-resize; + -webkit-border-radius: 1px; + -moz-border-radius: 1px; + border-radius: 1px; + margin: 0; +} + +/* horizontal version */ +.mejs-controls div.mejs-horizontal-volume-slider { + height: 26px; + width: 60px; + position: relative; +} + +.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-total { + position: absolute; + left: 0; + top: 11px; + width: 50px; + height: 8px; + margin: 0; + padding: 0; + font-size: 1px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + background: #333; + background: rgba(50,50,50,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(30,30,30,0.8)), to(rgba(60,60,60,0.8))); + background: -webkit-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -moz-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -o-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: -ms-linear-gradient(top, rgba(30,30,30,0.8), rgba(60,60,60,0.8)); + background: linear-gradient(rgba(30,30,30,0.8), rgba(60,60,60,0.8)); +} + +.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-current { + position: absolute; + left: 0; + top: 11px; + width: 50px; + height: 8px; + margin: 0; + padding: 0; + font-size: 1px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + background: #fff; + background: rgba(255,255,255,0.8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255,255,255,0.9)), to(rgba(200,200,200,0.8))); + background: -webkit-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -moz-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -o-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: -ms-linear-gradient(top, rgba(255,255,255,0.9), rgba(200,200,200,0.8)); + background: linear-gradient(rgba(255,255,255,0.9), rgba(200,200,200,0.8)); +} + +.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-handle { + display: none; +} + +/* End: Mute/Volume */ + +/* Start: Track (Captions and Chapters) */ +.mejs-controls .mejs-captions-button { + position: relative; +} + +.mejs-controls .mejs-captions-button button { + background-position: -48px 0; +} +.mejs-controls .mejs-captions-button .mejs-captions-selector { + visibility: hidden; + position: absolute; + bottom: 26px; + right: -10px; + width: 130px; + height: 100px; + background: url(/img/media/background.png); + background: rgba(50,50,50,0.7); + border: solid 1px transparent; + padding: 10px; + overflow: hidden; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +/* +.mejs-controls .mejs-captions-button:hover .mejs-captions-selector { + visibility: visible; +} +*/ + +.mejs-controls .mejs-captions-button .mejs-captions-selector ul { + margin: 0; + padding: 0; + display: block; + list-style-type: none !important; + overflow: hidden; +} + +.mejs-controls .mejs-captions-button .mejs-captions-selector ul li { + margin: 0 0 6px 0; + padding: 0; + list-style-type: none !important; + display: block; + color: #fff; + overflow: hidden; +} + +.mejs-controls .mejs-captions-button .mejs-captions-selector ul li input { + clear: both; + float: left; + margin: 3px 3px 0 5px; +} + +.mejs-controls .mejs-captions-button .mejs-captions-selector ul li label { + width: 100px; + float: left; + padding: 4px 0 0 0; + line-height: 15px; + font-family: helvetica, arial; + font-size: 10px; +} + +.mejs-controls .mejs-captions-button .mejs-captions-translations { + font-size: 10px; + margin: 0 0 5px 0; +} + +.mejs-chapters { + position: absolute; + top: 0; + left: 0; + -xborder-right: solid 1px #fff; + width: 10000px; + z-index: 1; +} + +.mejs-chapters .mejs-chapter { + position: absolute; + float: left; + background: #222; + background: rgba(0, 0, 0, 0.7); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(50,50,50,0.7)), to(rgba(0,0,0,0.7))); + background: -webkit-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -moz-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -o-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: -ms-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + background: linear-gradient(rgba(50,50,50,0.7), rgba(0,0,0,0.7)); + filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0, startColorstr=#323232,endColorstr=#000000); + overflow: hidden; + border: 0; +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block { + font-size: 11px; + color: #fff; + padding: 5px; + display: block; + border-right: solid 1px #333; + border-bottom: solid 1px #333; + cursor: pointer; +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block-last { + border-right: none; +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block:hover { + background: #666; + background: rgba(102,102,102, 0.7); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(102,102,102,0.7)), to(rgba(50,50,50,0.6))); + background: -webkit-linear-gradient(top, rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + background: -moz-linear-gradient(top, rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + background: -o-linear-gradient(top, rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + background: -ms-linear-gradient(top, rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + background: linear-gradient(rgba(102,102,102,0.7), rgba(50,50,50,0.6)); + filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0, startColorstr=#666666,endColorstr=#323232); +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block .ch-title { + font-size: 12px; + font-weight: bold; + display: block; + white-space: nowrap; + text-overflow: ellipsis; + margin: 0 0 3px 0; + line-height: 12px; +} + +.mejs-chapters .mejs-chapter .mejs-chapter-block .ch-timespan { + font-size: 12px; + line-height: 12px; + margin: 3px 0 4px 0; + display: block; + white-space: nowrap; + text-overflow: ellipsis; +} + +.mejs-captions-layer { + position: absolute; + bottom: 0; + left: 0; + text-align:center; + line-height: 22px; + font-size: 12px; + color: #fff; +} + +.mejs-captions-layer a { + color: #fff; + text-decoration: underline; +} + +.mejs-captions-layer[lang=ar] { + font-size: 20px; + font-weight: normal; +} + +.mejs-captions-position { + position: absolute; + width: 100%; + bottom: 15px; + left: 0; +} + +.mejs-captions-position-hover { + bottom: 45px; +} + +.mejs-captions-text { + padding: 3px 5px; + background: url(/img/media/background.png); + background: rgba(20, 20, 20, 0.8); + +} +/* End: Track (Captions and Chapters) */ + +/* Start: Error */ +.me-cannotplay { +} + +.me-cannotplay a { + color: #fff; + font-weight: bold; +} + +.me-cannotplay span { + padding: 15px; + display: block; +} +/* End: Error */ + + +/* Start: Loop */ +.mejs-controls .mejs-loop-off button { + background-position: -64px -16px; +} + +.mejs-controls .mejs-loop-on button { + background-position: -64px 0; +} + +/* End: Loop */ + +/* Start: backlight */ +.mejs-controls .mejs-backlight-off button { + background-position: -80px -16px; +} + +.mejs-controls .mejs-backlight-on button { + background-position: -80px 0; +} +/* End: backlight */ + +/* Start: Picture Controls */ +.mejs-controls .mejs-picturecontrols-button { + background-position: -96px 0; +} +/* End: Picture Controls */ + + +/* context menu */ +.mejs-contextmenu { + position: absolute; + width: 150px; + padding: 10px; + border-radius: 4px; + top: 0; + left: 0; + background: #fff; + border: solid 1px #999; + z-index: 1001; /* make sure it shows on fullscreen */ +} +.mejs-contextmenu .mejs-contextmenu-separator { + height: 1px; + font-size: 0; + margin: 5px 6px; + background: #333; +} + +.mejs-contextmenu .mejs-contextmenu-item { + font-family: Helvetica, Arial; + font-size: 12px; + padding: 4px 6px; + cursor: pointer; + color: #333; +} +.mejs-contextmenu .mejs-contextmenu-item:hover { + background: #2C7C91; + color: #fff; +} + +/* Start: Source Chooser */ +.mejs-controls .mejs-sourcechooser-button { + position: relative; +} + +.mejs-controls .mejs-sourcechooser-button button { + background-position: -128px 0; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector { + visibility: hidden; + position: absolute; + bottom: 26px; + right: -10px; + width: 130px; + height: 100px; + background: url(/img/media/background.png); + background: rgba(50,50,50,0.7); + border: solid 1px transparent; + padding: 10px; + overflow: hidden; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul { + margin: 0; + padding: 0; + display: block; + list-style-type: none !important; + overflow: hidden; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul li { + margin: 0 0 6px 0; + padding: 0; + list-style-type: none !important; + display: block; + color: #fff; + overflow: hidden; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul li input { + clear: both; + float: left; + margin: 3px 3px 0 5px; +} + +.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector ul li label { + width: 100px; + float: left; + padding: 4px 0 0 0; + line-height: 15px; + font-family: helvetica, arial; + font-size: 10px; +} +/* End: Source Chooser */ + +/* Start: Postroll */ +.mejs-postroll-layer { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + background: url(/img/media/background.png); + background: rgba(50,50,50,0.7); + z-index: 1000; + overflow: hidden; +} +.mejs-postroll-layer-content { + width: 100%; + height: 100%; +} +.mejs-postroll-close { + position: absolute; + right: 0; + top: 0; + background: url(/img/media/background.png); + background: rgba(50,50,50,0.7); + color: #fff; + padding: 4px; + z-index: 100; + cursor: pointer; +} +/* End: Postroll */ +\ No newline at end of file diff --git a/src/main/less/styles.less b/src/main/less/styles.less @@ -0,0 +1,239 @@ +/********************************************************** + * Import the files you want to use. This is a custom + * version of bootstrap.less with responsive + * support included. + ******************************************************** + */ + +// CSS Reset +@import "bootstrap/reset.less"; + +// Core variables and mixins +@import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "bootstrap/mixins.less"; + +// Grid system and page structure +@import "bootstrap/scaffolding.less"; +@import "bootstrap/grid.less"; +@import "bootstrap/layouts.less"; + +// Base CSS +@import "bootstrap/type.less"; +@import "bootstrap/code.less"; +@import "bootstrap/forms.less"; +@import "bootstrap/tables.less"; + +// Components: common +@import "bootstrap/sprites.less"; +@import "bootstrap/dropdowns.less"; +@import "bootstrap/wells.less"; +@import "bootstrap/component-animations.less"; +@import "bootstrap/close.less"; + +// Components: Buttons & Alerts +@import "bootstrap/buttons.less"; +@import "bootstrap/button-groups.less"; +@import "bootstrap/alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less + +// Components: Nav +@import "bootstrap/navs.less"; +@import "bootstrap/navbar.less"; +@import "bootstrap/breadcrumbs.less"; +@import "bootstrap/pagination.less"; +@import "bootstrap/pager.less"; + +// Components: Popovers +@import "bootstrap/modals.less"; +@import "bootstrap/tooltip.less"; +@import "bootstrap/popovers.less"; + +// Components: Misc +@import "bootstrap/thumbnails.less"; +@import "bootstrap/media.less"; +@import "bootstrap/labels-badges.less"; +@import "bootstrap/progress-bars.less"; +@import "bootstrap/accordion.less"; +@import "bootstrap/carousel.less"; +@import "bootstrap/hero-unit.less"; + +// Media Element +@import "mediaelementplayer.less"; + +// This needs to be set before the responsive classes when using .navbar-fixed-top; +body { + padding-top: 40px; +} + +// RESPONSIVE CLASSES +// ------------------ + +@import "bootstrap/responsive-utilities.less"; + + +// MEDIA QUERIES +// ------------------ + +// Large desktops +@import "bootstrap/responsive-1200px-min.less"; + +// Tablets to regular desktops +@import "bootstrap/responsive-768px-979px.less"; + +// Phones to portrait tablets and narrow desktops +@import "bootstrap/responsive-767px-max.less"; + + +// RESPONSIVE NAVBAR +// ------------------ + +// From 979px and below, show a button to toggle navbar contents +@import "bootstrap/responsive-navbar.less"; + + +// Utility classes +@import "bootstrap/utilities.less"; // Has to be last to override when necessary + +/*********************************************************** + * Custom styles + ********************************************************** + */ +// @import "custom.less"; + + +// Override some defaults +html, body { + padding-bottom: 40px; +} +body { overflow-y: scroll; } + +.main-content { + background-color: @white; + padding: 20px; + margin: 0 -20px; /* negative indent the amount of the padding to maintain the grid system */ + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,.15); + box-shadow: 0 1px 2px rgba(0,0,0,.15); +} + +.hero-unit { + h2 { + font-size: 36px; + } +} + +// FORM FIELD FEEDBACK STATES +// -------------------------- + +/** + * Default is to change the color of the help text to the same as the alert. + * This returns help text to original color. + */ +.customFormFieldState(@groupColor) { + .help-block, + .help-inline { + color: lighten(@textColor, 15%); // lighten the text some for contrast + } +} + +// Warning +.control-group.warning { + .customFormFieldState(@warningText); +} +// Error +.control-group.error { + .customFormFieldState(@errorText); +} +// Success +.control-group.success { + .customFormFieldState(@successText); +} +// Info +.control-group.info { + .customFormFieldState(@infoText); +} + +/** + * For coloring id notices + */ +.alert-block { + .warning { + color: @warningText; + } + .error { + color: @errorText; + } + .success { + color: @successText; + } + .info { + color: @infoText; + } +} + +#user img { + display: inline-block; + width: 20px; + height: 20px; + vertical-align: top; + margin-right: 6px; + border: none; + cursor: pointer; +} + +#user-header { + line-height: 4em; + + img { + display: inline-block; + vertical-align: middle; + margin-right: 6px; + border: none; + cursor: pointer; + } + + h3 { + display: inline-block; + } +} + +div.gravatar { + float:left; + margin-left:10px; + max-width: 320px; + + img { + padding-top:10px; + } +} + +// fixes gap between navbar and section below it +@media (max-width: @navbarCollapseWidth) { + .navbar-fixed-top { + margin-bottom: 0px; + } +} + +.signup-overlay { + position: absolute; + left: 0; + top: @navbarHeight + 40; + z-index: 1010; + background-color:#fff; +} + +#writer-list { + label { + display: inline-block; + cursor: pointer; + img { + width: 100px; + height: 100px; + padding: 3px; + } + input[type="radio"] { display: none; } + } + .selected { background-color: #ccc; } +} +\ No newline at end of file diff --git a/src/main/less/variables.less b/src/main/less/variables.less @@ -0,0 +1,301 @@ +// +// Variables +// -------------------------------------------------- + + +// Global values +// -------------------------------------------------- + + +// Grays +// ------------------------- +@black: #000; +@grayDarker: #222; +@grayDark: #333; +@gray: #555; +@grayLight: #999; +@grayLighter: #eee; +@white: #fff; + + +// Accent colors +// ------------------------- +@blue: #049cdb; +@blueDark: #0064cd; +@green: #46a546; +@red: #9d261d; +@yellow: #ffc40d; +@orange: #f89406; +@pink: #c3325f; +@purple: #7a43b6; + + +// Scaffolding +// ------------------------- +@bodyBackground: @white; +@textColor: @grayDark; + + +// Links +// ------------------------- +@linkColor: #08c; +@linkColorHover: darken(@linkColor, 15%); + + +// Typography +// ------------------------- +@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif; +@serifFontFamily: Georgia, "Times New Roman", Times, serif; +@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace; + +@baseFontSize: 14px; +@baseFontFamily: @sansFontFamily; +@baseLineHeight: 20px; +@altFontFamily: @serifFontFamily; + +@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily +@headingsFontWeight: bold; // instead of browser default, bold +@headingsColor: inherit; // empty to use BS default, @textColor + + +// Component sizing +// ------------------------- +// Based on 14px font-size and 20px line-height + +@fontSizeLarge: @baseFontSize * 1.25; // ~18px +@fontSizeSmall: @baseFontSize * 0.85; // ~12px +@fontSizeMini: @baseFontSize * 0.75; // ~11px + +@paddingLarge: 11px 19px; // 44px +@paddingSmall: 2px 10px; // 26px +@paddingMini: 1px 6px; // 24px + +@baseBorderRadius: 4px; +@borderRadiusLarge: 6px; +@borderRadiusSmall: 3px; + + +// Tables +// ------------------------- +@tableBackground: transparent; // overall background-color +@tableBackgroundAccent: #f9f9f9; // for striping +@tableBackgroundHover: #f5f5f5; // for hover +@tableBorder: #ddd; // table and cell border + +// Buttons +// ------------------------- +@btnBackground: @white; +@btnBackgroundHighlight: darken(@white, 10%); +@btnBorder: #bbb; + +@btnPrimaryBackground: @linkColor; +@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 20%); + +@btnInfoBackground: #5bc0de; +@btnInfoBackgroundHighlight: #2f96b4; + +@btnSuccessBackground: #62c462; +@btnSuccessBackgroundHighlight: #51a351; + +@btnWarningBackground: lighten(@orange, 15%); +@btnWarningBackgroundHighlight: @orange; + +@btnDangerBackground: #ee5f5b; +@btnDangerBackgroundHighlight: #bd362f; + +@btnInverseBackground: #444; +@btnInverseBackgroundHighlight: @grayDarker; + + +// Forms +// ------------------------- +@inputBackground: @white; +@inputBorder: #ccc; +@inputBorderRadius: @baseBorderRadius; +@inputDisabledBackground: @grayLighter; +@formActionsBackground: #f5f5f5; +@inputHeight: @baseLineHeight + 10px; // base line-height + 8px vertical padding + 2px top/bottom border + + +// Dropdowns +// ------------------------- +@dropdownBackground: @white; +@dropdownBorder: rgba(0,0,0,.2); +@dropdownDividerTop: #e5e5e5; +@dropdownDividerBottom: @white; + +@dropdownLinkColor: @grayDark; +@dropdownLinkColorHover: @white; +@dropdownLinkColorActive: @dropdownLinkColor; + +@dropdownLinkBackgroundActive: @linkColor; +@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive; + + + +// COMPONENT VARIABLES +// -------------------------------------------------- + + +// Z-index master list +// ------------------------- +// Used for a bird's eye view of components dependent on the z-axis +// Try to avoid customizing these :) +@zindexDropdown: 1000; +@zindexPopover: 1010; +@zindexTooltip: 1030; +@zindexFixedNavbar: 1030; +@zindexModalBackdrop: 1040; +@zindexModal: 1050; + + +// Sprite icons path +// ------------------------- +@iconSpritePath: "../img/glyphicons-halflings.png"; +@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png"; + + +// Input placeholder text color +// ------------------------- +@placeholderText: @grayLight; + + +// Hr border color +// ------------------------- +@hrBorder: @grayLighter; + + +// Horizontal forms & lists +// ------------------------- +@horizontalComponentOffset: 180px; + + +// Wells +// ------------------------- +@wellBackground: #f5f5f5; + + +// Navbar +// ------------------------- +@navbarCollapseWidth: 768px; +@navbarCollapseDesktopWidth: @navbarCollapseWidth + 1; + +@navbarHeight: 40px; +@navbarBackgroundHighlight: #ffffff; +@navbarBackground: darken(@navbarBackgroundHighlight, 5%); +@navbarBorder: darken(@navbarBackground, 12%); + +@navbarText: #777; +@navbarLinkColor: #777; +@navbarLinkColorHover: @grayDark; +@navbarLinkColorActive: @gray; +@navbarLinkBackgroundHover: transparent; +@navbarLinkBackgroundActive: darken(@navbarBackground, 5%); + +@navbarBrandColor: @navbarLinkColor; + +// Inverted navbar +@navbarInverseBackground: #111111; +@navbarInverseBackgroundHighlight: #222222; +@navbarInverseBorder: #252525; + +@navbarInverseText: @grayLight; +@navbarInverseLinkColor: @grayLight; +@navbarInverseLinkColorHover: @white; +@navbarInverseLinkColorActive: @navbarInverseLinkColorHover; +@navbarInverseLinkBackgroundHover: transparent; +@navbarInverseLinkBackgroundActive: @navbarInverseBackground; + +@navbarInverseSearchBackground: lighten(@navbarInverseBackground, 25%); +@navbarInverseSearchBackgroundFocus: @white; +@navbarInverseSearchBorder: @navbarInverseBackground; +@navbarInverseSearchPlaceholderColor: #ccc; + +@navbarInverseBrandColor: @navbarInverseLinkColor; + + +// Pagination +// ------------------------- +@paginationBackground: #fff; +@paginationBorder: #ddd; +@paginationActiveBackground: #f5f5f5; + + +// Hero unit +// ------------------------- +@heroUnitBackground: @grayLighter; +@heroUnitHeadingColor: inherit; +@heroUnitLeadColor: inherit; + + +// Form states and alerts +// ------------------------- +@warningText: #c09853; +@warningBackground: #fcf8e3; +@warningBorder: darken(spin(@warningBackground, -10), 3%); + +@errorText: #b94a48; +@errorBackground: #f2dede; +@errorBorder: darken(spin(@errorBackground, -10), 3%); + +@successText: #468847; +@successBackground: #dff0d8; +@successBorder: darken(spin(@successBackground, -10), 5%); + +@infoText: #3a87ad; +@infoBackground: #d9edf7; +@infoBorder: darken(spin(@infoBackground, -10), 7%); + + +// Tooltips and popovers +// ------------------------- +@tooltipColor: #fff; +@tooltipBackground: #000; +@tooltipArrowWidth: 5px; +@tooltipArrowColor: @tooltipBackground; + +@popoverBackground: #fff; +@popoverArrowWidth: 10px; +@popoverArrowColor: #fff; +@popoverTitleBackground: darken(@popoverBackground, 3%); + +// Special enhancement for popovers +@popoverArrowOuterWidth: @popoverArrowWidth + 1; +@popoverArrowOuterColor: rgba(0,0,0,.25); + + + +// GRID +// -------------------------------------------------- + + +// Default 940px grid +// ------------------------- +@gridColumns: 12; +@gridColumnWidth: 60px; +@gridGutterWidth: 20px; +@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); + +// 1200px min +@gridColumnWidth1200: 70px; +@gridGutterWidth1200: 30px; +@gridRowWidth1200: (@gridColumns * @gridColumnWidth1200) + (@gridGutterWidth1200 * (@gridColumns - 1)); + +// 768px-979px +@gridColumnWidth768: 42px; +@gridGutterWidth768: 20px; +@gridRowWidth768: (@gridColumns * @gridColumnWidth768) + (@gridGutterWidth768 * (@gridColumns - 1)); + + +// Fluid grid +// ------------------------- +@fluidGridColumnWidth: percentage(@gridColumnWidth/@gridRowWidth); +@fluidGridGutterWidth: percentage(@gridGutterWidth/@gridRowWidth); + +// 1200px min +@fluidGridColumnWidth1200: percentage(@gridColumnWidth1200/@gridRowWidth1200); +@fluidGridGutterWidth1200: percentage(@gridGutterWidth1200/@gridRowWidth1200); + +// 768px-979px +@fluidGridColumnWidth768: percentage(@gridColumnWidth768/@gridRowWidth768); +@fluidGridGutterWidth768: percentage(@gridGutterWidth768/@gridRowWidth768); diff --git a/src/main/resources/default.logback.xml b/src/main/resources/default.logback.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration debug="false"> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> + <pattern>[%level] %logger{36} - %msg%n%nopex</pattern> + </encoder> + </appender> + + <root level="INFO"> + <appender-ref ref="STDOUT" /> + </root> +</configuration> diff --git a/src/main/resources/production.default.logback.xml b/src/main/resources/production.default.logback.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration debug="true"> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> + <pattern>[%level] %logger{36} - %msg%n%ex{20}</pattern> + </encoder> + </appender> + + <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender"> + <SMTPHost></SMTPHost> + <SMTPPort></SMTPPort> + <SSL></SSL> + <Username></Username> + <Password></Password> + <To></To> + <From></From> + <Subject>Production: %logger{20} - %m</Subject> + + <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> + <level>ERROR</level> + </filter> + + <layout class="ch.qos.logback.classic.PatternLayout"> + <pattern>%d{HH:mm:ss.SSS} [%thread] - %msg%n%mdc%n</pattern> + </layout> + + <cyclicBufferTracker class="ch.qos.logback.core.spi.CyclicBufferTrackerImpl"> + <bufferSize>100</bufferSize> + </cyclicBufferTracker> + + <!-- + <layout class="ch.qos.logback.classic.html.HTMLLayout"> + <pattern>%relative%thread%mdc%level%logger%msg</pattern> + </layout> + --> + </appender> + + <logger name="net.liftweb.util.TimeHelpers" level="WARN" /> + + <root level="INFO"> + <appender-ref ref="STDOUT" /> + <appender-ref ref="EMAIL" /> + </root> +</configuration> diff --git a/src/main/resources/props/default.props b/src/main/resources/props/default.props @@ -0,0 +1,13 @@ +mail.smtp.host=email-smtp.us-east-1.amazonaws.com +mail.smtp.user= +mail.smtp.pass= +mail.smtp.port=587 +mail.smtp.auth=true + +aws.access.key= +aws.secret.access.key= +aws.s3.bucket= + +mongo.default.url= +mongo.default.user= +mongo.default.pwd= +\ No newline at end of file diff --git a/src/main/resources/props/production.default.props b/src/main/resources/props/production.default.props @@ -0,0 +1,3 @@ +mail.smtp.host=smtp.gmail.com +mail.smtp.user= +mail.smtp.pass= diff --git a/src/main/scala/bootstrap/liftweb/Boot.scala b/src/main/scala/bootstrap/liftweb/Boot.scala @@ -0,0 +1,92 @@ +package bootstrap.liftweb + +import scala.xml.{Null, UnprefixedAttribute} +import javax.mail.internet.MimeMessage + +import net.liftweb._ +import common._ +import http._ +import util._ +import util.Helpers._ + +import com.joereader.config._ +import com.joereader.model.{SystemUser, User} + +import net.liftmodules.extras.{Gravatar, LiftExtras} +import net.liftmodules.mongoauth.MongoAuth +import net.liftweb.http.SHtml.ChoiceHolder +import net.liftweb.sitemap.SiteMap + +/** + * A class that's instantiated early and run. It allows the application + * to modify lift's environment + */ +class Boot extends Loggable { + def boot { + logger.info("Run Mode: "+Props.mode.toString) + + // init mongodb + MongoConfig.init() + + // init auth-mongo + MongoAuth.authUserMeta.default.set(User) + MongoAuth.loginTokenAfterUrl.default.set(Site.password.url) + MongoAuth.siteName.default.set("Joe Reader") + MongoAuth.systemEmail.default.set(SystemUser.user.email.is) + MongoAuth.systemUsername.default.set(SystemUser.user.name.is) + + // For S.loggedIn_? and TestCond.loggedIn/Out builtin snippet + LiftRules.loggedInTest = Full(() => User.isLoggedIn) + + // Gravatar + Gravatar.defaultImage.default.set("jcabrra.files.wordpress.com/2013/06/mr_noman.jpg") + + // config an email sender + SmtpMailer.init + + // where to search snippet + LiftRules.addToPackages("com.joereader") + + // set the default htmlProperties + LiftRules.htmlProperties.default.set((r: Req) => + new Html5Properties(r.userAgent)) + + // Build SiteMap + SiteMap.enforceUniqueLinks = false + LiftRules.setSiteMap(Site.siteMap) + + // Error handler + ErrorHandler.init + + // 404 handler + LiftRules.uriNotFound.prepend(NamedPF("404handler") { + case (req, failure) => + NotFoundAsTemplate(ParsePath(List("404"), "html", false, false)) + }) + + // Show the spinny image when an Ajax call starts + LiftRules.ajaxStart = + Full(() => LiftRules.jsArtifacts.show("ajax-spinner").cmd) + + // Make the spinny image go away when it ends + LiftRules.ajaxEnd = + Full(() => LiftRules.jsArtifacts.hide("ajax-spinner").cmd) + + // Force the request to be UTF-8 + LiftRules.early.append(_.setCharacterEncoding("UTF-8")) + + // Init Extras + LiftExtras.init() + + // don't include the liftAjax.js code. It's served statically. + LiftRules.autoIncludeAjaxCalc.default.set(() => + (session: LiftSession) => false) + + // Twitter Bootstrap css fix radio inputs by surrounding with label + ChoiceHolder.htmlize = ci => evalElemWithId((id,e) => + <label for={id}>{e} {ci.key.toString}</label>)(ci.xhtml) + + // API + LiftRules.dispatch.append(com.joereader.api.ImageUpload) +} +} diff --git a/src/main/scala/com/joereader/api/ImageUpload.scala b/src/main/scala/com/joereader/api/ImageUpload.scala @@ -0,0 +1,110 @@ +package com.joereader.api + +import net.liftweb.http.rest._ +import net.liftweb.common._ +import net.liftweb.http._ +import net.liftweb.util._ +import com.joereader.lib.aws._ +import com.joereader.config.S3Config._ +import com.joereader.model._ +import concurrent.ExecutionContext.Implicits.global + +object ImageUpload extends RestHelper with Logger { + + val acceptedImages = "image/jpeg" :: "image/png" :: "image/svg+xml" :: Nil + + def userUrl(imgType: String = "") = + "/api/user/upload/image"+includeImgType(imgType) + + def blogUrl(blog: Blog, imgType: String = "") = + "/api/blog/"+blog.id.is+"/upload/image"+includeImgType(imgType) + + def includeImgType(imgType: String) = + if(imgType.nonEmpty) "?type="+imgType else "" + + serve { + + case "api" :: "user" :: "upload" :: "image" :: Nil Post req => + + val imgType = S.param("type") getOrElse "" + + if(User.currentUser.isEmpty) + ResponseWithReason(BadResponse(), "Must be logged in.") + + else if(req.uploadedFiles.exists(file => acceptedImages.filter(file.mimeType == _).isEmpty)) + ResponseWithReason(BadResponse(), "Only JPEG, PNG and SVG are allowed.") + + else if(imgType == "bg" && req.uploadedFiles.exists(f => f.length > 5243000)) // 5 MB - bg img + ResponseWithReason(BadResponse(), "Image is too big. Must be 1MB or smaller.") + + else if(imgType != "bg" && req.uploadedFiles.exists(f => f.length > 2097000)) // 2 MB - logo + ResponseWithReason(BadResponse(), "Image is too big. Must be 2MB or smaller.") + + else { + for(user <- User.currentUser; file <- req.uploadedFiles) { + + val fn = StringHelpers.randomString(32) + val s3 = new S3(s3_access_key.get, s3_secret_key.get, s3_bucket.get) + val s3f = s3.createFile(fn, file.file, file.mimeType) + + if(imgType == "bg") { + if(user.bgImg.is != "") {val s3f = s3.deleteFile(user.bgImg.is)} + user.bgImg(fn).update + } + else { + if(user.img.is != "") {val s3f = s3.deleteFile(user.img.is)} + user.img(fn).update + } + + info("Successfully saved: "+fn+" for "+user.name.is) + } + OkResponse() + } + + + case "api" :: "blog" :: id :: "upload" :: "image" :: Nil Post req => + + val blog = Blog.findByStringId(id) + val user = User.currentUser + val imgType = S.param("type") getOrElse "" + + if(blog.isEmpty) + ResponseWithReason(BadResponse(), "Blog id does not exist.") + + else if(user.isEmpty) + ResponseWithReason(BadResponse(), "Must be logged in.") + + else if(user.exists(u => blog.exists(_.isOwner(u)))) + ResponseWithReason(BadResponse(), "You do not have permission to control the requested blog.") + + else if (req.uploadedFiles.exists(file => acceptedImages.filter(file.mimeType == _).isEmpty)) + ResponseWithReason(BadResponse(), "Only JPEG, PNG and SVG are allowed.") + + else if(imgType == "bg" && req.uploadedFiles.exists(f => f.length > 5243000)) // 5 MB - bg img + ResponseWithReason(BadResponse(), "Image is too big. Must be 1MB or smaller.") + + else if(imgType != "bg" && req.uploadedFiles.exists(f => f.length > 2097000)) // 2 MB - logo + ResponseWithReason(BadResponse(), "Image is too big. Must be 2MB or smaller.") + + else { + for(blog <- blog; file <- req.uploadedFiles) { + + val fn = StringHelpers.randomString(32) + val s3 = new S3(s3_access_key.get, s3_secret_key.get, s3_bucket.get) + val s3f = s3.createFile(fn, file.file, file.mimeType) + + if(imgType == "bg") { + if(blog.bgImg.is != "") {val s3f = s3.deleteFile(blog.bgImg.is)} + blog.bgImg(fn).update + } + else { + if(blog.img.is != "") {val s3f = s3.deleteFile(blog.img.is)} + blog.img(fn).update + } + + info("Successfully saved: "+fn+" for "+blog.name.is) + } + OkResponse() + } + } +} diff --git a/src/main/scala/com/joereader/comet/UserSignUp.scala b/src/main/scala/com/joereader/comet/UserSignUp.scala @@ -0,0 +1,124 @@ +package com.joereader.comet + +import net.liftweb.http._ +import net.liftweb.common._ +import net.liftweb.http.js.{JsCmd, JE, JsCmds} +import net.liftweb.actor.LiftActor +import net.liftweb.http.js.JsCmds._ +import net.liftweb.http.SHtml._ +import com.joereader.model._ +import net.liftweb.json.ext.JodaTimeSerializers +import net.liftweb.json._ +import scala.xml.{Text, NodeSeq} +import net.liftweb.util.CssSel +import com.joereader.config.Site +import net.liftmodules.extras.{Gravatar, SnippetHelper} +import net.liftweb.util.Helpers._ +import java.text.SimpleDateFormat + + +sealed trait UserSnippet extends SnippetHelper with Loggable { + + protected def user: Box[User] + + def serve(snip: User => NodeSeq): NodeSeq = + (for { + u <- user ?~ "User not found" + } yield { + snip(u) + }): NodeSeq + + def serve(html: NodeSeq)(snip: User => CssSel): NodeSeq = + (for { + u <- user ?~ "User not found" + } yield { + snip(u)(html) + }): NodeSeq + + def header(xhtml: NodeSeq): NodeSeq = serve { user => + <div id="user-header"> + {gravatar(xhtml)} + <h3>{name(xhtml)}</h3> + </div> + } + + def gravatar(xhtml: NodeSeq): NodeSeq = { + val size = S.attr("size").map(toInt) openOr Gravatar.defaultSize.vend + + serve { user => + Gravatar.imgTag(user.email.is, size) + } + } + + def username(xhtml: NodeSeq): NodeSeq = serve { user => + Text(user.username.is) + } + + def name(xhtml: NodeSeq): NodeSeq = serve { user => + if (user.name.is.length > 0) + Text("%s (%s)".format(user.name.is, user.username.is)) + else + Text(user.username.is) + } + + def title(xhtml: NodeSeq): NodeSeq = serve { user => + <lift:head> + <title lift="Menu.title">{"Joe Reader: %*% - "+user.username.is}</title> + </lift:head> + } +} + +object CurrentUser extends UserSnippet { + def user = User.currentUser +} + + + +abstract class UserProfileComet extends CometActor with CometListener with UserSnippet { + + protected def user: Box[User] = Full(User.createRecord) + + val df = new SimpleDateFormat("MMM d, yyyy") + + protected def registerWith = UserActor + + override def lowPriority = { + case cmd: JsCmd => + partialUpdate(cmd) + } + /* + def render = + "#id_avatar *" #> Gravatar.imgTag(user.email.is) & + "#id_name *" #> <h3>{user.name.is}</h3> & + "#id_location *" #> user.location.is & + "#id_whencreated" #> df.format(user.whenCreated.toDate).toString & + "#id_bio *" #> user.bio.is & + "#id_editlink *" #> editLink + */ +} + +object UserActor extends LiftActor with ListenerManager { + private var msgs: Vector[String] = Vector("") // private state + + /** + * When we update the listeners, what message do we send? + * We send the msgs, which is an immutable data structure, + * so it can be shared with lots of threads without any + * danger or locking. + */ + def createUpdate = msgs + + /** + * process messages that are sent to the Actor. In + * this case, we're looking for Strings that are sent + * to the ChatServer. We append them to our Vector of + * messages, and then update all the listeners. + */ + override def lowPriority = { + case m: String => + msgs :+= m + updateListeners() + + } +} + diff --git a/src/main/scala/com/joereader/config/ErrorHandler.scala b/src/main/scala/com/joereader/config/ErrorHandler.scala @@ -0,0 +1,64 @@ +package com.joereader +package config + +import model.User + +import net.liftweb._ +import common.{Loggable, MDC} +import http.{Factory, LiftRules, RedirectResponse, Req, S, XhtmlResponse} +import util.Props + +object ErrorHandler extends Factory with Loggable { + // config + val errorUrl = new FactoryMaker[String]("/error") {} // where to send the user when an error occurs + + def init(): Unit = { + LiftRules.exceptionHandler.prepend { + case (Props.RunModes.Development, r, e) => + logException(r, e) + XhtmlResponse( + (<html><body>Exception occured while processing {r.uri}<pre>{showException(e)}</pre></body></html>), + S.htmlProperties.docType, + List("Content-Type" -> "text/html; charset=utf-8"), + Nil, + 500, + S.ieMode + ) + case (_, r, e) => + logException(r, e) + RedirectResponse(errorUrl.vend) + } + } + + /* + * Log the exception with some user info. + */ + def logException(r: Req, e: Throwable) { + import java.net.InetAddress + val srvr = InetAddress.getLocalHost.getHostName + + MDC.put(("UserId", User.currentUserId openOr "GUEST")) + MDC.put(("Username", User.currentUser.map(_.username.is) openOr "GUEST")) + MDC.put(("User Agent", r.userAgent openOr "UNKNOWN")) + MDC.put(("Server", srvr)) + logger.error("Exception occurred while processing %s".format(r.uri), e) + } + + /** + * A utility method to convert an exception to a string of stack traces + * @param le the exception + * + * @return the stack trace + */ + def showException(le: Throwable): String = { + val ret = "Message: " + le.toString + "\n\t" + + le.getStackTrace.map(_.toString).mkString("\n\t") + "\n" + + val also = le.getCause match { + case null => "" + case sub: Throwable => "\nCaught and thrown by:\n" + showException(sub) + } + + ret + also + } +} +\ No newline at end of file diff --git a/src/main/scala/com/joereader/config/MongoConfig.scala b/src/main/scala/com/joereader/config/MongoConfig.scala @@ -0,0 +1,51 @@ +package com.joereader +package config + +import net.liftweb._ +import common._ +import json._ +import mongodb._ +import util.Props + +import com.mongodb.{DBAddress, Mongo} + +object MongoConfig extends Loggable { + + def init() { + /** + * First checks for existence of mongo.default.url. If not found, then + * checks for mongo.default.host, port, and name. Uses defaults if those + * are not found. + */ + val defaultDbAddress = Props.get("mongo.default.url") + .map(url => new DBAddress(url)) + .openOr(new DBAddress( + Props.get("mongo.default.host", "127.0.0.1"), + Props.getInt("mongo.default.port", 27017), + Props.get("mongo.default.name", "joe-reader") + )) + + /* + * If mongo.default.user, and pwd are defined, configure Mongo using authentication. + */ + (Props.get("mongo.default.user"), Props.get("mongo.default.pwd")) match { + case (Full(user), Full(pwd)) => + MongoDB.defineDbAuth( + DefaultMongoIdentifier, + new Mongo(defaultDbAddress), + defaultDbAddress.getDBName, + user, + pwd + ) + logger.info("MongoDB inited using authentication: %s".format(defaultDbAddress.toString)) + case _ => + MongoDB.defineDb( + DefaultMongoIdentifier, + new Mongo(defaultDbAddress), + defaultDbAddress.getDBName + ) + logger.info("MongoDB inited: %s".format(defaultDbAddress.toString)) + } + } +} + diff --git a/src/main/scala/com/joereader/config/S3Config.scala b/src/main/scala/com/joereader/config/S3Config.scala @@ -0,0 +1,13 @@ +package com.joereader.config + +import net.liftweb.util.Props +import com.joereader.lib.aws._ + +// AWS +object S3Config { + val s3_access_key = Props.get("aws.access.key") + val s3_secret_key = Props.get("aws.secret.access.key") + val s3_bucket = Props.get("aws.s3.bucket") + + val s3 = new S3(s3_access_key.get, s3_secret_key.get, s3_bucket.get) +} diff --git a/src/main/scala/com/joereader/config/Site.scala b/src/main/scala/com/joereader/config/Site.scala @@ -0,0 +1,101 @@ +package com.joereader +package config + +import model._ + +import net.liftweb._ +import net.liftweb.http.{Templates, S} +import sitemap._ +import sitemap.Loc._ + +import net.liftmodules.mongoauth.Locs + +object MenuGroups { + val SettingsGroup = LocGroup("settings") + val TopBarGroup = LocGroup("topbar") +} + +/* + * Wrapper for Menu locations + */ +case class MenuLoc(menu: Menu) { + lazy val url: String = S.contextPath+menu.loc.calcDefaultHref + lazy val fullUrl: String = S.hostAndPath+menu.loc.calcDefaultHref +} + +case class ParamInfo(theParam: String) +object Site extends Locs { + import MenuGroups._ + + val notFound = MenuLoc(Menu.i("404") / "404" >> Hidden) + val error = MenuLoc(Menu.i("Error") / "error" >> Hidden) + + // locations (menu entries) + val home = MenuLoc(Menu.i("Home") / "index" >> TopBarGroup) + val about = MenuLoc(Menu.i("About") / "about" >> TopBarGroup) + val login = MenuLoc(Menu.i("Login") / "login" >> RequireNotLoggedIn) + val loginToken = MenuLoc(buildLoginTokenMenu) + val logout = MenuLoc(buildLogoutMenu) + + private val userProfileParamMenu = Menu.param[User]("User", "Profile", + User.findByUsername _, _.username.is) / * >> TemplateBox(() => Templates("user" :: Nil)) + lazy val userProfileLoc = userProfileParamMenu.toLoc + + private val blogProfileParamMenu = Menu.param[Blog]("Blog", "Profile", + Blog.findByBlogName _, _.blogname.is) / "blog" + lazy val blogProfileLoc = blogProfileParamMenu.toLoc + + val signUp1 = MenuLoc(Menu.i("Sign Up ⋅ Writer") / "signup" / "writer" >> RequireNotLoggedIn) + val signUp2 = MenuLoc(Menu.i("Sign Up ⋅ Verify") / "signup" / "verify" >> RequireNotLoggedIn) + val signUp3 = MenuLoc(Menu.i("Sign Up ⋅ Categories") / "signup" / "categories" >> RequireLoggedIn) + val signUp4 = MenuLoc(Menu.i("Sign Up ⋅ Blog") / "signup" / "blog" >> RequireLoggedIn) + val signUp5 = MenuLoc(Menu.i("Sign Up ⋅ User") / "signup" / "user" >> RequireLoggedIn) + val signUp6 = MenuLoc(Menu.i("Sign Up ⋅ Password") / "signup" / "password" >> RequireLoggedIn) + + val reader = MenuLoc(Menu.i("Reader") / "reader" >> RequireLoggedIn) + + val password = MenuLoc(Menu.i("Password Reset") / "settings" / "password" >> RequireLoggedIn >> Hidden) + val account = MenuLoc(Menu.i("Account") / "settings" / "account" >> SettingsGroup >> RequireLoggedIn) + val editProfile = MenuLoc(Menu("EditProfile", "Profile") / "settings" / "profile" >> SettingsGroup >> RequireLoggedIn) + val register = MenuLoc(Menu.i("Register") / "register" >> RequireNotLoggedIn) + + val passwordRecovery = MenuLoc(Menu.i("Password Recovery") / "help" / "recovery" >> RequireNotLoggedIn) + + private def menus = List( + userProfileParamMenu, + blogProfileParamMenu, + home.menu, + about.menu, + login.menu, + register.menu, + loginToken.menu, + logout.menu, + signUp1.menu, + signUp2.menu, + signUp3.menu, + signUp4.menu, + signUp5.menu, + signUp6.menu, + reader.menu, + account.menu, + password.menu, + editProfile.menu, + passwordRecovery.menu, + error.menu, + notFound.menu, + Menu.i("Throw") / "throw" >> EarlyResponse(() => throw new Exception("This is only a test.")) + ) + + /* + * Return a SiteMap needed for Lift + */ + def siteMap: SiteMap = SiteMap(menus:_*) + + val invalidUsernames = siteMap.menus.map{ menu => + val path = menu.loc.calcDefaultHref + if(path.startsWith("/")) path.split("/")(1) + else "" + } + def isAvailableMenu(n: String) = invalidUsernames.forall(_ != n) + +} diff --git a/src/main/scala/com/joereader/config/SmtpMailer.scala b/src/main/scala/com/joereader/config/SmtpMailer.scala @@ -0,0 +1,71 @@ +package com.joereader +package config + +import javax.mail.{Authenticator, PasswordAuthentication} +import javax.mail.internet.MimeMessage + +import net.liftweb._ +import common._ +import util._ + +/* + * A Mailer config object that uses Props and auto configures for gmail + * if detected + */ +object SmtpMailer extends Loggable { + def init(): Unit = { + + var isAuth = Props.get("mail.smtp.auth", "false").toBoolean + + Mailer.customProperties = Props.get("mail.smtp.host", "localhost") match { + case "smtp.gmail.com" => // auto configure for gmail + isAuth = true + Map( + "mail.smtp.host" -> "smtp.gmail.com", + "mail.smtp.port" -> "587", + "mail.smtp.auth" -> "true", + "mail.smtp.starttls.enable" -> "true" + ) + case h => Map( + "mail.smtp.host" -> h, + "mail.smtp.port" -> Props.get("mail.smtp.port", "25"), + "mail.smtp.auth" -> isAuth.toString + ) + } + + /* + Mailer.devModeSend.default.set((m: MimeMessage) => + logger.info("Dev mode message:\n" + prettyPrintMime(m))) + */ + + if (isAuth) { + (Props.get("mail.smtp.user"), Props.get("mail.smtp.pass")) match { + case (Full(username), Full(password)) => + logger.info("Smtp user: %s".format(username)) + logger.info("Smtp password length: %s".format(password.length)) + Mailer.authenticator = Full(new Authenticator() { + override def getPasswordAuthentication = new + PasswordAuthentication(username, password) + }) + logger.info("SmtpMailer inited") + case _ => logger.error("Username/password not supplied for Mailer.") + } + } + } + + private def prettyPrintMime(m: MimeMessage): String = { + val buf = new StringBuilder + val hdrs = m.getAllHeaderLines + while (hdrs.hasMoreElements) + buf ++= hdrs.nextElement.toString + "\n" + + val out = + """ + |%s + |==================================== + |%s + """.format(buf.toString, m.getContent.toString).stripMargin + + out + } +} diff --git a/src/main/scala/com/joereader/lib/DependencyFactory.scala b/src/main/scala/com/joereader/lib/DependencyFactory.scala @@ -0,0 +1,56 @@ +package com.joereader +package lib + +import java.util.Date + +import net.liftweb._ +import http._ +import util._ +import common._ + +/** + * A factory for generating new instances of Date. You can create + * factories for each kind of thing you want to vend in your application. + * An example is a payment gateway. You can change the default implementation, + * or override the default implementation on a session, request or current call + * stack basis. + */ +object DependencyFactory extends Factory { + implicit object time extends FactoryMaker(Helpers.now _) + + /** + * objects in Scala are lazily created. The init() + * method creates a List of all the objects. This + * results in all the objects getting initialized and + * registering their types with the dependency injector + */ + private def init() { + List(time) + } + init() +} + +/* +/** + * Examples of changing the implementation + */ +sealed abstract class Changer { + def changeDefaultImplementation() { + DependencyFactory.time.default.set(() => new Date()) + } + + def changeSessionImplementation() { + DependencyFactory.time.session.set(() => new Date()) + } + + def changeRequestImplementation() { + DependencyFactory.time.request.set(() => new Date()) + } + + def changeJustForCall(d: Date) { + DependencyFactory.time.doWith(d) { + // perform some calculations here + } + } +} +*/ diff --git a/src/main/scala/com/joereader/lib/Event.scala b/src/main/scala/com/joereader/lib/Event.scala @@ -0,0 +1,46 @@ +package com.joereader.lib + +import akka.event.ActorEventBus +import akka.event.LookupClassification +import akka.actor.ActorSystem +import akka.actor.Props +import akka.actor.Actor +import java.util.Date +import java.util.UUID + + +class Message(val id:String,val timestamp: Long) + +case class PostMessage( override val id:String=UUID.randomUUID().toString(), + override val timestamp: Long=new Date().getTime(), + text:String)extends Message(id, timestamp) + +case class MessageEvent(val channel:String, val message:Message) + +class AppActorEventBus extends ActorEventBus with LookupClassification{ + type Event = MessageEvent + type Classifier=String + protected def mapSize(): Int={ + 10 + } + + protected def classify(event: Event): Classifier={ + event.channel + } + + protected def publish(event: Event, subscriber: Subscriber): Unit={ + subscriber ! event + } +} + +object TryAkka { + val system = ActorSystem("MySystem") + val appActorEventBus=new AppActorEventBus + val NEW_POST_CHANNEL="/posts/new" + val subscriber = system.actorOf(Props(new Actor { + def receive = { + case d: MessageEvent => println(d) + } + })) + +} +\ No newline at end of file diff --git a/src/main/scala/com/joereader/lib/Helper.scala b/src/main/scala/com/joereader/lib/Helper.scala @@ -0,0 +1,69 @@ +package com.joereader.lib + +import java.util.regex.Pattern + + +object Helper { + + implicit class PatternImplicitStringHelper(str: String) { + val Email = """(^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$)""".r + + def isEmail: Boolean = str match { + case Email(_) => true + case _ => false + } + + def singleQuoteToDouble: String = { + str.toList.map(_.toString).map(ch => + if(ch == "'") "\"" else ch).mkString + } + + } + + case class URLFormatter (in: String) { + val url = new java.net.URL(validate(in)) + + val ext = "htm" :: "html" :: "php" :: "php3" :: "jsp" :: "asp" :: "phtml" :: + "shtm" :: "shtml" :: "cgi" :: "cfm" :: "cfml" :: Nil + + def removeFileExtension(p: String) = { + val arr = p.split("""\.""") + val last = arr.lastOption.getOrElse("") + if(ext.exists(_ == last)) arr.slice(0,arr.length-1).mkString(".") else p + } + + /** + * If host address starts with "www." remove it. + */ + def removeWWW(h: String) = { + if(h.slice(0,4) == "www.") h.slice(4,h.length) else h + } + + def escape(s: String): String = { + val meta_regex = ".[]\\^$|?*+(){}" + s.toList. + map(_.toString). + map(ch => if(meta_regex.exists(c=> ch.exists(_==c))) "\\"+ch else ch). + mkString + } + + def pattern: Pattern = { + val s = removeWWW(url.getHost) + removeFileExtension(getPath) + val escS = escape(s) + Pattern.compile("""(https?://)?(www\.)?"""+escS+"""(\.("""+ext.mkString("|")+"""))?""", Pattern.CASE_INSENSITIVE) + } + + def getPath = if(url.getPath=="/") "" else url.getPath + + def hostAndPath = url.getHost + getPath + + /** + * Prepends http:// if it doesn't have it. + */ + def validate(str: String): String = + if(str.slice(0,8).matches("https?://.*")) str else "http://"+str + + override def toString = url.getProtocol + "://" + url.getHost + + (if (url.getPort == -1) "" else ":" + url.getPort) + url.getPath + } +} diff --git a/src/main/scala/com/joereader/lib/VideoInfo.scala b/src/main/scala/com/joereader/lib/VideoInfo.scala @@ -0,0 +1,61 @@ +package com.joereader.lib + +import dispatch._ +import xml._ +import concurrent.Future +import concurrent.ExecutionContext.Implicits.global +import scala.util.parsing.json._ + +/** + * Video information of certain video service providers. + * Supports Youtube, Vimeo, DailyMotion. + * Can only get video duration. + */ +abstract class VideoInfo { + protected def req(id: String): Req + def videoDuration(id: String): Future[Option[Int]] +} + +abstract class VideoInfoXML extends VideoInfo { + + protected def extract[T](id: String, extractFunc: Elem => Option[T]) = + for (xmlOpt <- getXML(req(id))) + yield for { + xml <- xmlOpt + out <- extractFunc(xml) + } yield out + + private def getXML(req: Req): Future[Option[Elem]] = + Http(req OK as.xml.Elem).option + + protected def node2Int(duration: NodeSeq): Option[Int] = + (for (elem <- duration) yield elem.text.toInt).headOption +} + + +object YoutubeVideoInfo extends VideoInfoXML { + protected def req(id: String) = url("https://gdata.youtube.com/feeds/api/videos/"+id) <<? Map("v" -> "2") + + def videoDuration(id: String) = extract[Int](id, { xml: Elem => + node2Int(xml \\ "duration" \ "@seconds") + }) +} + +object VimeoVideoInfo extends VideoInfoXML { + protected def req(id: String) = url("http://vimeo.com/api/v2/video/"+id+".xml") + + def videoDuration(id: String) = extract[Int](id, { xml: Elem => + node2Int(xml \\ "duration") + }) +} + + + +object VideoService extends Enumeration { + type VideoService = Value + val Youtube = Value(YoutubeVideoInfo) + val Vimeo = Value(VimeoVideoInfo) + + class VideoServiceVal(val info : VideoInfo) extends Val(nextId) + protected final def Value(info : VideoInfo): VideoServiceVal = new VideoServiceVal(info) +} diff --git a/src/main/scala/com/joereader/lib/aws/AmazonS3.scala b/src/main/scala/com/joereader/lib/aws/AmazonS3.scala @@ -0,0 +1,144 @@ +package com.joereader.lib.aws + +import java.net.URLEncoder._ +import java.lang.Long +import com.ning.http.client.RequestBuilder +import com.ning.http.util.Base64 +import dispatch._ + + +protected object AmazonS3 { + import javax.crypto + + import java.util.{Date,Locale,SimpleTimeZone} + import java.text.SimpleDateFormat + + val UTF_8 = "UTF-8" + + val Root = "s3.amazonaws.com" + + object rfc822DateParser extends SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US) { + this.setTimeZone(new SimpleTimeZone(0, "GMT")) + } + + def trim(s: String): String = s.dropWhile(_ == ' ').reverse.dropWhile(_ == ' ').reverse.toString + + def md5(bytes: Array[Byte]) = { + import java.security.MessageDigest + + val r = MessageDigest.getInstance("MD5") + r.reset() + r.update(bytes) + Base64.encode(r.digest) + } + + def md5(stream:java.io.InputStream) = { + import java.security.MessageDigest + + val buffer = new Array[Byte](1024) + val r = MessageDigest.getInstance("MD5") + var numRead:Int = 0 + do { + numRead = stream.read(buffer) + if (numRead > 0) { + r.update(buffer, 0, numRead) + } + } while (numRead != -1) + + stream.close() + Base64.encode(r.digest()) + } + + def sign(method: String, path: String, secretKey: String, date: Date, + contentType: Option[String], contentMd5: Option[String], amzHeaders: Map[String,Set[String]]): String = { + sign(method, path, secretKey, Left(date), contentType, contentMd5, amzHeaders) + } + + def sign(method: String, path: String, secretKey: String, dateOrExpires: Either[Date, Long], + contentType: Option[String], contentMd5: Option[String], amzHeaders: Map[String,Set[String]]) = { + val SHA1 = "HmacSHA1" + val message = canonicalString(method, path, dateOrExpires, contentType, contentMd5, amzHeaders) + val sig = { + val mac = crypto.Mac.getInstance(SHA1) + val key = new crypto.spec.SecretKeySpec(bytes(secretKey), SHA1) + mac.init(key) + Base64.encode(mac.doFinal(bytes(message))) + } + sig + } + + def signedUri(accessKey: String, secretKey: String, method:String, path: String, amzHeaders: Map[String, Set[String]], + expires: Long = defaultExpiryTime, + contentType: Option[String] = None, contentMd5: Option[String] = None) = { + val signed = encode(sign(method, path, secretKey, Right(expires), contentType, contentMd5, amzHeaders), "UTF-8") + "%s?Signature=%s&Expires=%s&AWSAccessKeyId=%s".format(path, signed, expires, accessKey) + } + + def defaultExpiryTime = System.currentTimeMillis() / 1000 + 600 + + /** + * @return the canonical request string that needs to be signed for authentication + */ + def canonicalString(method: String, path: String, dateOrExpires: Either[Date, Long], contentType: Option[String], + contentMd5: Option[String], amzHeaders: Map[String, Set[String]]) = { + val amzString = amzHeaders.toList.sortWith(_._1.toLowerCase < _._1.toLowerCase).map{ case (k,v) => "%s:%s".format(k.toLowerCase, v.map(trim _).mkString(",")) } + val dateExpiresString = dateOrExpires match { + case Left(date) => rfc822DateParser.format(date) + case Right(expires) => expires.toString + } + (method :: contentMd5.getOrElse("") :: contentType.getOrElse("") :: dateExpiresString :: Nil) ++ amzString ++ List(path) mkString "\n" + } + + def bytes(s: String) = s.getBytes(UTF_8) + + implicit def Request2S3RequestSigner(r: RequestBuilder) = new S3RequestSigner(r) + implicit def Request2S3RequestSigner(r: String) = new S3RequestSigner(new RequestBuilder().setUrl(r)) + + class S3RequestSigner(r: RequestBuilder) { + import scala.collection.JavaConverters._ + + protected def path = RawUri(r.build.getUrl).path.getOrElse("") + + def <@ (accessKey: String, secretKey: String) = { + val req = r.build + val contentStream = req.getStreamData + val contentMd5 = if (req.getContentLength <= 0) None else Some(md5(contentStream)) + + for (cmd5 <- contentMd5) + r.addHeader("Content-MD5", cmd5) + + val headers = req.getHeaders + val contentType = headers.keySet.asScala.find { + _.toLowerCase == "content-type" + }.map {headers.get(_).asScala.head } + + val d = new Date + r.addHeader("Authorization", "AWS %s:%s".format(accessKey, sign(req.getMethod, path, secretKey, d, contentType, contentMd5, amazonHeaders))) + r.addHeader("Date", AmazonS3.rfc822DateParser.format(d)) + r + } + + def signed(accessKey: String, secretKey: String, expires: Long = defaultExpiryTime, + contentType: Option[String] = None, contentMd5: Option[String] = None): RequestBuilder = { + val req = r.build + val path = RawUri(req.getUrl).path.getOrElse("") + val uri = signedUri(accessKey, secretKey, req.getMethod, path, amazonHeaders, expires, contentType, contentMd5) + val requestHeaders = for { + (key, Some(value)) <- Map("Content-Type" -> contentType, "Content-Md5" -> contentMd5) + } yield key -> value + (:/(AmazonS3.Root) / uri.substring(1)).setMethod(req.getMethod).setHeaders(req.getHeaders).secure <:< requestHeaders + } + + private def amazonHeaders = { + val headers = r.build.getHeaders + headers.keySet.asScala.filter { + _.toLowerCase.startsWith("x-amz") + } + .map(name => name -> headers.get(name).asScala.toSet).toMap + } + } +} + +object Bucket extends (String => RequestBuilder) { + def apply(name: String) = :/(AmazonS3.Root) / name +} +\ No newline at end of file diff --git a/src/main/scala/com/joereader/lib/aws/S3.scala b/src/main/scala/com/joereader/lib/aws/S3.scala @@ -0,0 +1,33 @@ +package com.joereader.lib.aws + +import dispatch._ +import AmazonS3._ +import concurrent.ExecutionContext.Implicits.global +import com.ning.http.client.Response +import java.io.FileOutputStream + + +class S3(access_key: String, secret_key: String, bucket: String) { + + def createFile(fn: String, bytes: Array[Byte], contentType: String): Future[Response] = { + val file = new java.io.File("/tmp/"+fn) + inputToFile(bytes, file) + createFile(fn, file, contentType) + } + + def createFile(fn: String, file: java.io.File, contentType: String) = + Http(Bucket(bucket) / fn <<< file <:< + Map("content-type" -> contentType, "x-amz-acl" -> "public-read") + <@(access_key, secret_key)) + + def deleteFile(fn: String) = + Http(Bucket(bucket).DELETE / fn <@(access_key, secret_key)) + + def fileUrl(fn: String) = "http://"+Root+"/"+bucket+"/"+fn + + private def inputToFile(bytes: Array[Byte], f: java.io.File) { + val out = new FileOutputStream(f) + try { out.write(bytes) } + finally { out.close() } + } +} diff --git a/src/main/scala/com/joereader/lib/rss/Rss.scala b/src/main/scala/com/joereader/lib/rss/Rss.scala @@ -0,0 +1,210 @@ +package com.joereader.lib.rss + +import dispatch._, Defaults._ +import org.jsoup._, nodes._ +import scala.collection.JavaConversions._ +import java.io.ByteArrayInputStream +import com.sun.syndication.io.{XmlReader, SyndFeedInput} +import com.sun.syndication.feed.synd.{SyndContent, SyndEntry, SyndFeed} +import scala.collection.SortedSet + +import com.joereader.lib.Helper._ +import java.util.Date +import java.text.SimpleDateFormat +import scala.xml.NodeSeq +import org.jsoup.safety.Whitelist +import net.liftweb.util.PCDataXmlParser + +/** + * + */ +object Rss { + + implicit class RssImplicitString(link: String) { + + def entries: List[FeedEntry] = { + val response = requestLink(link)() + Feed.build(extractFeed(response), List(link)).entries + } + + /** + * Given a url link, it tries to find all rss links in html head. + * @return list of rss links and the html content. + */ + def rssLinks: List[String] = { + val response = requestLink(link)() + findRssLinks(response) + } + } + + implicit class RssImplicitListOfString(links: List[String]) { + /** + * Given a url link to rss feed, it returns a syndicated feed. + * @return a syndicated feed + */ + def feed: Feed = { + val feeds = links.map { link => + val response = requestLink(link)() + Feed.build(extractFeed(response), List(link)) + }.toList + Feed.merge(feeds) + } + } + + /** + * Returns a result of the page content (as a string) requested by a link. + * @param link the web link to the page + * @return either a failure with a message or successful content as a string + */ + private def requestLink(link: String) : Future[Option[String]] = { + val request = url(URLFormatter(link).toString) + Http.configure(_.setFollowRedirects(true).setCompressionEnabled(true)) + Http(request OK as.String).option + } + + /** + * Finds link(s) to RSS feeds in HTML elements. + * @param html html in plain text. + */ + private def findRssLinks(html: String): List[String] = { + + val document = parseHtml(html) + + /* All the types of rss mimes (including atom.) */ + val mimes = + "application/rss+xml" :: + "application/atom+xml" :: + "application/xml" :: + "text/xml" :: Nil + + /* Retrieves a list of elements that match type={mimes} */ + def filterByMimeType(elems: List[Element]): List[Element] = + elems.filter(e => mimes.filter(_ == e.attr("type")).nonEmpty) + + /* Remove elements that are likely rss comment feeds. */ + def filterNotRssComments(elems: List[Element]): List[Element] = + elems.filterNot(e => e.toString.contains("comment")) + + /* Retrieves the web link from each elements href value. */ + def getLinkHrefs(elems: List[Element]): List[String] = + elems.map(_.attr("href").trim) + + getLinkHrefs( + filterNotRssComments( + filterByMimeType(headElements(document)))) + } + + private def findRssLinks(html: Option[String]): List[String] = + html map findRssLinks _ getOrElse Nil + + /** + * Takes an xml (as string) and builds a syndicated feed. + * @param xml xml as string. + * @return a syndicated feed if xml if correctly parsed. + */ + private def extractFeed(xml: String): Option[SyndFeed] = try { + val bytes = new ByteArrayInputStream(xml.getBytes("UTF-8")) + val b = new XmlReader(bytes) + val a = new SyndFeedInput() + val c = a.build(b) + val h = Some(c) + h + } catch {case _: Throwable => None} + + private def extractFeed(link: Option[String]): Option[SyndFeed] = + link.map(extractFeed _).flatten + + private def parseHtml(html: String): Document = Jsoup.parse(html) + private def headElements(doc: Document): List[Element] = + doc.head.children.toList + + case class MetaVerification(metaName: String, metaContent: String, link: String) { + def verified: Boolean = + requestLink(link)().exists(response => + headElements(parseHtml(response)) exists + (e => e.attr("name").toLowerCase.trim == metaName.toLowerCase.trim && + e.attr("content").toLowerCase.trim == metaContent.toLowerCase.trim)) + } + + /* Interface of a SyndFeed. */ + object Feed { + def build(feed: Option[SyndFeed], links: List[String]) = + Feed( + feed.map(_.getTitle).getOrElse(""), + feed.map(_.getDescription).getOrElse(""), + feed.map { _.getEntries.asInstanceOf[java.util.List[SyndEntry]]. + map(_.getAuthor).toList}.getOrElse(Nil), + FeedEntry.build(feed.map(_.getEntries.asInstanceOf[java.util.List[SyndEntry]].toList), feed.map(_.getFeedType).getOrElse("")), + links + ) + + /** + * Merges multiple feeds into one. If any feed contains one entry with same + * title from an entry of another feed, the second feed will be removed. + * @param feeds the feeds to merge + * @return one merged feed + */ + def merge(feeds: List[Feed]): Feed = if(feeds.nonEmpty) { + var _name = feeds.head.name + var _description = feeds.head.description + var _writers = feeds.head.writers + var _entries: List[FeedEntry] = Nil + var _links: List[String] = Nil + + if(_name.isEmpty) feeds.tail.map(f => _name = f.name) + if(_description.isEmpty) feeds.tail.map(f => _description = f.description) + + for(f <- 0 until feeds.size) { + var repeat = false + for(entry1 <- feeds(f).entries) { + for(entry2 <- _entries) { + if(entry1.title == entry2.title) repeat = true + } + } + if(!repeat) { + _writers = _writers ::: feeds(f).writers + _entries = _entries ::: feeds(f).entries + _links = _links ::: feeds(f).links + } + } + + // remove any repeated values + _links = SortedSet(_links: _*).toList + _writers = SortedSet(_writers: _*).toList + + Feed(_name, _description, _writers, _entries, _links) + } else Feed("","",Nil, Nil, Nil) + + } + + case class Feed(name: String, + description: String, + writers: List[String], + entries: List[FeedEntry], + links: List[String]) + + object FeedEntry { + def build(entry: SyndEntry, feedType: String) = FeedEntry(entry.getTitle, entry.getPublishedDate, entry.getAuthor, content(entry, feedType)) + def build(entries: List[SyndEntry], feedType: String): List[FeedEntry] = entries.map(build(_, feedType)) + def build(entries: Option[List[SyndEntry]], feedType: String): List[FeedEntry] = entries.map(build(_, feedType)).getOrElse(Nil) + + def content(entry: SyndEntry, feedType: String): String = + if(feedType.startsWith("rss")) entry.getDescription.getValue + else entry.getContents.asInstanceOf[java.util.List[SyndContent]].toList(0).getValue + } + case class FeedEntry(title: String, date: Date, author: String, content: String) { + val df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + + def toForm: NodeSeq = + <div class="article"> + <div class="title">{title}</div> + <div class="author">{author}</div> + <time class="timeago" datetime={df.format(date)}></time> + <div class="content">{ + val unsanitized = Jsoup.parse("<div>"+content+"</div>").body.toString + val semisanitized = Jsoup.clean(unsanitized, Whitelist.relaxed) + PCDataXmlParser(semisanitized) openOr NodeSeq.Empty + }</div> + </div> + } +} +\ No newline at end of file diff --git a/src/main/scala/com/joereader/model/BetaUser.scala b/src/main/scala/com/joereader/model/BetaUser.scala @@ -0,0 +1,27 @@ +package com.joereader.model + +import net.liftweb._ +import common._ +import record.field._ +import mongodb.record._ +import mongodb.record.field._ +import mongodb.BsonDSL._ + +import org.bson.types._ + + +/** + * Temporary collection for users that have joined during beta. + */ + +class BetaUser private () extends MongoRecord[BetaUser] with StringPk[BetaUser] { + def meta = BetaUser + + /* Users that couldn't validate their blog because of Wordpress. */ + object writesWordPress extends BooleanField(this, false) +} + +object BetaUser extends BetaUser with MongoMetaRecord[BetaUser] { + override def collectionName = "beta.user.users" + override def find(in: String): Box[BetaUser] = find(id.name, in) +} diff --git a/src/main/scala/com/joereader/model/Blog.scala b/src/main/scala/com/joereader/model/Blog.scala @@ -0,0 +1,114 @@ +package com.joereader.model + +import net.liftweb._ +import common._ +import record.field._ +import mongodb.record._ +import mongodb.record.field._ +import mongodb.BsonDSL._ +import com.joereader.lib.Helper._ + +import org.bson.types._ +import net.liftweb.mongodb.PatternSerializer + + +class Blog private () extends MongoRecord[Blog] with ObjectIdPk[Blog] { + def meta = Blog + + object name extends StringField(this, 64) + object description extends TextareaField(this, 500) + object urlRss extends MongoListField[Blog, String](this) + + object urlHtml extends StringField(this, 500) { + def format(s: ValueType) = URLFormatter(s).hostAndPath + override def setFilter = format _ :: super.setFilter + } + + object img extends StringField(this, 50) + object bgImg extends StringField(this, 50) + + // blogname is the blog's site url + object blogname extends StringField(this,64) + + // owner is the writer that has control to edit blog + object owner extends ObjectIdRefField(this, User) { + override def defaultValue = new ObjectId("0"*24) + } + def isOwner(u: User) = owner.is == u.id.is + def hasOwner: Boolean = owner.is != owner.defaultValue + + // other writers + object writers extends BsonRecordListField(this, BlogWriter) + + def modifyWriter(bw: BlogWriter) = { + val exists = writers.get.find(_.user.is == bw.user.is) + val clean = exists.map(writers.get diff List(_)) + clean.map(c => writers(bw :: c)) + this + } + + def addWriter(bw: BlogWriter) = + if(writers.get.exists(_.name.is == bw.name.is)) this + else writers(bw :: writers.get) + + // if writer exists, only update user id. + def addWriterSafely(bw: BlogWriter) = { + val existing = writer(bw.name.is).map(_.user(bw.user.is)) + if(!existing.isDefined) addWriter(bw) + } + + def removeWriter(bw: BlogWriter) = writers(writers.get.filter(_.user.is != bw.user.is)) + def removeWriter(n: String) = writers(writers.get.filter(_.name.is != n)) + + def writersNames: List[String] = writers.is.map(_.name.is) + + def registeredWriters: List[BlogWriter] = writers.is.filter(u => u.user.is != u.user.defaultValue) + def unregisteredWriters: List[BlogWriter] = writers.is diff registeredWriters + + def registeredWritersNames = registeredWriters.map(_.name.is) + def unregisteredWritersNames = unregisteredWriters.map(_.name.is) + + def writer(name: String): Box[BlogWriter] = + writers.is.find(_.name.is == name) match { + case Some(x) => Full(x) + case None => Empty + } + + def writerExists(name: String): Boolean = writers.is.exists(_.name.is == name) + +} + +object Blog extends Blog with MongoMetaRecord[Blog] { + + override def collectionName = "blog.blogs" + override def formats = super.formats + new PatternSerializer + + ensureIndex(name.name -> 1, unique = false) + ensureIndex(blogname.name -> 1, unique = true) + ensureIndex(urlHtml.name -> 1, unique = true) + + def findByStringId(id: String): Box[Blog] = + if (ObjectId.isValid(id) && id != "0"*24) find(new ObjectId(id)) else Empty + + def findByName(in: String): Box[Blog] = find(name.name, in) + def findByBlogName(in: String): Box[Blog] = find(blogname.name, in) + def findByUrl(in: String): Box[Blog] = { + val pattern = URLFormatter(in).pattern + find("urlHtml"-> ("$regex" -> pattern.pattern) ~ ("$flags" -> pattern.flags)) + } +} + +class BlogWriter private () extends BsonRecord[BlogWriter] { + def meta = BlogWriter + + object name extends StringField(this, 100) // name from rss feed + object email extends StringField(this, 100) // if email is present, assume he was invited + object user extends ObjectIdRefField(this, User) { // if user is present, assume he joined + override def defaultValue = new ObjectId("0"*24) + } + + // todo does this go here? + object followers extends ObjectIdRefListField(this, User) +} +object BlogWriter extends BlogWriter with BsonMetaRecord[BlogWriter] + diff --git a/src/main/scala/com/joereader/model/Category.scala b/src/main/scala/com/joereader/model/Category.scala @@ -0,0 +1,44 @@ +package com.joereader.model + +import net.liftweb._ +import common._ +import record.field._ +import mongodb.record._ +import net.liftweb.mongodb.record.field._ + +import org.bson.types._ + + +/** + * Collection of urls for blogs and users. Url name must be unique. + * ie. if url=johnsmith then joereader.com/johnsmith + */ + +class Category private () extends MongoRecord[Category] { + def meta = Category + + object id extends StringField(this.asInstanceOf[Category], 12) { + override def name = "_id" + override def defaultValue = "0" + override def shouldDisplay_? = false + override def setFilter = trim _ :: toLower _ :: super.setFilter + } + + // Users that fall under this category + object users extends ObjectIdRefListField(this, User) + + def addUser(u: User) = + if(users.get.exists(_ == u.id.is)) this + else users(u.id.is :: users.get) + + def removeUser(u: User) = { + users(users.get.filter(_ != u.id.is)) + if(users.is.isEmpty) delete_! + this + } +} + +object Category extends Category with MongoMetaRecord[Category] { + override def collectionName = "category.categories" + override def find(in: String): Box[Category] = find(id.name, in) +} +\ No newline at end of file diff --git a/src/main/scala/com/joereader/model/User.scala b/src/main/scala/com/joereader/model/User.scala @@ -0,0 +1,201 @@ +package com.joereader +package model + +import org.bson.types.ObjectId + +import net.liftweb._ +import common._ +import http.{StringField => _, BooleanField => _, _} +import mongodb.record.field._ +import record.field._ + +import net.liftmodules.mongoauth._, model._ +import net.liftweb.mongodb.record.{BsonMetaRecord, BsonRecord} +import net.liftweb.common.Full + +class User private () extends ProtoAuthUser[User] with ObjectIdPk[User] { + def meta = User + + def userIdAsString: String = id.toString() + + object name extends StringField(this, 64) + def firstName = name.is.split(" ").headOption getOrElse "" + + object about extends TextareaField(this, 500) + object img extends StringField(this, 50) + object bgImg extends StringField(this, 50) + object introVid extends StringField(this, 20) + + object blogs extends BsonRecordListField(this, UserBlog) + + def modifyBlog(ub: UserBlog) = { + val exists = blogs.get.find(_.blog.is == ub.blog.is) + val clean = exists.map(blogs.get diff List(_)) + clean.map(c => blogs(ub :: c)) + this + } + + def addBlog(ub: UserBlog) = + if(blogs.get.exists(_.blog.is == ub.blog.is)) this + else blogs(ub :: blogs.get) + + def removeBlog(ub: UserBlog) = blogs(blogs.get.filter(_.blog.is != ub.blog.is)) + + def blog(b: Blog) = + blogs.is.find(_.blog.is == b.id.is) match { + case Some(x) => Full(x) + case None => Empty + } + + // todo figure out if this goes here + object following extends MongoMapField[User,Blog](this) { + import com.mongodb.{BasicDBObject, DBObject} + // todo test + override def asDBObject: DBObject = { + val dbo = new BasicDBObject + for((k,v) <- value) dbo.put(k, v.id.is) + dbo + } + + override def setFromDBObject(dbo: DBObject): Box[Map[String, Blog]] = { + import scala.collection.JavaConversions._ + + setBox(Full( + Map() ++ dbo.keySet.map { + k => (k, Blog.findByStringId(dbo.get(k).toString). + getOrElse(Blog.createRecord)) + } + )) + } + } +} + +object User extends User with ProtoAuthUserMeta[User] with Loggable { + import mongodb.BsonDSL._ + + override def collectionName = "user.users" + ensureIndex(email.name -> 1, unique = true) + ensureIndex(username.name -> 1, unique = true) + ensureIndex(name.name -> 1, unique = false) + + def findByEmail(in: String): Box[User] = find(email.name, in) + def findByName(in: String): Box[User] = find(name.name, in) + def findByUsername(in: String): Box[User] = find(username.name, in) + + def findByStringId(id: String): Box[User] = + if (ObjectId.isValid(id) && id != "0"*24) find(new ObjectId(id)) else Empty + + override def onLogIn: List[User => Unit] = List(user => User.loginCredentials.remove()) + override def onLogOut: List[Box[User] => Unit] = List( + x => logger.debug("User.onLogOut called."), + boxedUser => boxedUser.foreach { u => + ExtSession.deleteExtCookie() + } + ) + + /* + * MongoAuth vars + */ + private lazy val siteName = MongoAuth.siteName.vend + private lazy val sysUsername = MongoAuth.systemUsername.vend + private lazy val indexUrl = MongoAuth.indexUrl.vend + private lazy val registerUrl = MongoAuth.registerUrl.vend + private lazy val loginTokenAfterUrl = MongoAuth.loginTokenAfterUrl.vend + + /* + * LoginToken + */ + override def handleLoginToken(): Box[LiftResponse] = { + val resp = S.param("token").flatMap(LoginToken.findByStringId) match { + case Full(at) if at.expires.isExpired => { + at.delete_! + RedirectWithState(indexUrl, RedirectState(() => { S.error("Login token has expired") })) + } + case Full(at) => find(at.userId.is).map(user => { + if (user.validate.length == 0) { + user.verified(true) + user.save + logUserIn(user) + //at.delete_! + RedirectResponse(loginTokenAfterUrl) + } + else { + at.delete_! + regUser(user) + RedirectWithState(registerUrl, RedirectState(() => { S.notice("Please complete the registration form") })) + } + }).openOr(RedirectWithState(indexUrl, RedirectState(() => { S.error("User not found") }))) + case _ => RedirectWithState(indexUrl, RedirectState(() => { S.warning("Login token not provided") })) + } + + Full(resp) + } + + // send an email to the user with a link for logging in + def sendLoginToken(user: User) { + import net.liftweb.util.Mailer._ + + val token = LoginToken.createForUserId(user.id.is) + + val msgTxt = + """ + |Someone requested a link to change your password on the %s website. + | + |If you did not request this, you can safely ignore it. It will expire 48 hours from the time this message was sent. + | + |Follow the link below or copy and paste it into your internet browser. + | + |%s + | + |Yours truly, + |%s + """.format(siteName, token.url, sysUsername).stripMargin + + sendMail( + From(MongoAuth.systemFancyEmail), + Subject("%s Password Help".format(siteName)), + To(user.fancyEmail), + PlainMailBodyType(msgTxt) + ) + } + + // used during login process (holds email address) + object loginCredentials extends SessionVar[String]("") + object regUser extends SessionVar[User](createRecord.email(loginCredentials.is)) +} + +object SystemUser { + private val username = "joe" + private val email = "j.cabrra@gmail.com" + + lazy val user: User = User.find("username", username) openOr { + User.createRecord + .name("Julio") + .username(username) + .email(email) + .verified(true) + .password("deanjoe321", isPlain = true) + .save + } +} + +class UserBlog private () extends BsonRecord[UserBlog] { + def meta = UserBlog + + object blog extends ObjectIdRefField(this, Blog) { + override def defaultValue = new ObjectId("0"*24) + } + + object categories extends MongoListField[UserBlog, String](this) { + + def remove(category: String) = super.set(get.filter(_ != category)) + } + def addCategory(category: String) = + if(categories.get.exists(_ == category)) this + else categories(category :: categories.get) + + def removeCategory(category: String) = categories(categories.get.filter(_ != category)) +} +object UserBlog extends UserBlog with BsonMetaRecord[UserBlog] + + diff --git a/src/main/scala/com/joereader/snippet/BlogSnips.scala b/src/main/scala/com/joereader/snippet/BlogSnips.scala @@ -0,0 +1,150 @@ +package com.joereader.snippet + +import scala.xml._ + +import net.liftweb._ +import common._ +import http._ +import SHtml._ +import js._ +import JE._ +import JsCmds._ +import util._, Helpers._ + +import net.liftmodules.extras.SnippetHelper + +import com.joereader._ +import com.joereader.model._ +import snippet.SnipHelpers._ +import lib.rss.Rss._ +import api._ +import config._ + + +sealed trait BlogSnippet extends SnippetHelper with Loggable { + + protected def blog: Box[Blog] + private def test = true + + protected def serve(snip: Blog => NodeSeq)(test: Boolean, fallback: NodeSeq): NodeSeq = + (for { + b <- blog ?~ "Blog not found" + } yield { + if(test) snip(b) else fallback + }): NodeSeq + + protected def serve(html: NodeSeq)(snip: Blog => CssSel)(test: Boolean, fallback: NodeSeq): NodeSeq = + (for { + b <- blog ?~ "Blog not found" + } yield { + if(test) snip(b)(html) else fallback + }): NodeSeq + + def name = serve(blog => Text(blog.name.is))(test, NodeSeq.Empty) + + def url = serve(blog => Text(blog.urlHtml.is))(test, NodeSeq.Empty) + + def description = serve(blog => Text(blog.description.is))(test, NodeSeq.Empty) + + def img(html: NodeSeq) = serve(html)(blog => "* [src]" #> imageUrl(blog))(test, NodeSeq.Empty) + + def bgImg(html: NodeSeq) = serve(html)(blog => "* [src]" #> imageBgUrl(blog))(test, NodeSeq.Empty) + + def articles = serve(blog => blog.urlRss.is.head.entries.flatMap(_.toForm))(test, NodeSeq.Empty) + + def writers = serve(blog => writersList(blog.writers.is, owner = false))(test, NodeSeq.Empty) + + def writersList(writers: List[BlogWriter], owner: Boolean): NodeSeq = + for(i <- 0 until writers.size; writer <- writers) yield { + val user = User.findByStringId(writer.user.is.toString).openOr(User.createRecord) + val name = writer.name.is + val dashName = name.split(" ").mkString("-") + + <div id={"writer-"+dashName} class="blog-writer"> + <img id={"img-"+dashName} src={imageUrl(user)} /> + <span class="name">{name}</span>{ + + if(owner && writer.user.is.toString.isEmpty) + <label class="inline"> + <input id={"email-"+dashName} placeholder="email" value={writer.email.is} /> + <button onclick={ajaxCall(JsArray(ValById("email-"+dashName), Str(name)), invite _)._2.toJsCmd. + toString+"; return false;"} class="btn btn-primary">Invite</button> + <button onclick={ajaxCall(Str(name), remove _)._2.toJsCmd. + toString+"; return false;"} class="close category">×</button> + </label> + + }</div> + } + + def remove(name: String): JsCmd = { + blog.map(_.removeWriter(name).update) + Remove("writer-"+name.split(" ").mkString("-")) + } + + def invite(nameAndEmail: String): JsCmd = { + import net.liftweb.util.Mailer._ + import net.liftmodules.mongoauth.MongoAuth + + val name = nameAndEmail.split(",").headOption.getOrElse("") + val email = nameAndEmail.split(",").tail.headOption.getOrElse("") + + blog.map { blog => + blog.writer(name).map(_.email(email)) + blog.update + + User.currentUser.map { user => + // todo create a token to sign him up as blog writer + val token = "" + val msgTxt = + """ + |Hi %s, + | + |%s has invited you to Joe Reader to become a member of your + |current blog, %s. + | + |Click below to sign up: + |%s + | + |Yours truly, + |%s + """.format(name, user.name.is, blog.name.is, token, MongoAuth.systemUsername.vend).stripMargin + + sendMail( + From(MongoAuth.systemFancyEmail), + Subject("%s Invite from %s".format(MongoAuth.siteName.vend, user.name.is)), + To(email), + PlainMailBodyType(msgTxt) + ) + } + } + + S.notice(name+" has been invited") + } +} + +object ProfileLocBlog extends BlogSnippet { + + protected def blog = Site.blogProfileLoc.currentValue + protected def test = blog.exists(b => User.currentUser.exists(_.id.is == b.owner.is)) + + override def writers = serve { blog => + writersList(blog.writers.is, owner = true) + }(test, super.writers) + + def name(html: NodeSeq) = serve(html) { blog => + "*" #> ajaxText(blog.name.is, {s => blog.name(s).update; Noop}) + } (test, super.name) + + def description(html: NodeSeq) = serve(html) { blog => + "*" #> ajaxTextarea(blog.description.is, {s => blog.description(s).update; Noop}) + } (test, super.description) + + def uploadImg(html: NodeSeq) = serve(html) { user => + "*" #> insertFileUpload("pic", ImageUpload.userUrl()) + }(test, NodeSeq.Empty) + + def uploadBgImg(html: NodeSeq) = serve(html) { user => + "*" #> insertFileUpload("bg", ImageUpload.userUrl("bg")) + }(test, NodeSeq.Empty) +} + diff --git a/src/main/scala/com/joereader/snippet/LiftExtras.scala b/src/main/scala/com/joereader/snippet/LiftExtras.scala @@ -0,0 +1,21 @@ +package com.joereader.snippet + +import net.liftweb.util.Props +import net.liftmodules.extras._, snippet._ +import scala.xml.NodeSeq + +object Menus extends BsMenu +object Notices extends BsAlerts + +/* + * Base all LiftScreens off this. Currently configured to use bootstrap. + */ +abstract class BaseScreen extends BootstrapScreen { + override def defaultToAjax_? = true +} + +object ProductionOnly { + def render(in: NodeSeq): NodeSeq = + if (Props.productionMode) in + else NodeSeq.Empty +} +\ No newline at end of file diff --git a/src/main/scala/com/joereader/snippet/SnipHelpers.scala b/src/main/scala/com/joereader/snippet/SnipHelpers.scala @@ -0,0 +1,71 @@ +package com.joereader.snippet + +import com.joereader._ +import model._ +import config.S3Config._ + +import net.liftmodules.extras._ +import net.liftweb.http.SHtml._ +import scala.xml._ +import net.liftweb.http.js._ +import com.joereader.api.ImageUpload + + +object SnipHelpers { + + + def imageUrl(u: User) = if(u.img.is.nonEmpty) s3.fileUrl(u.img.is) else Gravatar.imageUrl(u.email.is) + def imageUrl(b: Blog) = if(b.img.is.nonEmpty) s3.fileUrl(b.img.is) else Gravatar.imageUrl("blah", 200, "G", "wavatar") + def imageBgUrl(u: User) = if(u.bgImg.is.nonEmpty) s3.fileUrl(u.bgImg.is) else "" + def imageBgUrl(b: Blog) = if(b.bgImg.is.nonEmpty) s3.fileUrl(b.bgImg.is) else "" + + + implicit class InputRadioDesignImplicit(choices: ChoiceHolder[String]) { + + def unregisteredWritersChoicesToPictureForm : NodeSeq = { + + for(i <- 0 until choices.items.size) yield { + val choice = choices.items(i) + val user = User.createRecord + + <label onClick="$(this).addClass('selected').siblings().removeClass('selected')"> + {choice.xhtml}<img src={imageUrl(user)} />{choice.key.toString}</label> + } + } + } + + implicit class InputRadioDesign2Implicit(writers: List[BlogWriter]) { + + def registeredWritersChoicesToPictureForm : NodeSeq = { + + for(i <- 0 until writers.size) yield { + val writer = writers(i) + val user = User.findByStringId(writer.user.is.toString).openOr(User.createRecord) + + <label><img src={imageUrl(user)} />{writer.name.is}</label> + } + } + } + + def insertFileUpload(id: String, path: String): NodeSeq = { + <input id={id+"-fileupload"} type="file" name="files2[]" data-url={path} accept={ImageUpload.acceptedImages.mkString(",")} /> + <div id={id+"-progress"} style="width:20em; border: 1pt solid silver; display: none"><div id={id+"-progress-bar"} style="background: green; height: 1em; width:0%"></div></div> + } + + + class JQuery(id: String, func: String, params: String*) extends JsCmd { + val script = """$("#%s").%s('%s')""".format(id, func, params.mkString("','")) + override def toJsCmd = JE.JsRaw(script).cmd.toJsCmd + } + + case class DisableInput(id: String) extends JQuery(id,"attr", "disabled", "") + case class AddClass(id: String, className: String) extends JQuery(id,"addClass", className) + case class RemoveClass(id: String, className: String) extends JQuery(id,"removeClass", className) + case class ToggleClass(id: String, className: String) extends JQuery(id,"toggleClass", className) + case class ToggleHide(id: String) extends JQuery(id,"toggleClass", "hide") + case class Hide(id: String) extends JQuery(id,"addClass", "hide") + case class Show(id: String) extends JQuery(id,"removeClass", "hide") + case class Prepend(id: String, node: Elem) extends JQuery(id,"prepend", node.toString()) + case class LoadButton(id: String) extends JQuery(id, "button", "loading") + case class Remove(id: String) extends JQuery(id, "remove", "") +} diff --git a/src/main/scala/com/joereader/snippet/UserSnips.scala b/src/main/scala/com/joereader/snippet/UserSnips.scala @@ -0,0 +1,251 @@ +package com.joereader +package snippet + + +import scala.xml._ + +import net.liftweb._ +import common._ +import http._ +import SHtml._ +import js._ +import JsCmds._ +import util._, Helpers._ + +import net.liftmodules._ +import mongoauth._ +import extras.{Gravatar, SnippetHelper} +import net.liftmodules.mongoauth.model.{LoginToken, ExtSession} + +import com.joereader._ +import com.joereader.model._ +import com.joereader.snippet.SnipHelpers._ +import api._ +import config._ +import lib._, rss.Rss._ +import net.liftweb.common.Full + +sealed trait UserSnippet extends SnippetHelper with Loggable { + + protected def user: Box[User] + private def test = true + + protected def serve(snip: User => NodeSeq)(test: Boolean, fallback: NodeSeq): NodeSeq = + (for { + u <- user ?~ "User not found" + } yield { + if(test) snip(u) else fallback + }): NodeSeq + + protected def serve(html: NodeSeq)(snip: User => CssSel)(test: Boolean, fallback: NodeSeq): NodeSeq = + (for { + u <- user ?~ "User not found" + } yield { + if(test) snip(u)(html) else fallback + }): NodeSeq + + def name = serve(user => Text(user.name.is))(test, NodeSeq.Empty) + + def about = serve(user => Text(user.about.is))(test, NodeSeq.Empty) + + def img(html: NodeSeq) = serve(html)(user => "* [src]" #> imageUrl(user))(test, NodeSeq.Empty) + + def bgImg(html: NodeSeq) = serve(html)(user => "* [src]" #> imageBgUrl(user))(test, NodeSeq.Empty) + + def articles = serve { user => + user.blogs.is.map { + ub => + val blog = ub.blog.obj.openOr(Blog.createRecord) + blog.writers.is.filter(_.user.is == user.id.is).flatMap { + bw => + blog.urlRss.is.head.entries.filter(_.author == bw.name.is).flatMap(_.toForm) + } + }.flatten + }(test, NodeSeq.Empty) +} + +trait CurrentUser extends UserSnippet { + + protected def test = true + + def name(html: NodeSeq) = serve(html) { user => + "*" #> ajaxText(user.name.is, {s => user.name(s).update; Noop}) + } (test, super.name) + + def about(html: NodeSeq) = serve(html) { user => + "*" #> ajaxTextarea(user.about.is, {s => user.about(s).update; Noop}) + } (test, super.about) + + def introVid(html: NodeSeq) = serve(html) { user => + + def checkVideo(id: String): JsCmd = { + import VideoService._, dispatch._ + val time: Int = Youtube.info.videoDuration(id)().getOrElse(-1) + if(time < 0) S.error("Video could not be found") + else if(time <= 30) { user.introVid(id).update; Noop } + else S.error("Video duration must be 30 seconds or less") + } + + "*" #> ajaxText(user.introVid.is, {s => checkVideo(s); Noop}) + }(test, NodeSeq.Empty) + + def uploadImg(html: NodeSeq) = serve(html) { user => + "*" #> insertFileUpload("pic", ImageUpload.userUrl()) + }(test, NodeSeq.Empty) + + def uploadBgImg(html: NodeSeq) = serve(html) { user => + "*" #> insertFileUpload("bg", ImageUpload.userUrl("bg")) + }(test, NodeSeq.Empty) +} + +object CurrentUser extends CurrentUser { + protected def user = User.currentUser +} + +object ProfileLocUser extends CurrentUser { + protected def user = Site.userProfileLoc.currentValue + override protected def test = user.exists(u => User.currentUser.exists(_.id.is == u.id.is)) +} + +object UserLogin extends Loggable { + + def render = { + + var password = "" + + def doSubmit(): JsCmd = { + S.param("email").map(e => { + val email = e.toLowerCase.trim + // save the email and remember entered in the session var + User.loginCredentials(email) + + if (email.length > 0 && password.length > 0) { + User.findByEmail(email) match { + case Full(user) if user.password.isMatch(password) => + logger.debug("pwd matched") + User.logUserIn(user, isAuthed = true) + ExtSession.deleteExtCookie() + RedirectTo(LoginRedirect.openOr(Site.home.url)) + case _ => + S.error("Invalid credentials") + Noop + } + } + else if (email.length <= 0 && password.length > 0) { + S.error("id_email_err", "Please enter an email") + Noop + } + else if (password.length <= 0 && email.length > 0) { + S.error("id_password_err", "Please enter a password") + Noop + } + else { + S.error("id_email_err", "Please enter an email") + S.error("id_password_err", "Please enter a password") + Noop + } + }) openOr { + S.error("id_email_err", "Please enter an email address") + Noop + } + } + + "#id_email [value]" #> User.loginCredentials.is & + "#id_password" #> SHtml.password(password, password = _) & + "#forgot_password [href]" #> Site.passwordRecovery.url & + "#id_submit" #> SHtml.hidden(doSubmit) + } +} + +object UserRecovery extends Loggable { + + def render = { + + def doSubmit(): JsCmd = + S.param("email").map(e => { + val email = e.toLowerCase.trim + User.loginCredentials(email) + + User.findByEmail(email) match { + case Full(user) => + User.sendLoginToken(user) + User.loginCredentials.remove() + S.notice("An email has been sent to you with instructions for accessing your account") + Noop + case _ => + S.error("id_email_err", "The email you entered cannot be found") + Noop + } + + }) openOr { + S.error("id_email_err", "Please enter an email address") + Noop + } + + "#id_email [value]" #> User.loginCredentials.is & + "#id_submit" #> SHtml.hidden(doSubmit) + } +} + +object PasswordReset extends Loggable { + + def render = { + + User.currentUserId.map { id => + val token = LoginToken.find(LoginToken.userId.name, id) + if(token.isEmpty) S.redirectTo(Site.notFound.url) + else token.map(_.delete_!) + } + + var pwd = "" + var pwd2 = "" + + def doSubmit(): JsCmd = + if(pwd == pwd2) { + User.currentUser.map { user => + user.password(pwd) + user.password.hashIt + user.save + } + S.redirectTo(Site.home.url) + } + else + S.error("Passwords do not match.") + + def passwordsMatch: JsCmd = + if(pwd == pwd2) Noop + else S.error("id_pwd2_err", "Password does not match") + + "#id_pwd" #> ajaxText(pwd, {s=> pwd = s; Noop}) & + "#id_pwd2" #> ajaxText(pwd2, {s=> pwd2 = s; passwordsMatch}) & + "#id_submit" #> ajaxSubmit("Save", doSubmit) + } +} + +object UserTopbar { + def render = { + User.currentUser match { + case Full(user) => + <ul class="nav pull-right" id="user"> + <li class="dropdown" data-dropdown="dropdown"> + <a href="#" class="dropdown-toggle" data-toggle="dropdown"> + <img src={imageUrl(user)} width="20" height="20"></img> + <span>{user.firstName}</span> + <b class="caret"></b> + </a> + <ul class="dropdown-menu"> + <li><a href={Site.userProfileLoc.calcHref(user)}><i class="icon-user"></i> Profile</a></li> + <li><lift:Menu.item name="Account" donthide="true" linktoself="true"><i class="icon-cog"></i> Settings</lift:Menu.item></li> + <li class="divider"></li> + <li><lift:Menu.item name="Logout" donthide="true"><i class="icon-off"></i> Log Out</lift:Menu.item></li> + </ul> + </li> + </ul> + case _ if S.request.flatMap(_.location).map(_.name).filterNot(it => List("Login", "Register").contains(it)).isDefined => + <ul class="nav pull-right"> + <li><a href="/login">Sign In</a></li> + </ul> + case _ => NodeSeq.Empty + } + } +} diff --git a/src/main/scala/com/joereader/snippet/signup/SignUp.scala b/src/main/scala/com/joereader/snippet/signup/SignUp.scala @@ -0,0 +1,28 @@ +package com.joereader.snippet + +import net.liftweb.http.RequestVar +import net.liftweb.http.js.JsCmd + + +abstract class SignUp { + + var email = "" + object EmailVar extends RequestVar("") + + var blogId = "" + object BlogIdVar extends RequestVar("") + + var verified = false + object VerifiedVar extends RequestVar(false) + + + /** + * Database storing happens here. + */ + def process(): JsCmd + + /** + * What happens when the wizard page is done. Usually store vars in RequestVars + */ + def continue: JsCmd +} diff --git a/src/main/scala/com/joereader/snippet/signup/SignUpBlog.scala b/src/main/scala/com/joereader/snippet/signup/SignUpBlog.scala @@ -0,0 +1,58 @@ +package com.joereader.snippet + +import scala.xml._ + +import net.liftweb._ +import util._ +import http._ +import SHtml._ +import js._, JsCmds._ +import common._ +import Helpers._ + +import com.joereader._ +import model._ +import config._ + +/** + * Fifth Step: if the user is the owner of the blog, allow him to customize + * the blog page, else continue to step six. + */ +class SignUpBlog extends SignUp { + + blogId = BlogIdVar.is + verified = VerifiedVar.is + + val user: Box[User] = User.currentUser + val blog: Box[Blog] = Blog.findByStringId(blogId) + + var siteUrl = "" + + def render = { + if(!verified || user.isEmpty || blog.isEmpty) + S.redirectTo(Site.notFound.url) + + "#blog-area" #> Templates("templates-hidden" :: "parts" :: "blog-profile" :: Nil).map{ ns => + Site.blogProfileLoc.requestValue(blog) + ns + } & + "#blog-site-url" #> ajaxText(siteUrl, checkSiteUrl(_)) & + "#blog-continue" #> ajaxSubmit("Continue", process) + } + + def process(): JsCmd = continue + + def continue: JsCmd = S.redirectTo(Site.signUp5.url, () => { + VerifiedVar(verified) + }) + + def checkSiteUrl(s: String): JsCmd = + if(!s.matches("^[a-z0-9-]{2,}$")) + S.error("Blog name can only contain alphanumeric or dash characters.") + else if(Blog.findByBlogName(s).isEmpty) { + siteUrl = s + blog.map(_.blogname(s).update) + Noop + } + else S.error("Blog name is taken") +} diff --git a/src/main/scala/com/joereader/snippet/signup/SignUpCategories.scala b/src/main/scala/com/joereader/snippet/signup/SignUpCategories.scala @@ -0,0 +1,85 @@ +package com.joereader.snippet + +import net.liftweb._ +import util._ +import Helpers._ +import http._ +import SHtml._ +import js._, JsCmds._, JE._ +import common._ + +import com.joereader._ +import model._ +import config._ +import snippet.SnipHelpers._ +import lib.Helper._ + +import scala.xml._ + + +/** + * Fourth Step: Set the categories that the user writes for his blog. + */ +class SignUpCategories extends SignUp { + + blogId = BlogIdVar.is + verified = VerifiedVar.is + + val user: Box[User] = User.currentUser + val blog: Box[Blog] = Blog.findByStringId(blogId) + + val userblog: Box[UserBlog] = + for { + user <- user + blog <- blog + } yield user.blog(blog) openOr UserBlog.createRecord.blog(blog.id.is) + + var category = "" + + def render = { + if(!verified || user.isEmpty || blog.isEmpty) + S.redirectTo(Site.notFound.url) + + "#chosen-categories *" #> userblog.map(_.categories.is.map(categoryNode(_))) & + "#category-input" #> text(category, category = _) & + "#category-add" #> ajaxSubmit("Add", ()=> add(category)) & + "#categories-continue" #> ajaxSubmit("Continue", process) + } + + def process(): JsCmd = continue + + def continue: JsCmd = S.redirectTo(Site.signUp4.url, () => { + BlogIdVar(blogId) + VerifiedVar(verified) + }) + + def add(cat: String): JsCmd = { + + for(ub <- userblog; u <- user) { + ub.addCategory(cat) + u.modifyBlog(ub).save // change to update (but wont work with embed docs) + val c = Category.find(cat).map(_.addUser(u).update) + if(!c.isDefined) Category.createRecord.id(cat).addUser(u).save + } + + category = "" + Prepend("chosen-categories", categoryNode(cat)) & + SetValById("category-input","") + } + + def remove(cat: String): JsCmd = { + + for(ub <- userblog; u <- user) { + ub.removeCategory(cat) + u.modifyBlog(ub).save + Category.find(cat).map(_.removeUser(u).update) + } + + Remove("chosen-cat-"+cat) + } + + def categoryNode(cat: String): Elem = + <span id={"chosen-cat-"+cat} class="uneditable-input">{cat}<button onclick={ + ajaxCall(Str(cat), remove _)._2.toJsCmd.toString.singleQuoteToDouble + + "; return false;"} class="close category">×</button></span> +} diff --git a/src/main/scala/com/joereader/snippet/signup/SignUpEmail.scala b/src/main/scala/com/joereader/snippet/signup/SignUpEmail.scala @@ -0,0 +1,51 @@ +package com.joereader.snippet + +import com.joereader._ +import model._ +import lib.Helper._ +import config._ + +import net.liftweb._ +import util.Helpers._ +import http._ +import SHtml._ +import js._ +import JsCmds._ +import common._ + + +/** + * First Step: get user's email. + */ +class SignUpEmail extends SignUp with Logger { + + def render = { + "#email-join" #> ajaxText(email, {s=> email = s; Noop}) & + "#join-btn" #> ajaxSubmit("Join", process) + } + + def process(): JsCmd = { + if(email.isEmail) { + if(BetaUser.find(email).isDefined) + joinedMsg & SetValById("email-join", "") + else if(User.findByEmail(email).isDefined) + invitedMsg & SetValById("email-join", "") + else { + BetaUser.createRecord.id(email).save + continue + } + } + else failureMsg + } + + def continue: JsCmd = S.redirectTo(Site.signUp1.url,() => { + EmailVar(email) + }) + + def invitedMsg: JsCmd = S.notice("You've been invited already silly") + + def joinedMsg: JsCmd = S.notice("You joined Joe Reader a while back. " + + "We'll invite you as soon as we're ready!") + + def failureMsg: JsCmd = S.error("Please enter a valid email.") +} diff --git a/src/main/scala/com/joereader/snippet/signup/SignUpPassword.scala b/src/main/scala/com/joereader/snippet/signup/SignUpPassword.scala @@ -0,0 +1,43 @@ +package com.joereader.snippet + +import net.liftweb._ +import util.Helpers._ +import http._ +import SHtml._ +import js._ + +import com.joereader._ +import config._ +import model._ + +/** + * Seventh Step: Set the user's password. + */ +class SignUpPassword extends SignUp { + + verified = VerifiedVar.is + + val user = User.currentUser + + var pwd = "" + + def render = { + if(!verified || user.isEmpty) S.redirectTo(Site.notFound.url) + + "#user-password" #> password(pwd, pwd = _) & + "#user-continue" #> ajaxSubmit("Continue", process) + } + + def process(): JsCmd = { + user.map{ u => + u.password(pwd) + u.password.hashIt + u.update + } + continue + } + + def continue: JsCmd = S.redirectTo("/", () => { + verified = false + }) +} diff --git a/src/main/scala/com/joereader/snippet/signup/SignUpUser.scala b/src/main/scala/com/joereader/snippet/signup/SignUpUser.scala @@ -0,0 +1,57 @@ +package com.joereader.snippet + +import scala.xml._ + +import net.liftweb._ +import util._ +import http._ +import SHtml._ +import js._, JsCmds._ +import common._ +import Helpers._ + +import com.joereader._ +import model._ +import config._ + +/** + * Sixth Step: Customize the user's page. + */ +class SignUpUser extends SignUp { + + verified = VerifiedVar.is + + val user: Box[User] = User.currentUser + + var siteUrl = "" + + def render = { + if(!verified || user.isEmpty) + S.redirectTo(Site.notFound.url) + + "#user-area" #> Templates("templates-hidden" :: "parts" :: "user-profile" :: Nil).map{ ns => + Site.userProfileLoc.requestValue(user) + ns + } & + "#user-site-url" #> ajaxText(siteUrl, checkSiteUrl(_)) & + "#user-continue" #> ajaxSubmit("Continue", process) + } + + def process(): JsCmd = continue + + def continue: JsCmd = S.redirectTo(Site.signUp6.url, () => { + VerifiedVar(verified) + }) + + def checkSiteUrl(s: String): JsCmd = + if(!s.matches("^[a-z0-9-]{2,}$")) + S.error("User name can only contain alphanumeric or dash characters.") + else if(User.findByUsername(s).isEmpty && + Site.isAvailableMenu(s)) + { + siteUrl = s + user.map(_.username(s).update) + Noop + } + else S.error("Username is taken") +} +\ No newline at end of file diff --git a/src/main/scala/com/joereader/snippet/signup/SignUpVerification.scala b/src/main/scala/com/joereader/snippet/signup/SignUpVerification.scala @@ -0,0 +1,162 @@ +package com.joereader.snippet + +import net.liftweb._ +import util._ +import Helpers._ +import http._ +import SHtml._ +import js._, JsCmds._ +import common._ + +import com.joereader._ +import lib.rss.Rss._ +import snippet.SnipHelpers._ +import config._ +import model._ +import scala.xml._ + +/** + * Third Step: Verify the user's blog. If verified, continue to next step. + */ +class SignUpVerification extends SignUp { + + email = EmailVar.is + + var searched = false + var urlHtml = "" + + val user = User.createRecord + var blog = Blog.createRecord + + val writerName = ValueCell("") + val owner = ValueCell(false) + + val metaName = "joe_reader" + val metaContent = (writerName lift owner)( + (w,o) => user.id.is+":"+w+{if(o)":owner" else ""}) + + val verification = metaContent.lift(metaContent => + s"""<meta name="$metaName" content="$metaContent"/>""") + + def render = { + if(email.isEmpty) S.redirectTo(Site.notFound.url) + + "#verify-state" #> idMemoize(verifyBlog _) + } + + def process(): JsCmd = { + verified = + MetaVerification(metaName, metaContent.get, urlHtml).verified + + if(verified) { + BetaUser.find(email).map(_.delete_!) + + user + .addBlog(UserBlog.createRecord.blog(blog.id.is)) + .email(email) + .password(Helpers.randomString(20)) + .username(StringHelpers.randomString(15)) + user.password.hashIt + user.save + User.logUserIn(user, isAuthed = true, isRemember = true) + + val bw = BlogWriter.createRecord.name(writerName.is).user(user.id.is) + + if(owner) blog.owner(user.id.is) + blog.addWriterSafely(bw) + + if(blog.blogname.is.isEmpty) + blog.blogname(StringHelpers.randomString(15)) + + blog.save + continue + } + else + Show("verification-unsuccessful") + } + + def continue: JsCmd = S.redirectTo(Site.signUp3.url,() => { + BlogIdVar(blog.id.is.toString) + VerifiedVar(verified) + }) + + def verifyBlog(outer: IdMemoizeTransform) = { + + // if idmemoize updated, get urlhtml and find feed + if(urlHtml.nonEmpty) { + val existing = Blog.findByUrl(blog.urlHtml.is) + if(existing.isEmpty) { + val links = urlHtml.rssLinks + val feed: Feed = links.feed + val writers = feed.writers.map(BlogWriter.createRecord.name(_)) + + blog = Blog.createRecord + .urlHtml(urlHtml) + .name(feed.name) + .description(feed.description) + .writers(writers) + .urlRss(feed.links) + } else + existing.map(blog = _) + + searched = true + } + + "#blog-url" #> text(urlHtml, urlHtml = _) & + "#search-blog" #> ajaxSubmit("Search Blog", () => ajaxInvoke(outer.setHtml _)) & + "#writer-list *" #> listWriters & + "#blog-owner-q-yes" #> radios(0) & + "#blog-owner-q-no" #> radios(1) & + "#verification-info [class]" #> hideVerification & + "#blog-owner-q [class]" #> hideOwnerQuestion & + "#verification-info-input" #> WiringUI.asText(verification) & + "#verify-blog" #> ajaxSubmit("Verify", process) + } + + def writersRadios = ajaxRadio[String]( + blog.unregisteredWritersNames, + Full(blog.unregisteredWritersNames headOr ""), + { n => + writerName.set(n) + user.name(n) + Noop + } + ).unregisteredWritersChoicesToPictureForm + + val radios = ajaxRadio[Boolean]( + Seq(true, false), + Full(false), + { bool => + owner.set(bool) + Noop + } + ) + + def registeredUsers = + blog.registeredWriters.registeredWritersChoicesToPictureForm + + def completedMsg = + if(blog.unregisteredWriters.isEmpty && searched) + Text("Looks like all writers have joined") + else NodeSeq.Empty + + def hideVerification: String = { + blog.unregisteredWritersNames.nonEmpty ? "" | "hide" + } + + def hideOwnerQuestion: String = { + val size = blog.unregisteredWritersNames.size + owner.set((size == 1) ? true | false) + (size < 2) ? "hide" | "" + } + + // returns the radio group of writers + def listWriters(): NodeSeq = + if(blog.writersNames.isEmpty && searched) + Text("Writers not found. Double check your url.") + else { + writerName.set(blog.unregisteredWritersNames headOr "") + writersRadios ++ registeredUsers ++ completedMsg + } + +} diff --git a/src/main/scala/com/joereader/snippet/signup/SignUpWriter.scala b/src/main/scala/com/joereader/snippet/signup/SignUpWriter.scala @@ -0,0 +1,34 @@ +package com.joereader.snippet + +import net.liftweb._ +import util.Helpers._ +import http._ +import SHtml._ +import js._ +import JsCmds._ + +import com.joereader.config.Site + + + +/** + * Second Step: Check if he's a blog writer. If so, continue to next steps + * and allow him to setup and customize his blog. + */ +class SignUpWriter extends SignUp { + + email = EmailVar.is + + def render = { + if(email.isEmpty) S.redirectTo(Site.notFound.url) + + "#yes-button" #> ajaxSubmit("Yes", process) & + "#no-button" #> ajaxSubmit("No", () => S.redirectTo("/")) + } + + def process(): JsCmd = continue + + def continue: JsCmd = S.redirectTo(Site.signUp2.url, () => { + EmailVar(email) + }) +} diff --git a/src/main/webapp/404.html b/src/main/webapp/404.html @@ -0,0 +1,3 @@ +<div data-lift="surround?with=default;at=content"> + <p style="font-size: 1.2em;">We're sorry but the page you are trying to access does not exist. (404)</p> +</div> diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<web-app + xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + version="2.5" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> + +<filter> + <filter-name>LiftFilter</filter-name> + <display-name>Lift Filter</display-name> + <description>The Filter that intercepts lift calls</description> + <filter-class>net.liftweb.http.LiftFilter</filter-class> +</filter> + +<filter-mapping> + <filter-name>LiftFilter</filter-name> + <url-pattern>/*</url-pattern> +</filter-mapping> + +</web-app> diff --git a/src/main/webapp/about.html b/src/main/webapp/about.html @@ -0,0 +1,17 @@ +<div data-lift="surround?with=base-wrap;at=content"> + <lift:head> + <title lift="Menu.title">Joe Reader: %*%</title> + </lift:head> + <div class="main-content"> + <div lift="Notices"></div> + <div class="hero-unit"> + <div class="inner"> + <h2>About Joe Reader</h2> + <p> + <span lift="Menu.item?name=Home;a:class=btn primary large">Back to Home »</span> + </p> + </div> + </div> + </div> + <span data-lift="embed?what=/templates-hidden/parts/footer"></span> +</div> +\ No newline at end of file diff --git a/src/main/webapp/blog.html b/src/main/webapp/blog.html @@ -0,0 +1,3 @@ +<div data-lift="surround?with=default;at=content"> + <span data-lift="embed?what=/templates-hidden/parts/blog-profile"></span> +</div> +\ No newline at end of file diff --git a/src/main/webapp/error.html b/src/main/webapp/error.html @@ -0,0 +1,4 @@ +<div data-lift="surround?with=default;at=content"> + <p style="font-size: 1.2em;">We're sorry but an error has occurred. Our staff has + been notified and is working on a solution.</p> +</div> diff --git a/src/main/webapp/help/recovery.html b/src/main/webapp/help/recovery.html @@ -0,0 +1,26 @@ +<div data-lift="surround?with=default;at=content"> + <div data-lift="Notices"></div> + <div class="row"> + <div class="span12"> + <form data-lift="Form.ajax?class=form-horizontal"> + <div lift="UserRecovery"> + <fieldset> + <div class="control-group"> + <label class="control-label" for="email"></label> + <div class="controls"> + <p><strong>Enter your email address:</strong></p> + <input class="xlarge" maxlength="254" id="id_email" size="32" name="email" type="text" value="" /> + <span data-alertid="id_email_err" class="notice-block"></span> + </div> + </div> + </fieldset> + <div class="form-actions"> + <input id="id_submit" /> + <input type="submit" class="btn btn-primary" value="Submit"> + <span lift="Menu.item?name=Home;a:class=btn">Cancel</span> + </div> + </div> + </form> + </div> + </div> +</div> +\ No newline at end of file diff --git a/src/main/webapp/img/controls.png b/src/main/webapp/img/controls.png Binary files differ. diff --git a/src/main/webapp/img/glyphicons-halflings-white.png b/src/main/webapp/img/glyphicons-halflings-white.png Binary files differ. diff --git a/src/main/webapp/img/glyphicons-halflings.png b/src/main/webapp/img/glyphicons-halflings.png Binary files differ. diff --git a/src/main/webapp/img/media/background.png b/src/main/webapp/img/media/background.png Binary files differ. diff --git a/src/main/webapp/img/media/controls.png b/src/main/webapp/img/media/controls.png Binary files differ. diff --git a/src/main/webapp/img/media/controls.svg b/src/main/webapp/img/media/controls.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!-- Generator: Adobe Fireworks CS6, Export SVG Extension by Aaron Beall (http://fireworks.abeall.com) . Version: 0.6.1 --><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg id="controls.fw-Page%201" viewBox="0 0 144 32" style="background-color:#ffffff00" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" x="0px" y="0px" width="144px" height="32px"> <defs> <radialGradient id="gradient1" cx="50%" cy="50%" r="50%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#f2f2f2" stop-opacity="0.2" offset="100%"/> </radialGradient> <linearGradient id="gradient2" x1="50%" y1="-7.8652%" x2="50%" y2="249.6629%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient3" x1="50%" y1="0%" x2="50%" y2="238.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient4" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient5" x1="50%" y1="-33.3333%" x2="50%" y2="152.0833%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient6" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient7" x1="50%" y1="-33.3333%" x2="50%" y2="152.0833%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient8" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient9" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient10" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient11" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient12" x1="50%" y1="0%" x2="50%" y2="238.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient13" x1="40%" y1="-140%" x2="40%" y2="98.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient14" x1="50%" y1="0%" x2="50%" y2="238.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient15" x1="60%" y1="-140%" x2="60%" y2="98.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient16" x1="50%" y1="0%" x2="50%" y2="298.4375%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient17" x1="50%" y1="0%" x2="50%" y2="238.75%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient18" x1="50%" y1="-200%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient19" x1="50%" y1="-200%" x2="50%" y2="110.9375%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient20" x1="55%" y1="0%" x2="55%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="100%"/> </linearGradient> <linearGradient id="gradient21" x1="50%" y1="0%" x2="50%" y2="100%"> <stop stop-color="#ffffff" stop-opacity="1" offset="0%"/> <stop stop-color="#c8c8c8" stop-opacity="1" offset="99.4444%"/> </linearGradient> </defs> <g id="BG"> </g> <g id="controls"> <path id="Line" d="M 98.5 7.5 L 109.5 7.5 " stroke="#ffffff" stroke-width="1" fill="none"/> <path id="Line2" d="M 98.5 3.5 L 109.5 3.5 " stroke="#ffffff" stroke-width="1" fill="none"/> <path id="Line3" d="M 98.5 11.5 L 109.5 11.5 " stroke="#ffffff" stroke-width="1" fill="none"/> <path id="Ellipse" d="M 108 11.5 C 108 10.6716 108.4477 10 109 10 C 109.5523 10 110 10.6716 110 11.5 C 110 12.3284 109.5523 13 109 13 C 108.4477 13 108 12.3284 108 11.5 Z" fill="#ffffff"/> <path id="Ellipse2" d="M 104 7.5 C 104 6.6716 104.4477 6 105 6 C 105.5523 6 106 6.6716 106 7.5 C 106 8.3284 105.5523 9 105 9 C 104.4477 9 104 8.3284 104 7.5 Z" fill="#ffffff"/> <path id="Ellipse3" d="M 108 3.5 C 108 2.6716 108.4477 2 109 2 C 109.5523 2 110 2.6716 110 3.5 C 110 4.3284 109.5523 5 109 5 C 108.4477 5 108 4.3284 108 3.5 Z" fill="#ffffff"/> </g> <g id="backlight"> <g id="off"> <rect x="83" y="21" width="10" height="6" stroke="#ffffff" stroke-width="1" fill="#333333"/> </g> <g id="on"> <path id="Ellipse4" d="M 81 8 C 81 5.2385 84.134 3 88 3 C 91.866 3 95 5.2385 95 8 C 95 10.7615 91.866 13 88 13 C 84.134 13 81 10.7615 81 8 Z" fill="url(#gradient1)"/> <rect x="83" y="5" width="10" height="6" stroke="#ffffff" stroke-width="1" fill="#333333"/> </g> </g> <g id="loop"> <g id="on2"> <path d="M 73.795 4.205 C 75.2155 4.8785 76.2 6.3234 76.2 8 C 76.2 10.3196 74.3196 12.2 72 12.2 C 69.6804 12.2 67.8 10.3196 67.8 8 C 67.8 6.3234 68.7845 4.8785 70.205 4.205 L 68.875 2.875 C 67.1501 3.9289 66 5.8306 66 8 C 66 11.3138 68.6862 14 72 14 C 75.3138 14 78 11.3138 78 8 C 78 5.8306 76.8499 3.9289 75.125 2.875 L 73.795 4.205 Z" fill="url(#gradient2)"/> <path d="M 71 2 L 66 2 L 71 7 L 71 2 Z" fill="url(#gradient3)"/> </g> <g id="off2"> <path d="M 73.795 20.205 C 75.2155 20.8785 76.2 22.3234 76.2 24 C 76.2 26.3196 74.3196 28.2 72 28.2 C 69.6804 28.2 67.8 26.3196 67.8 24 C 67.8 22.3234 68.7845 20.8785 70.205 20.205 L 68.875 18.875 C 67.1501 19.9289 66 21.8306 66 24 C 66 27.3138 68.6862 30 72 30 C 75.3138 30 78 27.3138 78 24 C 78 21.8306 76.8499 19.9289 75.125 18.875 L 73.795 20.205 Z" fill="#a8a8b7"/> <path d="M 71 18 L 66 18 L 71 23 L 71 18 Z" fill="#a8a8b7"/> </g> </g> <g id="cc"> <rect visibility="hidden" x="49" y="2" width="14" height="12" stroke="#b0b0b0" stroke-width="1" fill="none"/> <text visibility="hidden" x="49" y="17" width="14" fill="#ffffff" style="font-size: 10px; color: #ffffff; font-family: Arial; text-align: center; "><tspan><![CDATA[cc]]></tspan></text> <path d="M 55 7 C 50.2813 3.7813 50.063 12.9405 55 10 " stroke="#ffffff" stroke-width="1" fill="none"/> <path d="M 60 7 C 55.2813 3.7813 55.063 12.9405 60 10 " stroke="#ffffff" stroke-width="1" fill="none"/> <path d="M 50 3 L 62 3 L 62 13 L 50 13 L 50 3 ZM 49 2 L 49 14 L 63 14 L 63 2 L 49 2 Z" fill="url(#gradient4)"/> <rect x="49" y="2" width="14" height="12" fill="none"/> </g> <g id="volume"> <g id="no%20sound"> <rect x="17" y="5" width="5" height="6" fill="url(#gradient5)"/> <path d="M 21 5 L 25 2 L 25 14 L 21 11.0625 L 21 5 Z" fill="url(#gradient6)"/> </g> <g id="sound%20bars"> <rect x="17" y="21" width="5" height="6" fill="url(#gradient7)"/> <path d="M 21 21 L 25 18 L 25 30 L 21 27.0625 L 21 21 Z" fill="url(#gradient8)"/> <path d="M 27 18 C 27 18 30.0625 17.375 30 24 C 29.9375 30.625 27 30 27 30 " stroke="#ffffff" stroke-width="1" fill="none"/> <path d="M 26 21.0079 C 26 21.0079 28.041 20.6962 27.9994 24 C 27.9577 27.3038 26 26.9921 26 26.9921 " stroke="#ffffff" stroke-width="1" fill="none"/> </g> </g> <g id="play/pause"> <g id="play"> <path id="Polygon" d="M 14 8.5 L 3 14 L 3 3 L 14 8.5 Z" fill="url(#gradient9)"/> </g> <g id="pause"> <rect x="3" y="18" width="3" height="12" fill="url(#gradient10)"/> <rect x="10" y="18" width="3" height="12" fill="url(#gradient11)"/> </g> </g> <g id="fullscreen"> <g id="enter%201"> <path d="M 34 2 L 39 2 L 34 7 L 34 2 Z" fill="url(#gradient12)"/> <path d="M 34 14 L 39 14 L 34 9 L 34 14 Z" fill="url(#gradient13)"/> <path d="M 46 2 L 41 2 L 46 7 L 46 2 Z" fill="url(#gradient14)"/> <path d="M 46 14 L 41 14 L 46 9 L 46 14 Z" fill="url(#gradient15)"/> </g> <g id="exit"> <path d="M 42 22 L 46 22 L 42 18 L 42 22 Z" fill="url(#gradient16)"/> <path d="M 38 22 L 38 18 L 34 22 L 38 22 Z" fill="url(#gradient17)"/> <path d="M 38 26 L 34 26 L 38 30 L 38 26 Z" fill="url(#gradient18)"/> <path d="M 42 26 L 42 30 L 46 26 L 42 26 Z" fill="url(#gradient19)"/> </g> </g> <g id="stop"> <rect x="115" y="3" width="10" height="10" fill="url(#gradient20)"/> </g> <g id="chooser"> <path d="M 135.2346 6.1522 C 136.2551 5.7295 137.4251 6.2141 137.8478 7.2346 C 138.2704 8.2551 137.7859 9.425 136.7654 9.8478 C 135.7449 10.2705 134.5749 9.7859 134.1522 8.7654 C 133.7295 7.7449 134.2141 6.5749 135.2346 6.1522 ZM 133.2735 1.4176 L 136 4.0054 L 138.7265 1.4176 L 138.8246 5.1754 L 142.5824 5.2735 L 139.9946 8 L 142.5824 10.7265 L 138.8246 10.8246 L 138.7265 14.5824 L 136 11.9946 L 133.2735 14.5824 L 133.1754 10.8246 L 129.4176 10.7265 L 132.0054 8 L 129.4176 5.2735 L 133.1754 5.1754 L 133.2735 1.4176 Z" fill="url(#gradient21)"/> </g></svg> +\ No newline at end of file diff --git a/src/main/webapp/img/media/echo-hereweare.webm b/src/main/webapp/img/media/echo-hereweare.webm Binary files differ. diff --git a/src/main/webapp/img/media/flashmediaelement.swf b/src/main/webapp/img/media/flashmediaelement.swf Binary files differ. diff --git a/src/main/webapp/img/media/mediaelement.srt b/src/main/webapp/img/media/mediaelement.srt @@ -0,0 +1,61 @@ +0 +00:00:00,1 --> 00:00:04 +HTML5 <video> and <audio> was supposed to be awesome, powerful, and fun. + +1 +00:00:04 --> 00:00:07 +But browser vendors couldn't agree on a codec + +2 +00:00:07 --> 00:00:10 +and older browsers don't support <video> at all. + +3 +00:00:10 --> 00:00:12 +This means <video src="myfile.mp4" /> doesn't work ... + +4 +00:00:12 --> 00:00:14 +until now. + +5 +00:00:14 --> 00:00:18 +Introducing MediaElement.js, an HTML5 <video> and <audio> player + +6 +00:00:18 --> 00:00:21 +that looks and works the same in every browser (even iPhone and Android). + +7 +00:00:21 --> 00:00:24 +For older browsers, it has custom Flash and Silverlight plugins + +8 +00:00:24 --> 00:00:27 +that fully replicate the HTML5 MediaElement API + +9 +00:00:27 --> 00:00:30 +so you can build a consistent control UI using just HTML and CSS. + +10 +00:00:30 --> 00:00:33 +MediaElement.js even supports newer standards + +11 +00:00:33 --> 00:00:36 +like the <track> element that enables the subtitles you're reading right now. + +12 +00:00:36 --> 00:00:39 +The subtitles can even be translated into any language using Google's Translation API. + +13 +00:00:39 --> 00:00:42 +As a bonus, the Flash and Silverlight fallbacks allow you to use FLV and WMV files. + +14 +00:00:42 --> 00:00:45 +Hope you like it. + +Come follow me at <a href="http://twitter.com/johndyer">twitter.com/johndyer</a> +\ No newline at end of file diff --git a/src/main/webapp/img/media/silverlightmediaelement.xap b/src/main/webapp/img/media/silverlightmediaelement.xap Binary files differ. diff --git a/src/main/webapp/img/spinner.gif b/src/main/webapp/img/spinner.gif Binary files differ. diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html @@ -0,0 +1,38 @@ +<div data-lift="surround?with=base-wrap;at=content"> + <lift:head> + <title lift="Menu.title">Joe Reader: %*%</title> + </lift:head> + <div class="main-content"> + <div data-lift="Notices"></div> + <div class="hero-unit"> + <div class="inner"> + <h2>Joe Reader</h2> + <p> + <span lift="Menu.item?name=About;donthide=true;linktoself=true;a:class=btn primary large">About Joe Reader »</span> + </p> + + <div data-lift="SignUpEmail"> + <form data-lift="form.ajax"> + Email: <input id="email-join" placeholder="email"> + <input id="join-btn" class="btn btn-primary" data-loading-text="Loading..." /> + </form> + </div> + + + + + + + <video width="640" height="360" id="intro-player" controls="controls" autoplay="false"> + <source type="video/youtube" src="http://www.youtube.com/watch?v=nOEw9iiopwI" /> + </video> + + + <!-- + <h1>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</h1> + --> + </div> + </div> + </div> + <span data-lift="embed?what=/templates-hidden/parts/footer"></span> +</div> diff --git a/src/main/webapp/login.html b/src/main/webapp/login.html @@ -0,0 +1,34 @@ +<div data-lift="surround?with=default;at=content"> + <div data-lift="Notices"></div> + <div class="row"> + <div class="span12"> + <form data-lift="Form.ajax?class=form-horizontal"> + <span lift="UserLogin"> + <fieldset> + <div class="control-group"> + <label class="control-label" for="email"></label> + <div class="controls"> + <p><strong>Enter your email address:</strong></p> + <input class="xlarge" maxlength="254" id="id_email" size="32" name="email" type="text" value="" /> + <span data-alertid="id_email_err" class="notice-block"></span> + </div> + </div> + <div class="control-group"> + <div class="controls"> + <p><strong>Enter your password:</strong></p> + <input class="xlarge" id="id_password" /> + <span data-alertid="id_password_err" class="notice-block"></span> + </div> + </div> + <a id="forgot_password">Forgot password?</a> + </fieldset> + <div class="form-actions"> + <input id="id_submit" /> + <input type="submit" class="btn btn-primary" value="Submit"> + <span lift="Menu.item?name=Home;a:class=btn">Cancel</span> + </div> + </span> + </form> + </div> + </div> +</div> diff --git a/src/main/webapp/reader.html b/src/main/webapp/reader.html @@ -0,0 +1,3 @@ +<div data-lift="surround?with=default;at=content"> + <p>Homey</p> +</div> diff --git a/src/main/webapp/register.html b/src/main/webapp/register.html @@ -0,0 +1,4 @@ +<div data-lift="surround?with=default;at=content"> + <div data-lift="Notices"></div> + <div data-lift="RegisterScreen"></div> +</div> diff --git a/src/main/webapp/robots.txt b/src/main/webapp/robots.txt @@ -0,0 +1,5 @@ +# www.robotstxt.org/ +# www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 + +User-agent: * + diff --git a/src/main/webapp/settings/account.html b/src/main/webapp/settings/account.html @@ -0,0 +1,5 @@ +<div data-lift="layout?with=settings-wrap;at=content"> + <div class="row"><div class="span12">Name: <span id="user-name"></span></div></div> + <div class="row"><div class="span12">Name: <span id="user-about"></span></div></div> + +</div> diff --git a/src/main/webapp/settings/password.html b/src/main/webapp/settings/password.html @@ -0,0 +1,34 @@ +<div data-lift="surround?with=default;at=content"> + <div class="row"> + <div class="span12"> + <form data-lift="Form.ajax?class=form-horizontal"> + <div data-lift="PasswordReset"> + <fieldset> + + <div class="control-group"> + <label class="control-label"></label> + <div class="controls"> + <p><strong>Enter your password:</strong></p> + <input class="xlarge" maxlength="254" id="id_pwd" size="32" /> + <span data-alertid="id_pwd_err" class="notice-block"></span> + </div> + </div> + + <div class="control-group"> + <label class="control-label"></label> + <div class="controls"> + <p><strong>Confirm your password:</strong></p> + <input class="xlarge" maxlength="254" id="id_pwd2" size="32" /> + <span data-alertid="id_pwd2_err" class="notice-block"></span> + </div> + </div> + + </fieldset> + <div class="form-actions"> + <input id="id_submit" class="btn btn-primary"> + </div> + </div> + </form> + </div> + </div> +</div> diff --git a/src/main/webapp/settings/profile.html b/src/main/webapp/settings/profile.html @@ -0,0 +1,3 @@ +<div lift="layout?with=settings-wrap;at=content"> + <div lift="ProfileScreen?ajax=true"></div> +</div> diff --git a/src/main/webapp/signup/blog.html b/src/main/webapp/signup/blog.html @@ -0,0 +1,16 @@ +<div data-lift="surround?with=default;at=content"> + + <div data-lift="SignUpBlog"> + <div lift="Notices"></div> + <div class="hero-unit"> + <div class="inner"> + <form data-lift="form.ajax"> + www.joereader.com/blog/ <input id="blog-site-url" placeholder="Blog Url"><br> + <input id="blog-continue" class="btn btn-success"> + </form> + + </div> + </div> + <div class="row"><div class="span12"><span id="blog-area"></span></div></div> + </div> +</div> +\ No newline at end of file diff --git a/src/main/webapp/signup/categories.html b/src/main/webapp/signup/categories.html @@ -0,0 +1,28 @@ +<div data-lift="surround?with=base-wrap;at=content"> + <lift:head> + <title lift="Menu.title">Joe Reader: %*%</title> + </lift:head> + <div class="main-content"> + <div lift="Notices"></div> + <div class="hero-unit"> + <div data-lift="SignUpCategories" class="inner"> + + <form data-lift="form.ajax"> + <label class="inline"> + <input id="category-input" placeholder="Submit New Category"> + <input id="category-add" class="btn btn-primary"> + </label> + </form> + + <br> + + <label id="chosen-categories" class="inline"></label> + + <form data-lift="form.ajax"> + <input id="categories-continue" class="btn btn-success"> + </form> + </div> + </div> + </div> + <span data-lift="embed?what=/templates-hidden/parts/footer"></span> +</div> +\ No newline at end of file diff --git a/src/main/webapp/signup/password.html b/src/main/webapp/signup/password.html @@ -0,0 +1,19 @@ +<div data-lift="surround?with=base-wrap;at=content"> + <lift:head> + <title lift="Menu.title">Joe Reader: %*%</title> + </lift:head> + <div class="main-content"> + <div lift="Notices"></div> + <div class="hero-unit"> + <div data-lift="SignUpPassword" class="inner"> + + <form data-lift="form.ajax"> + the password <input id="user-password" placeholder="password"><br> + <input id="user-continue" class="btn btn-success"> + </form> + + </div> + </div> + </div> + <span data-lift="embed?what=/templates-hidden/parts/footer"></span> +</div> +\ No newline at end of file diff --git a/src/main/webapp/signup/user.html b/src/main/webapp/signup/user.html @@ -0,0 +1,16 @@ +<div data-lift="surround?with=default;at=content"> + + <div data-lift="SignUpUser"> + <div lift="Notices"></div> + <div class="hero-unit"> + <div class="inner"> + <form data-lift="form.ajax"> + www.joereader.com/ <input id="user-site-url" placeholder="Your Url"><br> + <input id="user-continue" class="btn btn-success"> + </form> + + </div> + </div> + <div class="row"><div class="span12"><span id="user-area"></span></div></div> + </div> +</div> +\ No newline at end of file diff --git a/src/main/webapp/signup/verify.html b/src/main/webapp/signup/verify.html @@ -0,0 +1,40 @@ +<div data-lift="surround?with=base-wrap;at=content"> + <lift:head> + <title lift="Menu.title">Joe Reader: %*%</title> + </lift:head> + <div class="main-content"> + <div lift="Notices"></div> + <div data-lift="SignUpVerification" class="hero-unit"> + <div id="verify-state" class="inner"> + + <h2>Verify your Blog</h2> + <form data-lift="form.ajax"> + Blog Url: <input id="blog-url" placeholder="ie. www.example.com/blog"> + <span data-alertid="blog-url_err" class="notice-block"></span> + <br> + <input id="search-blog" data-loading-text="Loading..."> + </form> + + Select yourself as a writer for your blog. If there's more than one writer, answer if you're an owner. + <div id="writer-list"></div><br> + + <div id="blog-owner-q" class="hide"> + Are you the owner of the blog? + <input id="blog-owner-q-yes" class="radio" /> <span>Yes</span> + <input id="blog-owner-q-no" class="radio" /> <span>No</span> + </div> + + <div id="verification-info" class="hide"> + <form data-lift="form.ajax"> + <div id="verification-info-input"></div> + <p>Copy and paste this meta tag to the <head> of your index.html file or equivalent.</p><br> + <input id="verify-blog"> + </form> + <div id="verification-unsuccessful" class="hide">Verification was unsuccessful. Double check if your <head> contains the meta content above.</div> + </div> + + </div> + </div> + </div> + <span data-lift="embed?what=/templates-hidden/parts/footer"></span> +</div> +\ No newline at end of file diff --git a/src/main/webapp/signup/writer.html b/src/main/webapp/signup/writer.html @@ -0,0 +1,19 @@ +<div data-lift="surround?with=base-wrap;at=content"> + <lift:head> + <title lift="Menu.title">Joe Reader: %*%</title> + </lift:head> + <div class="main-content"> + <div lift="Notices"></div> + <div class="hero-unit"> + <div data-lift="SignUpWriter" class="inner"> + <h2>Thank you for joining!</h2> + <p>Are you a blog writer?</p> + <form data-lift="form.ajax"> + <input type="submit" id="yes-button"><br> + <input type="submit" id="no-button"> + </form> + </div> + </div> + </div> + <span data-lift="embed?what=/templates-hidden/parts/footer"></span> +</div> +\ No newline at end of file diff --git a/src/main/webapp/templates-hidden/base-wrap.html b/src/main/webapp/templates-hidden/base-wrap.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content=""> + <meta name="author" content="Joe Reader"> + <meta name="Copyright" content="Copyright Joe Reader 2012. All Rights Reserved."> + + <!-- css --> + <link href="/css/styles.css" rel="stylesheet" data-lift="with-resource-id"> + + <!-- Le HTML5 shim, for IE6-8 support of HTML elements --> + <!--[if lt IE 9]> + <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> + <![endif]--> + <!-- fav and touch icons --> + <!-- + <link rel="shortcut icon" href="/img/favicon.ico"> + <link rel="apple-touch-icon" href="images/apple-touch-icon.png"> + <link rel="apple-touch-icon" sizes="72x72" href="images/apple-touch-icon-72x72.png"> + <link rel="apple-touch-icon" sizes="114x114" href="images/apple-touch-icon-114x114.png"> + --> + </head> + <body> + <div class="navbar navbar-fixed-top"> + <div class="navbar-inner"> + <div class="container"> + <a data-target=".nav-collapse" data-toggle="collapse" class="btn btn-navbar"> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </a> + <div class="brand"> + <a href="/">Read Means</a> + </div> + <div class="nav-collapse collapse"> + <ul data-lift="Menus.group?group=topbar" class="nav"></ul> + <span data-lift="UserTopbar"></span> + </div> + </div> + </div> + </div> + <div id="white-overlay" class="container hide" style="position:absolute; left:0; top:0; width:100%; height:100%; z-index: 1000; opacity: 0.7; background-color: #fff; filter: alpha(opacity=70);"></div> + <div id="content" class="container"></div><!-- /container --> + + <!-- javascript --> + <script src="/js/script.js" data-lift="with-resource-id"></script> + </body> +</html> diff --git a/src/main/webapp/templates-hidden/default.html b/src/main/webapp/templates-hidden/default.html @@ -0,0 +1,17 @@ +<div data-lift="surround?with=base-wrap;at=content"> + <lift:head> + <title data-lift="Menu.title">Joe Reader: %*%</title> + </lift:head> + <div class="main-content"> + <div class="page-header"> + <h1> + <span data-lift="Menu.title"></span> + <div style="display:none; float:right;" id="ajax-spinner"> + <img src="/img/spinner.gif" width="16" height="16"/> + </div> + </h1> + </div> + <div id="content"></div> + </div> + <span data-lift="embed?what=/templates-hidden/parts/footer"></span> +</div> diff --git a/src/main/webapp/templates-hidden/parts/blog-profile.html b/src/main/webapp/templates-hidden/parts/blog-profile.html @@ -0,0 +1,16 @@ +<div> + + <div class="row"><div class="span12">URL: <span data-lift="ProfileLocBlog.url" id="blog-url"></span></div></div> + <div class="row"><div class="span12">Name: <span data-lift="ProfileLocBlog.name" id="blog-name" ></span></div></div> + <div class="row"><div class="span12">About: <span data-lift="ProfileLocBlog.description" id="blog-about" ></span></div></div> + <div class="row"><div class="span12">Img: <img data-lift="ProfileLocBlog.img" id="blog-img"></div></div> + <div class="row"><div class="span12">Bg Img: <img data-lift="ProfileLocBlog.bgImg" id="blog-bgimg"></div></div> + + <div class="row"><div class="span12">Writers: <span data-lift="ProfileLocBlog.writers" id="blog-writers" ></span></div></div> + + <div class="row"><div class="span12"><span data-lift="ProfileLocBlog.uploadBgImg" id="bg-fileupload-outer" ></span></div></div> + <div class="row"><div class="span12"><span data-lift="ProfileLocBlog.uploadImg" id="pic-fileupload-outer" ></span></div></div> + + <div class="row"><div class="span12">Articles: <br><span data-lift="ProfileLocBlog.articles" id="blog-articles" ></span></div></div> + +</div> +\ No newline at end of file diff --git a/src/main/webapp/templates-hidden/parts/footer.html b/src/main/webapp/templates-hidden/parts/footer.html @@ -0,0 +1,4 @@ +<footer> + <br /> + <p>© Joe Reader 2012</p> +</footer> diff --git a/src/main/webapp/templates-hidden/parts/user-profile.html b/src/main/webapp/templates-hidden/parts/user-profile.html @@ -0,0 +1,15 @@ +<div> + + <div class="row"><div class="span12">Name: <span data-lift="ProfileLocUser.name" id="user-name" ></span></div></div> + <div class="row"><div class="span12">About: <span data-lift="ProfileLocUser.about" id="user-about" ></span></div></div> + <div class="row"><div class="span12">Youtube Vid: <span data-lift="ProfileLocUser.introVid" id="intro-vid" ></span></div></div> + + <div class="row"><div class="span12">Img: <img data-lift="ProfileLocUser.img" id="user-img"></div></div> + <div class="row"><div class="span12">Bg Img: <img data-lift="ProfileLocUser.bgImg" id="user-bgimg"></div></div> + + <div class="row"><div class="span12"><span data-lift="ProfileLocUser.uploadBgImg" id="bg-fileupload-outer" ></span></div></div> + <div class="row"><div class="span12"><span data-lift="ProfileLocUser.uploadBgImg" id="pic-fileupload-outer" ></span></div></div> + + <div class="row"><div class="span12">Articles: <br><span data-lift="ProfileLocUser.articles" id="user-articles" ></span></div></div> + +</div> +\ No newline at end of file diff --git a/src/main/webapp/templates-hidden/settings-wrap.html b/src/main/webapp/templates-hidden/settings-wrap.html @@ -0,0 +1,12 @@ +<div data-lift="surround?with=default;at=content"> + <div class="row"> + <div class="span12"> + <ul data-lift="Menus.group?group=settings" class="nav nav-tabs"></ul> + </div> + <div class="span10"> + <div id="content"></div> + </div> + <div class="span2"> + </div> + </div> +</div> diff --git a/src/main/webapp/templates-hidden/wizard-all.html b/src/main/webapp/templates-hidden/wizard-all.html @@ -0,0 +1,33 @@ +<div class="form-horizontal"> + <wizard:screen_info><div>Page <wizard:screen_number/> of <wizard:total_screens/></div></wizard:screen_info> + <wizard:wizard_top> <div> <wizard:bind/> </div> </wizard:wizard_top> + <wizard:screen_top> <div> <wizard:bind/> </div> </wizard:screen_top> + <!-- <wizard:errors> <div> <ul> <wizard:item> <li> <wizard:bind/> </li> </wizard:item> </ul> </div> </wizard:errors> --> + <fieldset> + <wizard:fields> + <div class="control-group" lift:bind="wizard:line"> + <wizard:label><label class="control-label" wizard:for=""><wizard:bind></wizard:bind></label></wizard:label> + <div class="controls"> + <wizard:form></wizard:form> + <wizard:help><span class="help-inline"><wizard:bind></wizard:bind></span></wizard:help> + <span class="alert-block"> + <wizard:field_errors> + <ul> + <wizard:error> + <li class="error"><wizard:bind></wizard:bind></li> + </wizard:error> + </ul> + </wizard:field_errors> + </span> + </div> + </div> + </wizard:fields> + </fieldset> + <div class="form-actions"> + <wizard:prev></wizard:prev> + <wizard:next></wizard:next> + <wizard:cancel></wizard:cancel> + </div> + <wizard:screen_bottom><div><wizard:bind></wizard:bind></div></wizard:screen_bottom> + <wizard:wizard_bottom><div><wizard:bind></wizard:bind></div></wizard:wizard_bottom> +</div> diff --git a/src/main/webapp/user.html b/src/main/webapp/user.html @@ -0,0 +1,3 @@ +<div data-lift="surround?with=default;at=content"> + <span data-lift="embed?what=/templates-hidden/parts/user-profile"></span> +</div> +\ No newline at end of file