├── .cache ├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs └── org.scala-ide.sdt.core.prefs ├── Procfile ├── bin └── application.conf ├── build.sbt ├── policy ├── project ├── build.properties ├── cdn.scala ├── plugins.sbt └── translate-zh.scala ├── readme.md ├── sbt ├── sbt ├── sbt-launch-0.11.3-2.jar └── sbt.cmd ├── scala-tour-zh.md ├── scala-tour.md ├── src ├── main │ ├── resources │ │ └── application.conf │ └── scala │ │ └── com │ │ └── yankay │ │ └── scalaTour │ │ ├── ScalaScriptRunner.scala │ │ ├── Web.scala │ │ └── script │ │ └── TaskMem.scala └── test │ └── scala │ ├── com │ └── yankay │ │ └── scalaTour │ │ ├── ScalaScriptCompilerTest.scala │ │ └── script │ │ └── TaskMem.scala │ └── test.scala └── webapp ├── apple-touch-icon.png ├── css ├── bootstrap-responsive.css ├── bootstrap-responsive.min.css ├── bootstrap.css ├── bootstrap.min.css ├── codemirror.css ├── elegant.css ├── impress-demo.css ├── scala-tour.css └── solarized.css ├── favicon.png ├── img ├── Lambda_lc.svg.png ├── glyphicons-halflings-white.png ├── glyphicons-halflings.png ├── glyphicons_065_tag.png ├── glyphicons_221_unshare.png └── logo.png ├── index-zh.html ├── index.html └── js ├── bootstrap.js ├── bootstrap.min.js ├── clike.js ├── codemirror.js ├── codemirror.min.js ├── go.js ├── impress.js ├── jquery.js ├── jquery.min.js ├── matchbrackets.js └── scala-tour.js /.cache: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankay/scala-tour/7a482eea52e1ee429cb5961cb7ee5272227b44b1/.cache -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .idea_modules 3 | target 4 | project/boot 5 | project/target 6 | project/plugins/target 7 | sftp-config.json 8 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | scala-tour 3 | 4 | 5 | org.scala-ide.sdt.core.scalabuilder 6 | 7 | 8 | 9 | org.scala-ide.sdt.core.scalanature 10 | org.eclipse.jdt.core.javanature 11 | 12 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | -------------------------------------------------------------------------------- /.settings/org.scala-ide.sdt.core.prefs: -------------------------------------------------------------------------------- 1 | Xcheck-null=false 2 | Xcheckinit=false 3 | Xdisable-assertions=false 4 | Xelide-below=-2147483648 5 | Xexperimental=false 6 | Xfatal-warnings=false 7 | Xfuture=false 8 | Xlog-implicits=false 9 | Xno-uescape=false 10 | Xpluginsdir=C\:\\Program Files\\eclipse\\configuration\\org.eclipse.osgi\\bundles\\245\\1\\.cp\\lib 11 | Ybuild-manager-debug=false 12 | Yno-generic-signatures=false 13 | Yno-imports=false 14 | Ypresentation-debug=false 15 | Ypresentation-delay=0 16 | Ypresentation-verbose=false 17 | Yrecursion=0 18 | Yself-in-annots=false 19 | Ystruct-dispatch=poly-cache 20 | Ywarn-dead-code=false 21 | buildmanager=sbt 22 | compileorder=Mixed 23 | debugIncremental=true 24 | deprecation=false 25 | eclipse.preferences.version=1 26 | explaintypes=false 27 | formatter.alignParameters=false 28 | formatter.alignSingleLineCaseStatements=false 29 | formatter.alignSingleLineCaseStatements.maxArrowIndent=40 30 | formatter.compactControlReadability=false 31 | formatter.compactStringConcatenation=false 32 | formatter.doubleIndentClassDeclaration=false 33 | formatter.formatXml=true 34 | formatter.indentLocalDefs=false 35 | formatter.indentPackageBlocks=true 36 | formatter.indentSpaces=2 37 | formatter.indentWithTabs=false 38 | formatter.multilineScaladocCommentsStartOnFirstLine=false 39 | formatter.placeScaladocAsterisksBeneathSecondAsterisk=false 40 | formatter.preserveDanglingCloseParenthesis=false 41 | formatter.preserveSpaceBeforeArguments=false 42 | formatter.rewriteArrowSymbols=false 43 | formatter.spaceBeforeColon=false 44 | formatter.spaceInsideBrackets=false 45 | formatter.spaceInsideParentheses=false 46 | formatter.spacesWithinPatternBinders=true 47 | g=vars 48 | no-specialization=false 49 | nowarn=false 50 | optimise=false 51 | organizeimports.expandcollapse=expand 52 | organizeimports.groups=java$scala$org$com 53 | organizeimports.scalapackage=false 54 | organizeimports.wildcards=scalaz$scalaz.Scalaz 55 | scala.compiler.useProjectSettings=true 56 | stopBuildOnError=true 57 | syntaxColouring.bracket.backgroundColourEnabled=false 58 | syntaxColouring.bracket.bold=false 59 | syntaxColouring.bracket.colour=0,0,0 60 | syntaxColouring.bracket.enabled=true 61 | syntaxColouring.bracket.italic=false 62 | syntaxColouring.bracket.underline=false 63 | syntaxColouring.default.backgroundColourEnabled=false 64 | syntaxColouring.default.bold=false 65 | syntaxColouring.default.colour=0,0,0 66 | syntaxColouring.default.enabled=true 67 | syntaxColouring.default.italic=false 68 | syntaxColouring.default.underline=false 69 | syntaxColouring.keyword.backgroundColourEnabled=false 70 | syntaxColouring.keyword.bold=true 71 | syntaxColouring.keyword.colour=127,0,85 72 | syntaxColouring.keyword.enabled=true 73 | syntaxColouring.keyword.italic=false 74 | syntaxColouring.keyword.underline=false 75 | syntaxColouring.operator.backgroundColourEnabled=false 76 | syntaxColouring.operator.bold=false 77 | syntaxColouring.operator.colour=0,0,0 78 | syntaxColouring.operator.enabled=true 79 | syntaxColouring.operator.italic=false 80 | syntaxColouring.operator.underline=false 81 | syntaxColouring.scaladoc.backgroundColourEnabled=false 82 | syntaxColouring.scaladoc.bold=false 83 | syntaxColouring.scaladoc.colour=63,95,191 84 | syntaxColouring.scaladoc.enabled=true 85 | syntaxColouring.scaladoc.italic=false 86 | syntaxColouring.scaladoc.underline=false 87 | syntaxColouring.singleLineComment.backgroundColourEnabled=false 88 | syntaxColouring.singleLineComment.bold=false 89 | syntaxColouring.singleLineComment.colour=63,127,95 90 | syntaxColouring.singleLineComment.enabled=true 91 | syntaxColouring.singleLineComment.italic=false 92 | syntaxColouring.singleLineComment.underline=false 93 | syntaxColouring.string.backgroundColourEnabled=false 94 | syntaxColouring.string.bold=false 95 | syntaxColouring.string.colour=42,0,255 96 | syntaxColouring.string.enabled=true 97 | syntaxColouring.string.italic=false 98 | syntaxColouring.string.underline=false 99 | syntaxColouring.xml.attributeName.backgroundColourEnabled=false 100 | syntaxColouring.xml.attributeName.bold=false 101 | syntaxColouring.xml.attributeName.colour=127,0,127 102 | syntaxColouring.xml.attributeName.enabled=true 103 | syntaxColouring.xml.attributeName.italic=false 104 | syntaxColouring.xml.attributeName.underline=false 105 | syntaxColouring.xml.attributeValue.backgroundColourEnabled=false 106 | syntaxColouring.xml.attributeValue.bold=false 107 | syntaxColouring.xml.attributeValue.colour=42,0,255 108 | syntaxColouring.xml.attributeValue.enabled=true 109 | syntaxColouring.xml.attributeValue.italic=true 110 | syntaxColouring.xml.attributeValue.underline=false 111 | syntaxColouring.xml.comment.backgroundColourEnabled=false 112 | syntaxColouring.xml.comment.bold=false 113 | syntaxColouring.xml.comment.colour=63,85,191 114 | syntaxColouring.xml.comment.enabled=true 115 | syntaxColouring.xml.comment.italic=false 116 | syntaxColouring.xml.comment.underline=false 117 | syntaxColouring.xml.equals.backgroundColourEnabled=false 118 | syntaxColouring.xml.equals.bold=false 119 | syntaxColouring.xml.equals.colour=0,0,0 120 | syntaxColouring.xml.equals.enabled=true 121 | syntaxColouring.xml.equals.italic=false 122 | syntaxColouring.xml.equals.underline=false 123 | syntaxColouring.xml.processingInstruction.backgroundColourEnabled=false 124 | syntaxColouring.xml.processingInstruction.bold=false 125 | syntaxColouring.xml.processingInstruction.colour=0,128,128 126 | syntaxColouring.xml.processingInstruction.enabled=true 127 | syntaxColouring.xml.processingInstruction.italic=false 128 | syntaxColouring.xml.processingInstruction.underline=false 129 | syntaxColouring.xml.tagDelimiter.backgroundColourEnabled=false 130 | syntaxColouring.xml.tagDelimiter.bold=false 131 | syntaxColouring.xml.tagDelimiter.colour=0,128,128 132 | syntaxColouring.xml.tagDelimiter.enabled=true 133 | syntaxColouring.xml.tagDelimiter.italic=false 134 | syntaxColouring.xml.tagDelimiter.underline=false 135 | syntaxColouring.xml.tagName.backgroundColourEnabled=false 136 | syntaxColouring.xml.tagName.bold=false 137 | syntaxColouring.xml.tagName.colour=63,127,127 138 | syntaxColouring.xml.tagName.enabled=true 139 | syntaxColouring.xml.tagName.italic=false 140 | syntaxColouring.xml.tagName.underline=false 141 | target=jvm-1.6 142 | unchecked=false 143 | verbose=false 144 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: target/start com.yankay.scalaTour.Web 2 | -------------------------------------------------------------------------------- /bin/application.conf: -------------------------------------------------------------------------------- 1 | remote { 2 | akka { 3 | 4 | actor { 5 | provider = "akka.remote.RemoteActorRefProvider" 6 | } 7 | remote { 8 | transport = "akka.remote.netty.NettyRemoteTransport" 9 | netty { 10 | hostname = "127.0.0.1" 11 | port = 2552 12 | } 13 | } 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import com.typesafe.sbt.SbtStartScript 2 | 3 | seq(SbtStartScript.startScriptForClassesSettings: _*) 4 | 5 | net.virtualvoid.sbt.graph.Plugin.graphSettings 6 | 7 | name := "scala-tour" 8 | 9 | version := "1.0" 10 | 11 | scalaVersion := "2.10.1" 12 | 13 | libraryDependencies ++= Seq( 14 | "org.scala-lang" % "scala-actors" % "2.10.1", 15 | "org.scala-lang" % "scala-compiler" % "2.10.1", 16 | "com.typesafe.akka" %% "akka-testkit" % "2.1.2", 17 | "com.typesafe.akka" %% "akka-actor" % "2.1.2", 18 | "com.typesafe.akka" %% "akka-remote" % "2.1.2", 19 | "org.eclipse.jetty" % "jetty-server" % "8.1.10.v20130312", 20 | "org.eclipse.jetty" % "jetty-servlet" % "8.1.10.v20130312", 21 | "org.eclipse.jetty" % "jetty-servlets" % "8.1.10.v20130312", 22 | "org.json4s" %% "json4s-jackson" % "3.2.2", 23 | "commons-beanutils" % "commons-beanutils" % "1.8.3", 24 | "org.specs2" % "specs2_2.10" % "1.14" 25 | ) -------------------------------------------------------------------------------- /policy: -------------------------------------------------------------------------------- 1 | /* AUTOMATICALLY GENERATED ON Sat Aug 10 20:31:30 PDT 2013*/ 2 | /* DO NOT EDIT */ 3 | 4 | grant { 5 | permission java.io.FilePermission "${user.home}/.ivy2/cache/com.typesafe", "read"; 6 | permission java.io.FilePermission "${user.home}/.ivy2/cache/com.typesafe/-", "read"; 7 | permission java.io.FilePermission "${user.home}/.ivy2/cache/com.typesafe.akka", "read"; 8 | permission java.io.FilePermission "${user.home}/.ivy2/cache/com.typesafe.akka/-", "read"; 9 | permission java.io.FilePermission "./target/specs2-reports", "read, write"; 10 | permission java.io.FilePermission "./target/specs2-reports/-", "read, write"; 11 | 12 | permission java.io.FilePermission "/tmp", "read"; 13 | 14 | permission java.io.FilePermission "./bin/application.conf", "read"; 15 | permission java.io.FilePermission "/usr/lib/jvm/java-*/-", "read"; 16 | permission java.io.FilePermission "/usr/lib/jvm/java-*/jre/-", "read"; 17 | permission java.io.FilePermission "/usr/java/packages/lib/ext/-", "read"; 18 | permission java.io.FilePermission "/usr/java/packages/lib/ext", "read"; 19 | permission java.io.FilePermission "/usr/share/java/-", "read"; 20 | 21 | permission java.util.PropertyPermission "java.version", "read"; 22 | permission java.util.PropertyPermission "java.vendor", "read"; 23 | permission java.util.PropertyPermission "os.name", "read"; 24 | permission java.util.PropertyPermission "os.version", "read"; 25 | permission java.util.PropertyPermission "os.arch", "read"; 26 | 27 | permission java.io.FilePermission "/proc/self/stat", "read"; 28 | 29 | permission java.net.SocketPermission "github.com:443", "connect, resolve"; 30 | permission java.net.SocketPermission "raw.github.com:443", "connect, resolve"; 31 | permission java.net.SocketPermission "scala-lang.org:80", "connect, resolve"; 32 | permission java.net.SocketPermission "127.0.0.1:2552", "connect, resolve, listen, accept"; 33 | 34 | permission java.lang.RuntimePermission "getenv.AKKA_HOME", "read"; 35 | permission java.util.PropertyPermission "akka.home", "read"; 36 | permission java.util.PropertyPermission "*", "read, write"; 37 | permission java.lang.RuntimePermission "setContextClassLoader"; 38 | permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; 39 | permission java.lang.RuntimePermission "accessClassInPackage.sun.misc"; 40 | permission java.lang.RuntimePermission "accessDeclaredMembers"; 41 | permission java.lang.RuntimePermission "modifyThread"; 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.12.2 2 | -------------------------------------------------------------------------------- /project/cdn.scala: -------------------------------------------------------------------------------- 1 | object CDN { 2 | 3 | def main(args: Array[String]) { 4 | val url = args(0) 5 | val cdn = scala.collection.mutable.Map[String, String]() 6 | cdn += ("""href="css/""" -> ("href=\"" + url + "css/").toString()) 7 | cdn += ("""src="js/""" -> ("src=\"" + url + "js/").toString()) 8 | cdn += ("""src="img/""" -> ("src=\"" + url + "img/").toString()) 9 | 10 | scala.io.Source.stdin.getLines.foreach( 11 | x => { 12 | var line = x 13 | cdn.foreach(p => line = line.replace(p._1, p._2)) 14 | println(line) 15 | }) 16 | } 17 | } -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-start-script" % "0.7.0") 2 | 3 | addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.1.1") 4 | 5 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.7.1") 6 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Scala Tour 2 | 3 | A cooler tour the same as scala language 4 | 5 | [http://www.scala-tour.com/](http://www.scala-tour.com/) 6 | 7 | 8 | ## Compile And Run 9 | ``` 10 | Linux/Mac: 11 | ./sbt/sbt stage 12 | ./target/start 13 | 14 | Windows can only compile 15 | sbt\sbt stage 16 | ``` 17 | view 18 | http://localhost:8080/#/welcome 19 | 20 | ## Pure Text 21 | [Scala tour](https://github.com/yankay/scala-tour/blob/master/scala-tour.md) 22 | 23 | ## Translate 24 | The tour is can be translate to Chinese by these scripts: 25 | ``` 26 | cat webapp/index.html | scala project/translate-zh.scala > webapp/index-cn.html 27 | cat scala-tour.md | scala project/translate-zh.scala > scala-tour-cn.md 28 | 29 | ``` 30 | -------------------------------------------------------------------------------- /sbt/sbt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | EXTRA_ARGS="" 3 | if [ "$MESOS_HOME" != "" ]; then 4 | EXTRA_ARGS="-Djava.library.path=$MESOS_HOME/lib/java" 5 | fi 6 | export SPARK_HOME=$(cd "$(dirname $0)/.."; pwd) 7 | export SPARK_TESTING=1 # To put test classes on classpath 8 | java -Xmx1200M -XX:MaxPermSize=200m $EXTRA_ARGS -jar $SPARK_HOME/sbt/sbt-launch-*.jar "$@" 9 | -------------------------------------------------------------------------------- /sbt/sbt-launch-0.11.3-2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankay/scala-tour/7a482eea52e1ee429cb5961cb7ee5272227b44b1/sbt/sbt-launch-0.11.3-2.jar -------------------------------------------------------------------------------- /sbt/sbt.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | set EXTRA_ARGS= 3 | if not "%MESOS_HOME%x"=="x" set EXTRA_ARGS=-Djava.library.path=%MESOS_HOME%\lib\java 4 | set SPARK_HOME=%~dp0.. 5 | java -Xmx1200M -XX:MaxPermSize=200m %EXTRA_ARGS% -jar %SPARK_HOME%\sbt\sbt-launch-0.11.3-2.jar "%*" 6 | -------------------------------------------------------------------------------- /scala-tour-zh.md: -------------------------------------------------------------------------------- 1 | # Scala 指南 2 | 3 | [http://zh.scala-修改为ur.com/](http://zh.scala-修改为ur.com/) 4 | 5 | ## 基本 6 | 7 | ### 表达式和值 8 | 9 | 在Scala中,几乎所有的语言元素都是表达式。println("hello wolrd")是一个表达式, "hello"+" world" 10 | 也是一个表达式。 11 | 12 | 可以通过val定义一个常量,亦可以通过var定义一个变量。推荐多使用常量。 13 | 14 | ``` 15 | var helloWorld = "hello" + " world" 16 | println(helloWorld) 17 | 18 | val again = " again" 19 | helloWorld = helloWorld + again 20 | println(helloWorld) 21 | ``` 22 | 23 | ### 函数是一等公民 24 | 25 | 可以使用def来定义一个函数。函数体是一个表达式。 26 | 27 | 使用Block表达式的时候,默认最后一行的返回是返回值,无需显式指定。 28 | 29 | 函数还可以像值一样,赋值给var或val。因此他也可以作为参数传给另一个函数。 30 | 31 | ``` 32 | def square(a: Int) = a * a 33 | 34 | def squareWithBlock(a: Int) = { 35 | a * a 36 | } 37 | 38 | val squareVal = (a: Int) => a * a 39 | 40 | def addOne(f: Int => Int, arg: Int) = f(arg) + 1 41 | 42 | println("square(2):" + square(2)) 43 | println("squareWithBlock(2):" + squareWithBlock(2)) 44 | println("squareVal(2):" + squareVal(2)) 45 | println("addOne(squareVal,2):" + addOne(squareVal, 2)) 46 | ``` 47 | 48 | 49 | 50 | ### 借贷模式 51 | 52 | 由于函数可以像值一样作为参数传递,所以可以方便的实现借贷模式。 53 | 54 | 这个例子是从/proc/self/stat文件中读取当前进程的pid。 55 | 56 | withScanner封装了try-finally块,所以调用者不用再close。 57 | 58 | 注:当表达式没有返回值时,默认返回Unit。 59 | 60 | ``` 61 | import scala.reflect.io.File 62 | import java.util.Scanner 63 | 64 | def withScanner(f: File, op: Scanner => Unit) = { 65 | val scanner = new Scanner(f.bufferedReader) 66 | try { 67 | op(scanner) 68 | } finally { 69 | scanner.close() 70 | } 71 | } 72 | 73 | withScanner(File("/proc/self/stat"), 74 | scanner => println("pid is " + scanner.next())) 75 | ``` 76 | 77 | 78 | ### 按名称传递参数 79 | 80 | 这个例子演示了按名称传递参数,由于有除以0,所以运行该程序会产生异常。 81 | 82 | 试着将def log(msg: String)修改为def log(msg: => String)。由按值传递修改为按名称传递后将不会产生异常。 83 | 84 | 85 | 因为log函数的参数是按名称传递,参数会等到真正访问的时候才会计算,由于logEnable = false,所以被跳过。 86 | 87 | 88 | 按名称传递参数可以减少不必要的计算和异常。 89 | 90 | 91 | ``` 92 | val logEnable = false 93 | 94 | def log(msg: String) = 95 | if (logEnable) println(msg) 96 | 97 | val MSG = "programing is running" 98 | 99 | log(MSG + 1 / 0) 100 | ``` 101 | 102 | ### 类定义 103 | 104 | 可以用class关键字来定义类。并通过new来创建类。 105 | 在定义类时可以定义字段,如firstName,lastName。这样做还可以自动生成构造函数。 106 | 可以在类中通过def定义函数。var和val定义字段。 107 | 函数名是任何字符如+,-,*,/。 108 | 例子中obama.age_=(51)的函数调用,可以简化为obama.age = 51 。 109 | obama.age()的函数调用,可以省略小括号,简化为obama.age。 110 | 111 | ``` 112 | class Person(val firstName: String, val lastName: String) { 113 | 114 | private var _age = 0 115 | def age = _age 116 | def age_=(newAge: Int) = _age = newAge 117 | 118 | def fullName() = firstName + " " + lastName 119 | 120 | override def 修改为String() = fullName() 121 | } 122 | 123 | val obama: Person = new Person("Barack", "Obama") 124 | 125 | println("Person: " + obama) 126 | println("firstName: " + obama.firstName) 127 | println("lastName: " + obama.lastName) 128 | obama.age_=(51) 129 | println("age: " + obama.age()) 130 | ``` 131 | 132 | 133 | ### 鸭子类型 134 | 135 | 走起来像鸭子,叫起来像鸭子,就是鸭子。 136 | 这个例子中使用{ def close(): Unit }作为参数类型。因此任何含有close()的函数的类都可以作为参数。 137 | 不必使用继承这种不够灵活的特性。 138 | 139 | ``` 140 | def withClose(closeAble: { def close(): Unit }, op: { def close(): Unit } => Unit) { 141 | try { 142 | op(closeAble) 143 | } finally { 144 | closeAble.close() 145 | } 146 | } 147 | 148 | class Connection { 149 | def close() = println("close Connection") 150 | } 151 | 152 | val conn: Connection = new Connection() 153 | withClose(conn, conn => 154 | println("do something with Connection")) 155 | ``` 156 | 157 | 158 | ### 柯里化 159 | 160 | 这个例子和上面的功能相同。不同的是使用了柯里化(Currying)的技术。 161 | def add(x:Int, y:Int) = x + y 是普通的函数 162 | def add(x:Int) = (y:Int) => x + y 是柯里化后的函数,相当于返回一个匿名函数表达式。 163 | def add(x:Int)(y:Int) = x + y 是上面的简化写法 164 | 柯里化可以让我们构造出更像原生语言提供的功能的代码 165 | 例子中的withclose(...)(...)换成withclose(...){...} 166 | 是否和java中的synchronized关键字用法很像? 167 | 168 | ``` 169 | def withClose(closeAble: { def close(): Unit })(op: { def close(): Unit } => Unit) { 170 | try { 171 | op(closeAble) 172 | } finally { 173 | closeAble.close() 174 | } 175 | } 176 | 177 | class Connection { 178 | def close() = println("close Connection") 179 | } 180 | 181 | val conn: Connection = new Connection() 182 | withClose(conn)(conn => 183 | println("do something with Connection")) 184 | ``` 185 | 186 | 187 | ### 范型 188 | 189 | 之前的例子可以使用泛型变得更简洁更灵活。 190 | 试着将val msg = "123456"修改为val msg = 123456。 191 | 虽然msg由String类型变为Int类型,但是由于使用了泛型,代码依旧可以正常运行。 192 | 193 | ``` 194 | def withClose[A <: { def close(): Unit }, B](closeAble: A)(op: A => B) { 195 | try { 196 | op(closeAble) 197 | } finally { 198 | closeAble.close() 199 | } 200 | } 201 | 202 | class Connection { 203 | val msg = "123456" 204 | def close() = println("close Connection") 205 | } 206 | 207 | val conn: Connection = new Connection() 208 | val msg = withClose(conn) { conn => 209 | { 210 | println("do something with Connection") 211 | conn.msg 212 | } 213 | } 214 | 215 | println(msg) 216 | ``` 217 | 218 | ### Traits 219 | 220 | Traits就像是有函数体的Interface。使用with关键字来混入。 221 | 这个例子是给java.util.ArrayList添加了foreach的功能。 222 | 试着再在后面加上with JsonAble,给list添加修改为Json的能力 223 | 224 | ``` 225 | trait ForEachAble[A] { 226 | def itera修改为r: java.util.Itera修改为r[A] 227 | def foreach(f: A => Unit) = { 228 | val iter = itera修改为r 229 | while (iter.hasNext) 230 | f(iter.next) 231 | } 232 | } 233 | 234 | trait JsonAble { 235 | override def 修改为String() = 236 | scala.util.parsing.json.JSONFormat.defaultFormatter(this) 237 | } 238 | 239 | val list = new java.util.ArrayList[Int]() with ForEachAble[Int] 240 | list.add(1); list.add(2) 241 | 242 | list.foreach(x => println(x)) 243 | println("Json: " + list.修改为String()) 244 | ``` 245 | 246 | 247 | ## 函数式编程 248 | 249 | ### 模式匹配 250 | 251 | 模式匹配是类似switch-case特性,但更加灵活;也类似if-else,但更加简约。 252 | 这个例子展示的使用用模式匹配实现斐波那契。 253 | 使用case来匹配参数,如果case _,则可以匹配任何参数。 254 | 这个例子有所不足,当输入负数时,会无限循环。 255 | 可以在case后添加if语句判断,将case n: Int 修改为 case n: Int if (n > 1)即可。 256 | Try 修改为 add case n: String => fibonacci(n.修改为Int) before case _ 这样就可以匹配String类型 257 | 在最后添加 println(fibonacci(-3));println(fibonacci("3"));来检查刚刚修改的效果。 258 | 259 | 260 | ``` 261 | def fibonacci(in: Any): Int = in match { 262 | case 0 => 0 263 | case 1 => 1 264 | case n: Int => fibonacci(n - 1) + fibonacci(n - 2) 265 | case _ => 0 266 | } 267 | 268 | println(fibonacci(3)) 269 | ``` 270 | 271 | ### Case Class 272 | 273 | case class 顾名思义就是为case语句专门设计的类。 274 | 在普通类的基础上添加了和类名一致的工厂方法, 275 | 还添加了hashcode,equals和修改为String等方法。 276 | 试试最后添加 println(Sum(1,2)) 。 277 | 由于使用了require(n >= 0)来检验参数,尝试使用负数,会抛出异常。 278 | 279 | 280 | ``` 281 | abstract class Expr 282 | 283 | case class FibonacciExpr(n: Int) extends Expr { 284 | require(n >= 0) 285 | } 286 | 287 | case class SumExpr(a: Expr, b: Expr) extends Expr 288 | 289 | def value(in: Expr): Int = in match { 290 | case FibonacciExpr(0) => 0 291 | case FibonacciExpr(1) => 1 292 | case FibonacciExpr(n) => value(SumExpr(FibonacciExpr(n - 1), FibonacciExpr(n - 2))) 293 | case SumExpr(a, b) => value(a) + value(b) 294 | case _ => 0 295 | } 296 | println(value(FibonacciExpr(3))) 297 | ``` 298 | 299 | ### 函数式的威力 300 | 301 | 这个例子是判断一个List中是否含有奇数。 302 | 第一行到倒数第二行是使用for循环的指令式编程解决。最后一行是通过函数式编程解决。 303 | 通过将函数作为参数,可以使程序极为简洁。其中 _ % 2 == 1 是 (x: Int) => x % 2 == 1 的简化写法。 304 | 相比于Ruby等动态语言,这威力来自于科学而不是魔法 305 | 306 | ``` 307 | val list = List(1, 2, 3, 4) 308 | 309 | def containsOdd(list: List[Int]): Boolean = { 310 | for (i <- list) { 311 | if (i % 2 == 1) 312 | return true; 313 | } 314 | return false; 315 | } 316 | 317 | println("list containsOdd by for loop:" + containsOdd(list)) 318 | 319 | println("list containsOdd by funtional:" + list.exists(_ % 2 == 1)) 320 | ``` 321 | 322 | ### 函数式真正的威力 323 | 324 | 函数式除了能简化代码外,更重要的是他关注的是Input和Output,函数本身没有副作用。 325 | 就是Unix pipeline一样,简单的命令组合在一起威力无穷。 326 | List的filter方法接受一个过滤函数,返回一个新的List。 327 | 如果你喜欢Unix pipeline的方式,你一定也会喜欢函数式编程。 328 | 这个例子是用函数式的代码模拟“cat file | grep 'warn' | grep '2013' | wc”的行为。 329 | 330 | ``` 331 | val file = List("warn 2013 msg", "warn 2012 msg", "error 2013 msg", "warn 2013 msg") 332 | 333 | println("cat file | grep 'warn' | grep '2013' | wc : " 334 | + file.filter(_.contains("warn")).filter(_.contains("2013")).size) 335 | ``` 336 | ### Word Count 337 | Word Count是一个MapReduce的一个经典示例。巧合的是,使用函数式的编程法,用类似MapReduce的方法实现word count也是最直观的。 338 | 这个例子介绍了List的两个重要的高阶方法map和reduceLeft。 339 | List的map方法接受一个转换函数,返回一个经过转换的List。 340 | List的reduceLeft方法接受一个合并函数,依次遍历合并。第一个参数是合并后的值,第二个参数是下一个需要合并的值。 341 | 将reduceLeft(_ + _)修改为foldLeft(0)(_ + _)。foldLeft比将reduceLeft更常用,因为他可以提供一个初始参数。 342 | Map和foldLeft可以代替大部分需要for循环的操作,并且使代码更清晰 343 | 344 | ``` 345 | val file = List("warn 2013 msg", "warn 2012 msg", "error 2013 msg", "warn 2013 msg") 346 | 347 | def wordcount(str: String): Int = str.split(" ").count("msg" == _) 348 | 349 | val num = file.map(wordcount).reduceLeft(_ + _) 350 | 351 | println("wordcount:" + num) 352 | ``` 353 | ### 尾递归 354 | 355 | 这个例子是用尾递归实现foldLeft。尾递归是递归的一种,特点在于会在函数的最末调用自身。 356 | 当用List做match case时,可以使用 :: 来解构。返回第一个元素head和剩余元素tail。 357 | 尾递归会在编译期优化,因此不用担心一般递归造成的栈溢出问题。 358 | 359 | ``` 360 | val file = List("warn 2013 msg", "warn 2012 msg", "error 2013 msg", "warn 2013 msg") 361 | 362 | def wordcount(str: String): Int = str.split(" ").count("msg" == _) 363 | 364 | def foldLeft(list: List[Int])(init: Int)(f: (Int, Int) => Int): Int = { 365 | list match { 366 | case List() => init 367 | case head :: tail => foldLeft(tail)(f(init, head))(f) 368 | } 369 | } 370 | 371 | val num = foldLeft(file.map(wordcount))(0)(_ + _) 372 | 373 | println("wordcount:" + num) 374 | ``` 375 | 376 | ### 更强大的For 377 | 378 | 循环语句是指令式编程的特产,Scala对其加以改进,成为适应函数式风格的利器。 379 | For循环也是有返回值的,其返回是一个List。在每一轮迭代中加入yield,yield后的值可以加入到List中。 380 | 这个例子是使用for循环代替map函数。 381 | 382 | ``` 383 | val file = List("warn 2013 msg", "warn 2012 msg", "error 2013 msg", "warn 2013 msg") 384 | 385 | def wordcount(str: String): Int = str.split(" ").count("msg" == _) 386 | 387 | val counts = 388 | for (line <- file) 389 | yield wordcount(line) 390 | 391 | val num = counts.reduceLeft(_ + _) 392 | 393 | println("wordcount:" + num) 394 | ``` 395 | ### Option 396 | 397 | NullException是Java中最常见的异常,要想避免他只有不断检查null。Scala提供了Option机制来解决。 398 | 这个例子包装了可能返回null的getProperty方法,使其返回一个Option。 399 | 这样就可以不再漫无目的地null检查。只要Option类型的值即可。 400 | 使用pattern match来检查是常见做法。也可以使用getOrElse来提供当为None时的默认值。 401 | 给力的是Option还可以看作是最大长度为1的List,其的强大功能都可以使用。 402 | 尝试在最后添加 osName.foreach(print _) 。 403 | 404 | 405 | ``` 406 | 407 | def getProperty(name: String): Option[String] = { 408 | val value = System.getProperty(name) 409 | if (value != null) Some(value) else None 410 | } 411 | 412 | val osName = getProperty("os.name") 413 | 414 | osName match { 415 | case Some(value) => println(value) 416 | case _ => println("none") 417 | } 418 | 419 | println(osName.getOrElse("none")) 420 | 421 | 422 | 423 | ``` 424 | 425 | 426 | ### Lazy 427 | 428 | Lazy可以延迟初始化。加上lazy的字段会在第一次访问的时候初始化。 429 | 这个例子是从github获得Scala的版本号,由于访问网络需要较多时间。 430 | 如果费尽力气获取到,而调用它的代码却不去访问就会很浪费。 431 | 可以使用lazy来延迟获取。 432 | 433 | ``` 434 | class ScalaCurrentVersion(val url: String) { 435 | lazy val source= { 436 | println("fetching from url...") 437 | scala.io.Source.fromURL(url).getLines().修改为List 438 | } 439 | lazy val majorVersion = source.find(_.contains("version.major")) 440 | lazy val minorVersion = source.find(_.contains("version.minor")) 441 | } 442 | val version = new ScalaCurrentVersion("https://raw.github.com/scala/scala/master/build.number") 443 | println("get scala version from " + version.url) 444 | version.majorVersion.foreach(println _) 445 | version.minorVersion.foreach(println _) 446 | ``` 447 | 448 | 449 | ## 并发 450 | 451 | ### 使用Ac修改为r 452 | 453 | Ac修改为r是Scala的并发模型。在2.10之后的版本中,使用[http://akka.io/](Akka)作为其推荐Ac修改为r实现。 454 | Ac修改为r是类似线程的实体,有一个邮箱。 455 | 可以通过system.ac修改为rOf来创建,receive获取邮箱消息,!向邮箱发送消息。 456 | 这个例子是一个EchoServer,接受信息并打印。 457 | 458 | ``` 459 | import akka.ac修改为r.{ Ac修改为r, Ac修改为rSystem, Props } 460 | 461 | val system = Ac修改为rSystem() 462 | 463 | class EchoServer extends Ac修改为r { 464 | def receive = { 465 | case msg: String => println("echo " + msg) 466 | } 467 | } 468 | 469 | val echoServer = system.ac修改为rOf(Props[EchoServer]) 470 | echoServer ! "hi" 471 | 472 | system.shutdown 473 | ``` 474 | 475 | ### Ac修改为r更简化的用法 476 | 477 | 可以通过更简化的办法声明Ac修改为r。 478 | 导入akka.ac修改为r.Ac修改为rDSL中的ac修改为r函数。 479 | 这个函数可以接受一个Ac修改为r的构造器Act,启动并返回Ac修改为r。 480 | 481 | 482 | ``` 483 | import akka.ac修改为r.Ac修改为rDSL._ 484 | import akka.ac修改为r.Ac修改为rSystem 485 | 486 | implicit val system = Ac修改为rSystem() 487 | 488 | val echoServer = ac修改为r(new Act { 489 | become { 490 | case msg => println("echo " + msg) 491 | } 492 | }) 493 | echoServer ! "hi" 494 | system.shutdown 495 | ``` 496 | ### Ac修改为r原理 497 | Ac修改为r比线程轻量。在Scala中可以创建数以百万级的Ac修改为r。奥秘在于Ac修改为r可以复用线程。 498 | Ac修改为r和线程是不同的抽象,他们的对应关系是由Dispatcher决定的。 499 | 这个例子创建4个Ac修改为r,每次调用的时候打印自身线程。 500 | 可以发现Ac修改为r和线程之间没有一对一的对应关系。 501 | 一个Ac修改为r可以使用多个线程,一个线程也会被多个Ac修改为r复用。 502 | 503 | ``` 504 | import akka.ac修改为r.{ Ac修改为r, Props, Ac修改为rSystem } 505 | import akka.testkit.CallingThreadDispatcher 506 | 507 | implicit val system = Ac修改为rSystem() 508 | 509 | class EchoServer(name: String) extends Ac修改为r { 510 | def receive = { 511 | case msg => println("server" + name + " echo " + msg + 512 | " by " + Thread.currentThread().getName()) 513 | } 514 | } 515 | 516 | val echoServers = (1 修改为 10).map(x => 517 | system.ac修改为rOf(Props(new EchoServer(x.修改为String)) 518 | .withDispatcher(CallingThreadDispatcher.Id))) 519 | (1 修改为 10).foreach(msg => 520 | echoServers(scala.util.Random.nextInt(10)) ! msg.修改为String) 521 | 522 | system.shutdown 523 | ``` 524 | 525 | 526 | 527 | ### 同步返回 528 | 529 | Ac修改为r非常适合于较耗时的操作。比如获取网络资源。 530 | 这个例子通过调用ask函数来获取一个Future。 531 | 在Ac修改为r内部通过 sender ! 传递结果。 532 | Future像Option一样有很多高阶方法,可以使用foreach查看结果。 533 | 534 | 535 | ``` 536 | import akka.ac修改为r.Ac修改为rDSL._ 537 | import akka.pattern.ask 538 | 539 | implicit val ec = scala.concurrent.ExecutionContext.Implicits.global 540 | implicit val system = akka.ac修改为r.Ac修改为rSystem() 541 | 542 | val versionUrl = "https://raw.github.com/scala/scala/master/starr.number" 543 | 544 | val fromURL = ac修改为r(new Act { 545 | become { 546 | case url: String => sender ! scala.io.Source.fromURL(url) 547 | .getLines().mkString("\n") 548 | } 549 | }) 550 | 551 | val version = fromURL.ask(versionUrl)(akka.util.Timeout(5 * 1000)) 552 | version.foreach(println _) 553 | 554 | system.shutdown 555 | ``` 556 | 557 | ### 异步返回 558 | 559 | 异步操作可以最大发挥效能。Scala的Futrue很强大,可以异步返回。 560 | 可以实现Futrue的onComplete方法。当Futrue结束的时候就会回调。 561 | 在调用ask的时候,可以设定超时。 562 | 563 | ``` 564 | import akka.ac修改为r.Ac修改为rDSL._ 565 | import akka.pattern.ask 566 | 567 | implicit val ec = scala.concurrent.ExecutionContext.Implicits.global 568 | implicit val system = akka.ac修改为r.Ac修改为rSystem() 569 | 570 | val versionUrl = "https://raw.github.com/scala/scala/master/starr.number" 571 | 572 | val fromURL = ac修改为r(new Act { 573 | become { 574 | case url: String => sender ! scala.io.Source.fromURL(url) 575 | .getLines().mkString("\n") 576 | } 577 | }) 578 | 579 | val version = fromURL.ask(versionUrl)(akka.util.Timeout(5 * 1000)) 580 | version onComplete { 581 | case msg => println(msg); system.shutdown 582 | } 583 | ``` 584 | ### 并发集合 585 | 586 | 这个例子是访问若干URL,并记录时间。 587 | 如果能并发访问,就可以大幅提高性能。 588 | 尝试将urls.map修改为urls.par.map。这样每个map中的函数都可以并发执行。 589 | 当函数式和并发结合,就会这样让人兴奋。 590 | 591 | ``` 592 | import scala.io.Codec 593 | import java.nio.charset.CodingErrorAction 594 | 595 | implicit val codec = Codec("UTF-8") 596 | codec.onMalformedInput(CodingErrorAction.REPLACE) 597 | codec.onUnmappableCharacter(CodingErrorAction.REPLACE) 598 | 599 | val urls = "http://scala-lang.org" :: "https://github.com/yankay/scala-修改为ur" :: Nil 600 | 601 | def fromURL(url: String) = scala.io.Source.fromURL(url).getLines().mkString("\n") 602 | 603 | val s = System.currentTimeMillis() 604 | time(urls.map(fromURL(_))) 605 | println("time: " + (System.currentTimeMillis - s) + "ms") 606 | ``` 607 | 608 | ### 并发wordcount 609 | 并发集合支持大部分集合的功能。 610 | 在前面有一个word count例子,也可以用并行集合加以实现。 611 | 不增加程序复杂性,却能大幅提高程序利用多核的能力。 612 | 613 | ``` 614 | val file = List("warn 2013 msg", "warn 2012 msg", "error 2013 msg", "warn 2013 msg") 615 | 616 | def wordcount(str: String): Int = str.split(" ").count("msg" == _) 617 | 618 | val num = file.par.map(wordcount).par.reduceLeft(_ + _) 619 | 620 | println("wordcount:" + num) 621 | ``` 622 | 623 | ### 远程Ac修改为r 624 | Ac修改为r是并发模型,也使用于分布式。 625 | 这个例子创建一个Echo服务器,通过ac修改为rOf来注册自己。 626 | 然后再创建一个client,通过Akka url来寻址。 627 | 除了是通过url创建的,其他使用的方法和普通Ac修改为r一样。 628 | 629 | ``` 630 | import akka.ac修改为r.{ Ac修改为r, Ac修改为rSystem, Props } 631 | import com.typesafe.config.ConfigFac修改为ry 632 | 633 | implicit val system = akka.ac修改为r.Ac修改为rSystem("RemoteSystem", 634 | ConfigFac修改为ry.load.getConfig("remote")) 635 | class EchoServer extends Ac修改为r { 636 | def receive = { 637 | case msg: String => println("echo " + msg) 638 | } 639 | } 640 | 641 | val server = system.ac修改为rOf(Props[EchoServer], name = "echoServer") 642 | 643 | val echoClient = system 644 | .ac修改为rFor("akka://RemoteSystem@127.0.0.1:2552/user/echoServer") 645 | echoClient ! "Hi Remote" 646 | 647 | system.shutdown 648 | 649 | ``` 650 | 651 | ## 实践 652 | 653 | ### 使用Java 654 | 655 | Scala和Java可以非常方便的互操作,前面已经有大量Scala直接使用Java的例子。 656 | 同样Java也可以使用Scala。这个例子演示使用@BeanProperty注解来生成Java Style的Bean。 657 | 尝试将在var name前加上@BeanProperty。这样就给bean添加了getter/setter 658 | Apache BeanUtils就可以正常工作。 659 | 660 | ``` 661 | import org.apache.commons.beanutils.BeanUtils 662 | import scala.beans.BeanProperty 663 | 664 | class SimpleBean( var name: String) { 665 | } 666 | 667 | val bean = new SimpleBean("foo") 668 | 669 | println(BeanUtils.describe(bean)) 670 | ``` 671 | 672 | ### 相等性 673 | 在Scala中==操作等效于equals,这一点和Java不同。更自然一些。 674 | 这个例子定义了一个equals函数,并验证。 675 | 写一个完全正确的equal函数并不容易,这个例子也有子类会不对称的Bug。 676 | 尝试将class修改为case class并删除equals函数。 677 | case类会自动生成正确的equals函数。 678 | 679 | ``` 680 | class Person(val name: String) { 681 | override def equals(other: Any) = other match { 682 | case that: Person => name.equals(that.name) 683 | case _ => false 684 | } 685 | } 686 | 687 | println(new Person("Black") == new Person("Black")) 688 | ``` 689 | 690 | 691 | ### 抽取器 692 | 抽取器可以帮助pattern match进行解构。 693 | 这个例子是构建一个Email抽取器,只要实现unapply函数就可以了。 694 | Scala的正则表达式会自带抽取器,可以抽取出一个List。List的元素是匹配()里的表达式。 695 | 抽取器很有用,短短的例子里就有两处使用抽取器: 696 | case user :: domain :: Nil解构List;case Email(user, domain) 解构Email。 697 | 698 | ``` 699 | object Email { 700 | def apply(user: String, domain: String) = user + "@" + domain 701 | 702 | def unapply(str: String) = new Regex("""(.*)@(.*)""").unapplySeq(str).get match { 703 | case user :: domain :: Nil => Some(user, domain) 704 | case _ => None 705 | } 706 | } 707 | 708 | "user@domain.com" match { 709 | case Email(user, domain) => println(user + "@" + domain) 710 | } 711 | ``` 712 | 713 | ### 记忆模式 714 | 记忆模式可以解决手动编写存取cache代码的麻烦。 715 | 这个例子中,memo可以将一个不含cache函数,包装成一个含有cache功能的。 716 | 还是斐波那契的例子,通过cache可以使性能提高。 717 | 尝试将fibonacci_(n - 1) + fibonacci_(n - 2)修改memo(fibonacci_)(n - 1) + memo(fibonacci_)(n - 2),可以提高更多。 718 | 719 | ``` 720 | import scala.collection.mutable.WeakHashMap 721 | 722 | val cache = new WeakHashMap[Int, Int] 723 | def memo(f: Int => Int) = (x: Int) => cache.getOrElseUpdate(x, f(x)) 724 | 725 | def fibonacci_(in: Int): Int = in match { 726 | case 0 => 0; 727 | case 1 => 1; 728 | case n: Int => fibonacci_(n - 1) + fibonacci_(n - 2) 729 | } 730 | 731 | val fibonacci: Int => Int = memo(fibonacci_) 732 | 733 | val t1 = System.currentTimeMillis() 734 | println(fibonacci(40)) 735 | println("it takes " + (System.currentTimeMillis() - t1) + "ms") 736 | 737 | val t2 = System.currentTimeMillis() 738 | println(fibonacci(40)) 739 | println("it takes " + (System.currentTimeMillis() - t2) + "ms") 740 | ``` 741 | ### 隐式转换 742 | implicit可以定义一个转换函数,可以在下面的使用到的时候自动转换。 743 | 这个例子可以将String自动转换为Date类型。隐式转换时实现DSL的重要工具。 744 | 745 | ``` 746 | implicit def strToDate(str: String) = new SimpleDateFormat("yyyy-MM-dd").parse(str) 747 | 748 | println("2013-01-01 unix time: " + "2013-01-01".getTime()/1000l) 749 | ``` 750 | 751 | ### DSL 752 | DSL是Scala最强大武器,Scala可以使一些描述性代码变得极为简单。 753 | 这个例子是使用DSL生成JSON。Scala很多看似是语言级的特性也是用DSL做到的。 754 | 自己编写DSL有点复杂,但使用方便灵活的。 755 | 756 | ``` 757 | import org.json4s._ 758 | import org.json4s.JsonDSL._ 759 | 760 | import org.json4s.jackson.JsonMethods._ 761 | 762 | case class Twitter(id: Long, text: String, publishedAt: Option[java.util.Date]) 763 | 764 | var twitters = Twitter(1, "hello scala", Some(new Date())) :: Twitter(2, "I like scala 修改为ur", None) :: Nil 765 | 766 | var json = ("twitters" 767 | -> twitters.map( 768 | t => ("id" -> t.id) 769 | ~ ("text" -> t.text) 770 | ~ ("published_at" -> t.publishedAt.修改为String()))) 771 | 772 | println(pretty(render(json))) 773 | ``` 774 | 775 | ### 测试 776 | Scala DSL可以使测试更方便。 777 | 这个例子是测试一个阶乘函数。使用should/in来建立测试用例。 778 | 测试是默认并发执行的。 779 | 780 | ``` 781 | import org.specs2.mutable._ 782 | 783 | class Fac修改为rialSpec extends Specification { 784 | args.report(color = false) 785 | 786 | def fac修改为rial(n: Int) = (1 修改为 n).reduce(_ * _) 787 | 788 | "The 'Hello world' string" should { 789 | "fac修改为rial 3 must be 6" in { 790 | fac修改为rial(3) mustEqual 6 791 | } 792 | "fac修改为rial 4 must be 6" in { 793 | fac修改为rial(4) must greaterThan(6) 794 | } 795 | } 796 | } 797 | specs2.run(new Fac修改为rialSpec) 798 | ``` 799 | 800 | 801 | ### Simple Build Tool 802 | SBT是Scala的最佳编译工具。 803 | 在他的帮助下,you can develop Scala even without installing anything except JRE. 804 | This example is 修改为 run this Scala 指南 in your computer. 805 | 806 | ``` 807 | #Linux/Mac(compile & run): 808 | git clone https://github.com/yankay/scala-修改为ur-zh.git 809 | cd scala-修改为ur-zh 810 | ./sbt/sbt stage 811 | ./target/start 812 | 813 | #Windows(can only compile): 814 | git clone https://github.com/yankay/scala-修改为ur-zh.git 815 | cd scala-修改为ur-zh 816 | sbt\sbt stage 817 | ``` 818 | -------------------------------------------------------------------------------- /scala-tour.md: -------------------------------------------------------------------------------- 1 | # Scala Tour 2 | 3 | [http://www.scala-tour.com/](http://www.scala-tour.com/) 4 | 5 | ## Basic 6 | 7 | ### Expressions and Values 8 | 9 | In Scala, almost everything is an expression.println("hello wolrd")is an expression, "hello"+" world" 10 | is also an expression. 11 | 12 | Constants can be created with val, and variables can be created with var. More constants are better. 13 | 14 | ``` 15 | var helloWorld = "hello" + " world" 16 | println(helloWorld) 17 | 18 | val again = " again" 19 | helloWorld = helloWorld + again 20 | println(helloWorld) 21 | ``` 22 | 23 | ### First class Functions 24 | 25 | You can create functions with def. And the function body is an expression. 26 | 27 | When the body is a block expression, it returns the value of the last line. So there's no need to use the return keyword 28 | 29 | And like values, functions can also be assigned using var or val So it can be passed as an argument to another function. 30 | 31 | ``` 32 | def square(a: Int) = a * a 33 | 34 | def squareWithBlock(a: Int) = { 35 | a * a 36 | } 37 | 38 | val squareVal = (a: Int) => a * a 39 | 40 | def addOne(f: Int => Int, arg: Int) = f(arg) + 1 41 | 42 | println("square(2):" + square(2)) 43 | println("squareWithBlock(2):" + squareWithBlock(2)) 44 | println("squareVal(2):" + squareVal(2)) 45 | println("addOne(squareVal,2):" + addOne(squareVal, 2)) 46 | ``` 47 | 48 | 49 | 50 | ### Loan Pattern 51 | 52 | For functions which can be passed as arguments, the 'Loan' pattern is easy to implement. 53 | 54 | This example reads the self pid from /proc/self/stat. 55 | 56 | Because the 'withScanner' function encapsulates the 'try-finally' block,there's no need to call 'close()' any more. 57 | 58 | PS: the expression's return type is 'Unit' when it doesn't return a value. 59 | 60 | ``` 61 | import scala.reflect.io.File 62 | import java.util.Scanner 63 | 64 | def withScanner(f: File, op: Scanner => Unit) = { 65 | val scanner = new Scanner(f.bufferedReader) 66 | try { 67 | op(scanner) 68 | } finally { 69 | scanner.close() 70 | } 71 | } 72 | 73 | withScanner(File("/proc/self/stat"), 74 | scanner => println("pid is " + scanner.next())) 75 | ``` 76 | 77 | 78 | ### Call-by-Name 79 | 80 | This example shows the call by name, When the last line tries to calculate '1 / 0', the program will throw an exception. 81 | 82 | Try to change 'def log(msg: String)' to 'def log(msg: => String)'.The program will not throw an exception because it has been changed to call-by-name 83 | 84 | 85 | Call-by-name means that the argument will be calculated when it be actually called. Because 'logEnable = false', the '1 / 0' would be skipped. 86 | 87 | 88 | Call-by-name can reduce the useless calculation and exception. 89 | 90 | 91 | ``` 92 | val logEnable = false 93 | 94 | def log(msg: String) = 95 | if (logEnable) println(msg) 96 | 97 | val MSG = "programing is running" 98 | 99 | log(MSG + 1 / 0) 100 | ``` 101 | 102 | ### Define Class 103 | 104 | The 'class' keyword defines a class, and the 'new' keyword creates an instance. 105 | The fields can be also defined in class, like the 'firstName' and 'lastName'.These are automatically generated from the constructor's arguments. 106 | Methods can be defined with def, and fields can be defined with val or var 107 | The function name can be any characters like +,-,*,/. 108 | The 'obama.age_=(51)' can be simplified as 'obama.age = 51'. 109 | And the 'obama.age()' can be simplified as 'obama.age'. 110 | 111 | ``` 112 | class Person(val firstName: String, val lastName: String) { 113 | 114 | private var _age = 0 115 | def age = _age 116 | def age_=(newAge: Int) = _age = newAge 117 | 118 | def fullName() = firstName + " " + lastName 119 | 120 | override def toString() = fullName() 121 | } 122 | 123 | val obama: Person = new Person("Barack", "Obama") 124 | 125 | println("Person: " + obama) 126 | println("firstName: " + obama.firstName) 127 | println("lastName: " + obama.lastName) 128 | obama.age_=(51) 129 | println("age: " + obama.age()) 130 | ``` 131 | 132 | 133 | ### Duck Typing 134 | 135 | When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck. 136 | This example uses '{ def close(): Unit }' as the type of argument. So any class contains methods 'close()' can be passed. 137 | And there's no need to use 'inherit'。 138 | 139 | ``` 140 | def withClose(closeAble: { def close(): Unit }, op: { def close(): Unit } => Unit) { 141 | try { 142 | op(closeAble) 143 | } finally { 144 | closeAble.close() 145 | } 146 | } 147 | 148 | class Connection { 149 | def close() = println("close Connection") 150 | } 151 | 152 | val conn: Connection = new Connection() 153 | withClose(conn, conn => 154 | println("do something with Connection")) 155 | ``` 156 | 157 | 158 | ### Currying 159 | 160 | This example is similar to the previous one.The difference between them is this one leverage currying technology。 161 | def add(x:Int, y:Int) = x + y is a normal function 162 | def add(x:Int) = (y:Int) => x + y is a curried function.The return value is a function expression. 163 | def add(x:Int)(y:Int) = x + y is syntactic sugar 164 | Currying can let our code look like it is part of the language. 165 | Change the withclose(...)(...) to withclose(...){...} 166 | Is it similar with Java's synchronized block? 167 | 168 | ``` 169 | def withClose(closeAble: { def close(): Unit })(op: { def close(): Unit } => Unit) { 170 | try { 171 | op(closeAble) 172 | } finally { 173 | closeAble.close() 174 | } 175 | } 176 | 177 | class Connection { 178 | def close() = println("close Connection") 179 | } 180 | 181 | val conn: Connection = new Connection() 182 | withClose(conn)(conn => 183 | println("do something with Connection")) 184 | ``` 185 | 186 | 187 | ### Generic 188 | 189 | The sample before can be more simplified with generics. 190 | Try to change val msg = "123456" to val msg = 123456. 191 | Although the type of msg changed from String to Int, the program still compiles. 192 | 193 | ``` 194 | def withClose[A <: { def close(): Unit }, B](closeAble: A)(op: A => B) { 195 | try { 196 | op(closeAble) 197 | } finally { 198 | closeAble.close() 199 | } 200 | } 201 | 202 | class Connection { 203 | val msg = "123456" 204 | def close() = println("close Connection") 205 | } 206 | 207 | val conn: Connection = new Connection() 208 | val msg = withClose(conn) { conn => 209 | { 210 | println("do something with Connection") 211 | conn.msg 212 | } 213 | } 214 | 215 | println(msg) 216 | ``` 217 | 218 | ### Traits 219 | 220 | Traits look like Java's interfaces, but with function blocks. One class can extend several traits using the with keyword. 221 | This example extends java.util.ArrayList with a foreach loop. 222 | Try to append with JsonAble to extend the ability of toJson. 223 | 224 | ``` 225 | trait ForEachAble[A] { 226 | def iterator: java.util.Iterator[A] 227 | def foreach(f: A => Unit) = { 228 | val iter = iterator 229 | while (iter.hasNext) 230 | f(iter.next) 231 | } 232 | } 233 | 234 | trait JsonAble { 235 | override def toString() = 236 | scala.util.parsing.json.JSONFormat.defaultFormatter(this) 237 | } 238 | 239 | val list = new java.util.ArrayList[Int]() with ForEachAble[Int] 240 | list.add(1); list.add(2) 241 | 242 | list.foreach(x => println(x)) 243 | println("Json: " + list.toString()) 244 | ``` 245 | 246 | 247 | ## Functional Programing 248 | 249 | ### Pattern Matching 250 | 251 | Pattern Matching is more flexible than switch-case and simpler than if-else. 252 | This example shows a Fibonacci function implemented with pattern matching. 253 | The case keyword matches on a value. The case _ means it can match anything. 254 | But the example has a bug. If the input is a negative number, it will loop endlessly. 255 | Try to add if after case. Change case n: Int to case n: Int if (n > 1). 256 | Try to add case n: String => fibonacci(n.toInt) before case _ to match String type. 257 | Try to add println(fibonacci(-3));println(fibonacci("3")) to test the program. 258 | 259 | 260 | ``` 261 | def fibonacci(in: Any): Int = in match { 262 | case 0 => 0 263 | case 1 => 1 264 | case n: Int => fibonacci(n - 1) + fibonacci(n - 2) 265 | case _ => 0 266 | } 267 | 268 | println(fibonacci(3)) 269 | ``` 270 | 271 | ### Case Class 272 | 273 | Case classes are used to conveniently store and match on the contents of a class. 274 | You can construct them without using new. 275 | It also has hashcode, equality and nice toString methods. 276 | Try to append println(Sum(1,2)) last. 277 | Because of the require(n >= 0), it will throw an exception when the input is negative. 278 | 279 | 280 | ``` 281 | abstract class Expr 282 | 283 | case class FibonacciExpr(n: Int) extends Expr { 284 | require(n >= 0) 285 | } 286 | 287 | case class SumExpr(a: Expr, b: Expr) extends Expr 288 | 289 | def value(in: Expr): Int = in match { 290 | case FibonacciExpr(0) => 0 291 | case FibonacciExpr(1) => 1 292 | case FibonacciExpr(n) => value(SumExpr(FibonacciExpr(n - 1), FibonacciExpr(n - 2))) 293 | case SumExpr(a, b) => value(a) + value(b) 294 | case _ => 0 295 | } 296 | println(value(FibonacciExpr(3))) 297 | ``` 298 | 299 | ### The power of functional programing 300 | 301 | This example determines whether there is an odd number in the list. 302 | Every line of the code excluding the last line is created using imperative programming.The last line is created using functional programming. 303 | Treating function expression as function arguments can simplify code effectively.The _ % 2 == 1 is the syntactic sugar of (x: Int) => x % 2 == 1. 304 | Ruby's power is from magic, but Scala's power is from science. 305 | 306 | ``` 307 | val list = List(1, 2, 3, 4) 308 | 309 | def containsOdd(list: List[Int]): Boolean = { 310 | for (i <- list) { 311 | if (i % 2 == 1) 312 | return true; 313 | } 314 | return false; 315 | } 316 | 317 | println("list containsOdd by for loop:" + containsOdd(list)) 318 | 319 | println("list containsOdd by funtional:" + list.exists(_ % 2 == 1)) 320 | ``` 321 | 322 | ### The true power of functional programming 323 | 324 | Besides simplifying code, functional programming is more concerned with Input & Output without side-effects. 325 | Like the Unix pipeline, simple commands can be combined together. 326 | The filter method in List can accept a filter function to return a new List. 327 | If you like the way Unix pipelines commands, you may also like functional programming 328 | This example uses Scala code to simulate the Unix command line "cat file | grep 'warn' | grep '2013' | wc." 329 | 330 | ``` 331 | val file = List("warn 2013 msg", "warn 2012 msg", "error 2013 msg", "warn 2013 msg") 332 | 333 | println("cat file | grep 'warn' | grep '2013' | wc : " 334 | + file.filter(_.contains("warn")).filter(_.contains("2013")).size) 335 | ``` 336 | ### Word Count 337 | Word Count is a classic use case for Map Reduce. Map Reduce with functional programming is also a wonderful way to implement word count. 338 | The example shows two important functions 'map' and 'reduceLeft' in List. 339 | The map function accepts a translation expression and returns the translated list. 340 | The reduceLeft function accepts a combining expression and returns the combined result.The first argument is the reduced value, and the second argument is the value next. 341 | Try to change reduceLeft(_ + _) into foldLeft(0)(_ + _).foldLeft is more popular than reduceLeft for it can provide a initial value. 342 | Map and ReduceLeft can replace a for-loop expression, making code cleaner. 343 | 344 | ``` 345 | val file = List("warn 2013 msg", "warn 2012 msg", "error 2013 msg", "warn 2013 msg") 346 | 347 | def wordcount(str: String): Int = str.split(" ").count("msg" == _) 348 | 349 | val num = file.map(wordcount).reduceLeft(_ + _) 350 | 351 | println("wordcount:" + num) 352 | ``` 353 | ### Tail Recursion 354 | 355 | This example shows how to implement foldLeft with Tail RecursionTail Recursion is one type of Recursion, in which a function calls itself as its last expression. 356 | Lists can be pattern matched by '::', the first element returned is head, and the others are the tail. 357 | Tail Recursion can be optimized at compile time. So there's no need to worry about stack overflow. 358 | 359 | ``` 360 | val file = List("warn 2013 msg", "warn 2012 msg", "error 2013 msg", "warn 2013 msg") 361 | 362 | def wordcount(str: String): Int = str.split(" ").count("msg" == _) 363 | 364 | def foldLeft(list: List[Int])(init: Int)(f: (Int, Int) => Int): Int = { 365 | list match { 366 | case List() => init 367 | case head :: tail => foldLeft(tail)(f(init, head))(f) 368 | } 369 | } 370 | 371 | val num = foldLeft(file.map(wordcount))(0)(_ + _) 372 | 373 | println("wordcount:" + num) 374 | ``` 375 | 376 | ### Powerful For Expression 377 | 378 | Loop expression is feature of imperative programming.So Scala improved it to suit functional programming. 379 | For expressions in Scala can also return a List. With 'yield' in the loop, the value after yield will be appended to the List. 380 | This example replaces the map function with for loop. 381 | 382 | ``` 383 | val file = List("warn 2013 msg", "warn 2012 msg", "error 2013 msg", "warn 2013 msg") 384 | 385 | def wordcount(str: String): Int = str.split(" ").count("msg" == _) 386 | 387 | val counts = 388 | for (line <- file) 389 | yield wordcount(line) 390 | 391 | val num = counts.reduceLeft(_ + _) 392 | 393 | println("wordcount:" + num) 394 | ``` 395 | ### Option 396 | 397 | NullException is the most common exception in Java. The only way to avoid it is to check null everywhere.Scala provides an Option feature to solve it. 398 | This example creates a getProperty function, which returns Option instead of null. 399 | We don't need to check null, getting a value from Option is enough. 400 | Using pattern matching is a common way to get the value of Option. Use getOrElse() to set a default value when Option is None. 401 | Another important think is that Option has lots of functions in List, so it can work like a list in most of time. 402 | Try to add 'osName.foreach(print _)' at last. 403 | 404 | 405 | ``` 406 | 407 | def getProperty(name: String): Option[String] = { 408 | val value = System.getProperty(name) 409 | if (value != null) Some(value) else None 410 | } 411 | 412 | val osName = getProperty("os.name") 413 | 414 | osName match { 415 | case Some(value) => println(value) 416 | case _ => println("none") 417 | } 418 | 419 | println(osName.getOrElse("none")) 420 | 421 | 422 | 423 | ``` 424 | 425 | 426 | ### Lazy 427 | 428 | Lazy is lazy initial value. A field with the lazy keyword is only initialized when it is first accessed. 429 | This example is to get the Scala Version Code from Github. It takes a little time because of network latency. 430 | It waste of time that if we get it with latency but we do not use it later. 431 | So we can get it with lazy. 432 | 433 | ``` 434 | class ScalaCurrentVersion(val url: String) { 435 | lazy val source= { 436 | println("fetching from url...") 437 | scala.io.Source.fromURL(url).getLines().toList 438 | } 439 | lazy val majorVersion = source.find(_.contains("version.major")) 440 | lazy val minorVersion = source.find(_.contains("version.minor")) 441 | } 442 | val version = new ScalaCurrentVersion("https://raw.github.com/scala/scala/master/build.number") 443 | println("get scala version from " + version.url) 444 | version.majorVersion.foreach(println _) 445 | version.minorVersion.foreach(println _) 446 | ``` 447 | 448 | 449 | ## Concurrent 450 | 451 | ### Using Actor 452 | 453 | Actors are one of Scala's concurrent models.Users of Scala earlier than version 2.10 must install [http://akka.io/](Akka). 454 | An Actor is a like a thread instance with a mailbox. 455 | It can be created with system.actorOf: use receive to get a message and ! to send a message. 456 | This example is an EchoServer which can receive messages then print them. 457 | 458 | ``` 459 | import akka.actor.{ Actor, ActorSystem, Props } 460 | 461 | val system = ActorSystem() 462 | 463 | class EchoServer extends Actor { 464 | def receive = { 465 | case msg: String => println("echo " + msg) 466 | } 467 | } 468 | 469 | val echoServer = system.actorOf(Props[EchoServer]) 470 | echoServer ! "hi" 471 | 472 | system.shutdown 473 | ``` 474 | 475 | ### Simplify Actor 476 | 477 | There is a simpler way to define an Actor. 478 | Import the actor function from akka.actor.ActorDSL. 479 | This function accepts an Actor instance, and returns a started Actor. 480 | 481 | 482 | ``` 483 | import akka.actor.ActorDSL._ 484 | import akka.actor.ActorSystem 485 | 486 | implicit val system = ActorSystem() 487 | 488 | val echoServer = actor(new Act { 489 | become { 490 | case msg => println("echo " + msg) 491 | } 492 | }) 493 | echoServer ! "hi" 494 | system.shutdown 495 | ``` 496 | ### Actor Implementation 497 | An Actor is more lightweight than a thread. Millions of actors can be generated in Scala. The secret is that an Actor can reuse a thread. 498 | The mapping relationship between an Actor and a Thread is decided by a Dispatcher. 499 | This example creates 4 Actors, and prints its thread name when invoked. 500 | You will find there is no fixed mapping relationship between Actors and Threads. 501 | An Actor can use many threads. And a thread can be used by many Actors. 502 | 503 | ``` 504 | import akka.actor.{ Actor, Props, ActorSystem } 505 | import akka.testkit.CallingThreadDispatcher 506 | 507 | implicit val system = ActorSystem() 508 | 509 | class EchoServer(name: String) extends Actor { 510 | def receive = { 511 | case msg => println("server" + name + " echo " + msg + 512 | " by " + Thread.currentThread().getName()) 513 | } 514 | } 515 | 516 | val echoServers = (1 to 10).map(x => 517 | system.actorOf(Props(new EchoServer(x.toString)) 518 | .withDispatcher(CallingThreadDispatcher.Id))) 519 | (1 to 10).foreach(msg => 520 | echoServers(scala.util.Random.nextInt(10)) ! msg.toString) 521 | 522 | system.shutdown 523 | ``` 524 | 525 | 526 | 527 | ### Synchronized Return 528 | 529 | Actors are very suitable for long-running operations, like getting resources over a network. 530 | This example creates a Future with the ask function. 531 | In the actor we use 'sender !' to return the value. 532 | Like Option, Future has lots of functions. The result can be printed with a foreach. 533 | 534 | 535 | ``` 536 | import akka.actor.ActorDSL._ 537 | import akka.pattern.ask 538 | 539 | implicit val ec = scala.concurrent.ExecutionContext.Implicits.global 540 | implicit val system = akka.actor.ActorSystem() 541 | 542 | val versionUrl = "https://raw.github.com/scala/scala/master/starr.number" 543 | 544 | val fromURL = actor(new Act { 545 | become { 546 | case url: String => sender ! scala.io.Source.fromURL(url) 547 | .getLines().mkString("\n") 548 | } 549 | }) 550 | 551 | val version = fromURL.ask(versionUrl)(akka.util.Timeout(5 * 1000)) 552 | version.foreach(println _) 553 | 554 | system.shutdown 555 | ``` 556 | 557 | ### Asynchronous Return 558 | 559 | Asynchronous operations can provide better performance. A Future in Scala is very powerful, it can execute asynchronously. 560 | The Future will call the 'onComplete' function when it is finished. 561 | It can also set a TIMEOUT when specified. 562 | 563 | ``` 564 | import akka.actor.ActorDSL._ 565 | import akka.pattern.ask 566 | 567 | implicit val ec = scala.concurrent.ExecutionContext.Implicits.global 568 | implicit val system = akka.actor.ActorSystem() 569 | 570 | val versionUrl = "https://raw.github.com/scala/scala/master/starr.number" 571 | 572 | val fromURL = actor(new Act { 573 | become { 574 | case url: String => sender ! scala.io.Source.fromURL(url) 575 | .getLines().mkString("\n") 576 | } 577 | }) 578 | 579 | val version = fromURL.ask(versionUrl)(akka.util.Timeout(5 * 1000)) 580 | version onComplete { 581 | case msg => println(msg); system.shutdown 582 | } 583 | ``` 584 | ### Concurrent Collection 585 | 586 | This example prints the time needed to access several URLs. 587 | If we access them concurrently, the performance can be better. 588 | Try to change the 'urls.map' to 'urls.par.map'.Now, the functions in map will run concurrently. 589 | It's exciting to combine functional and concurrent programming! 590 | 591 | ``` 592 | import scala.io.Codec 593 | import java.nio.charset.CodingErrorAction 594 | 595 | implicit val codec = Codec("UTF-8") 596 | codec.onMalformedInput(CodingErrorAction.REPLACE) 597 | codec.onUnmappableCharacter(CodingErrorAction.REPLACE) 598 | 599 | val urls = "http://scala-lang.org" :: "https://github.com/yankay/scala-tour" :: Nil 600 | 601 | def fromURL(url: String) = scala.io.Source.fromURL(url).getLines().mkString("\n") 602 | 603 | val s = System.currentTimeMillis() 604 | time(urls.map(fromURL(_))) 605 | println("time: " + (System.currentTimeMillis - s) + "ms") 606 | ``` 607 | 608 | ### Concurrentwordcount 609 | Concurrent Collection support most functions in normal ones. 610 | Here is the word count example from earlier, improved using a parallel collection. 611 | It can use the power of multiple cores without increasing the complexity. 612 | 613 | ``` 614 | val file = List("warn 2013 msg", "warn 2012 msg", "error 2013 msg", "warn 2013 msg") 615 | 616 | def wordcount(str: String): Int = str.split(" ").count("msg" == _) 617 | 618 | val num = file.par.map(wordcount).par.reduceLeft(_ + _) 619 | 620 | println("wordcount:" + num) 621 | ``` 622 | 623 | ### Remote Actor 624 | Actor is not only a concurrency model, it can also be used for distributed computing. 625 | This example builds an EchoServer using an Actor. 626 | Then it creates a client to access the Akka URL. 627 | The usage is the same as with a normal Actor. 628 | 629 | ``` 630 | import akka.actor.{ Actor, ActorSystem, Props } 631 | import com.typesafe.config.ConfigFactory 632 | 633 | implicit val system = akka.actor.ActorSystem("RemoteSystem", 634 | ConfigFactory.load.getConfig("remote")) 635 | class EchoServer extends Actor { 636 | def receive = { 637 | case msg: String => println("echo " + msg) 638 | } 639 | } 640 | 641 | val server = system.actorOf(Props[EchoServer], name = "echoServer") 642 | 643 | val echoClient = system 644 | .actorFor("akka://RemoteSystem@127.0.0.1:2552/user/echoServer") 645 | echoClient ! "Hi Remote" 646 | 647 | system.shutdown 648 | 649 | ``` 650 | 651 | ## Practice 652 | 653 | ### Using Java 654 | 655 | Scala can execute Java code very easily. There have already been many examples of this. 656 | Java can also use Scala. This example shows how to use the @BeanProperty Annotation to create a Java Style Bean. 657 | Try to add @BeanProperty before var name. Now the bean contains getter/setter functions. 658 | And the Apache BeanUtils can work correctly. 659 | 660 | ``` 661 | import org.apache.commons.beanutils.BeanUtils 662 | import scala.beans.BeanProperty 663 | 664 | class SimpleBean( var name: String) { 665 | } 666 | 667 | val bean = new SimpleBean("foo") 668 | 669 | println(BeanUtils.describe(bean)) 670 | ``` 671 | 672 | ### Equality 673 | In Scala == is the same as equals function. It's not the same as Java, but it's more reasonable. 674 | This example defines a equals function, and prints the result. 675 | Correctly writing an equals function is difficult. This example has an issue with subclasses. 676 | Try to change 'class' to 'case class', and delete the equals function. 677 | Case Class correctly generates the equals function for us. 678 | 679 | ``` 680 | class Person(val name: String) { 681 | override def equals(other: Any) = other match { 682 | case that: Person => name.equals(that.name) 683 | case _ => false 684 | } 685 | } 686 | 687 | println(new Person("Black") == new Person("Black")) 688 | ``` 689 | 690 | 691 | ### Extractor 692 | Extractor objects can deconstruct pattern matches. 693 | This example builds an Email Extractor, only the 'unapply function' is needed. 694 | Scala's Regex contains an extractor, which extracts a List. The List elements sequentially match expressions captured in (). 695 | Extractor is very useful. There are 2 cases in this example. 696 | case user :: domain :: Nil extracts a List. case Email(user, domain) extracts an Email. 697 | 698 | ``` 699 | object Email { 700 | def apply(user: String, domain: String) = user + "@" + domain 701 | 702 | def unapply(str: String) = new Regex("""(.*)@(.*)""").unapplySeq(str).get match { 703 | case user :: domain :: Nil => Some(user, domain) 704 | case _ => None 705 | } 706 | } 707 | 708 | "user@domain.com" match { 709 | case Email(user, domain) => println(user + "@" + domain) 710 | } 711 | ``` 712 | 713 | ### Memory Pattern 714 | Memory Pattern can be used to simplify caching. 715 | In this example, the 'memo function' wraps a function without caching to add the simple cache capability. 716 | In this Fibonacci example, a cache improves performance after the first call. 717 | Try to change fibonacci_(n - 1) + fibonacci_(n - 2) to memo(fibonacci_)(n - 1) + memo(fibonacci_)(n - 2), it can improve more. 718 | 719 | ``` 720 | import scala.collection.mutable.WeakHashMap 721 | 722 | val cache = new WeakHashMap[Int, Int] 723 | def memo(f: Int => Int) = (x: Int) => cache.getOrElseUpdate(x, f(x)) 724 | 725 | def fibonacci_(in: Int): Int = in match { 726 | case 0 => 0; 727 | case 1 => 1; 728 | case n: Int => fibonacci_(n - 1) + fibonacci_(n - 2) 729 | } 730 | 731 | val fibonacci: Int => Int = memo(fibonacci_) 732 | 733 | val t1 = System.currentTimeMillis() 734 | println(fibonacci(40)) 735 | println("it takes " + (System.currentTimeMillis() - t1) + "ms") 736 | 737 | val t2 = System.currentTimeMillis() 738 | println(fibonacci(40)) 739 | println("it takes " + (System.currentTimeMillis() - t2) + "ms") 740 | ``` 741 | Implicit Conversion 742 | Implicit can be used to define a Conversion function. Types are automatically implicitly converted when needed. 743 | This example converts String to Date automatically. Implicit is the most important feature when implementing a DSL. 744 | 745 | ``` 746 | implicit def strToDate(str: String) = new SimpleDateFormat("yyyy-MM-dd").parse(str) 747 | 748 | println("2013-01-01 unix time: " + "2013-01-01".getTime()/1000l) 749 | ``` 750 | 751 | ### DSL 752 | DSL is the most powerful tool in Scala. With it Scala code can become more descriptive. 753 | This example generates Json with a DSL. Some of the features look like native features but are created by a DSL. 754 | It's complex to write your own DSL. But it's very easy to use. 755 | 756 | ``` 757 | import org.json4s._ 758 | import org.json4s.JsonDSL._ 759 | 760 | import org.json4s.jackson.JsonMethods._ 761 | 762 | case class Twitter(id: Long, text: String, publishedAt: Option[java.util.Date]) 763 | 764 | var twitters = Twitter(1, "hello scala", Some(new Date())) :: Twitter(2, "I like scala tour", None) :: Nil 765 | 766 | var json = ("twitters" 767 | -> twitters.map( 768 | t => ("id" -> t.id) 769 | ~ ("text" -> t.text) 770 | ~ ("published_at" -> t.publishedAt.toString()))) 771 | 772 | println(pretty(render(json))) 773 | ``` 774 | 775 | ### Testing 776 | Scala DSL can make testing even easier. 777 | This example tests a Factorial function. It creates a test case with should/in. 778 | Test cases run concurrently by default. 779 | 780 | ``` 781 | import org.specs2.mutable._ 782 | 783 | class FactorialSpec extends Specification { 784 | args.report(color = false) 785 | 786 | def factorial(n: Int) = (1 to n).reduce(_ * _) 787 | 788 | "The 'Hello world' string" should { 789 | "factorial 3 must be 6" in { 790 | factorial(3) mustEqual 6 791 | } 792 | "factorial 4 must be 6" in { 793 | factorial(4) must greaterThan(6) 794 | } 795 | } 796 | } 797 | specs2.run(new FactorialSpec) 798 | ``` 799 | 800 | 801 | ### Simple Build Tool 802 | SBT is a very popular build tool for Scala. 803 | With its help, you can develop Scala even without installing anything except JRE. 804 | This example is to run this Scala Tour in your computer. 805 | 806 | ``` 807 | #Linux/Mac(compile & run): 808 | git clone https://github.com/yankay/scala-tour-zh.git 809 | cd scala-tour-zh 810 | ./sbt/sbt stage 811 | ./target/start 812 | 813 | #Windows(can only compile): 814 | git clone https://github.com/yankay/scala-tour-zh.git 815 | cd scala-tour-zh 816 | sbt\sbt stage 817 | ``` 818 | -------------------------------------------------------------------------------- /src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | remote { 2 | akka { 3 | 4 | actor { 5 | provider = "akka.remote.RemoteActorRefProvider" 6 | } 7 | remote { 8 | transport = "akka.remote.netty.NettyRemoteTransport" 9 | netty { 10 | hostname = "127.0.0.1" 11 | port = 2552 12 | } 13 | } 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/scala/com/yankay/scalaTour/ScalaScriptRunner.scala: -------------------------------------------------------------------------------- 1 | package com.yankay.scalaTour 2 | 3 | import java.io.OutputStream 4 | import java.io.PrintStream 5 | import java.io.PrintWriter 6 | import scala.collection.mutable.StringBuilder 7 | import scala.reflect.io.Directory 8 | import scala.reflect.io.File 9 | import scala.reflect.io.Path 10 | import scala.reflect.io.Path.string2path 11 | import scala.sys.process.Process 12 | import scala.sys.process.ProcessBuilder 13 | import scala.sys.process.ProcessCreation 14 | import scala.sys.process.ProcessIO 15 | import scala.sys.process.ProcessLogger 16 | import scala.tools.nsc.GenericRunnerCommand 17 | import scala.tools.nsc.GenericRunnerSettings 18 | import scala.tools.nsc.Global 19 | import scala.tools.nsc.Settings 20 | import scala.tools.nsc.reporters.ConsoleReporter 21 | import scala.tools.nsc.reporters.Reporter 22 | import scala.tools.nsc.util.HasClassPath 23 | import scala.util.Properties 24 | import scala.tools.nsc.io.Jar 25 | 26 | object ScalaScriptCompiler { 27 | // /** Default name to use for the wrapped script */ 28 | val defaultScriptMain = "Main" 29 | // 30 | // /** Pick a main object name from the specified settings */ 31 | // def scriptMain(settings: Settings) = settings.script.value match { 32 | // case "" => defaultScriptMain 33 | // case x => x 34 | // } 35 | 36 | val settings: GenericRunnerSettings = { 37 | val command = new GenericRunnerCommand(List(), Console.err println _) 38 | val CP = Properties.propOrEmpty("java.class.path") 39 | val PS = Properties.propOrEmpty("path.separator") 40 | CP.split(PS).foreach((x: String) => { 41 | command.settings.classpath.append(x) 42 | command.settings.bootclasspath.append(x) 43 | }) 44 | command.settings.nc.value = true 45 | command.settings.feature.value = true 46 | command.settings.language.appendToValue("reflectiveCalls") 47 | command.settings.language.appendToValue("implicitConversions") 48 | command.settings.nospecialization.value = true 49 | command.settings.nouescape.value = true 50 | command.settings; 51 | } 52 | 53 | def compile(script: String, err: OutputStream): Option[File] = { 54 | val scriptFile = File.makeTemp("scala-script", ".scala") 55 | // save the command to the file 56 | 57 | val illegalCodePattern = """.*(\.sys\.).*""".r 58 | 59 | val strippedScript = { 60 | if (illegalCodePattern.findAllIn(script).toList.isEmpty) script 61 | else """println("No need to import anything")""" 62 | } 63 | 64 | val scriptAll = """ 65 | import scala.io.Codec 66 | import java.nio.charset.CodingErrorAction 67 | object Main { 68 | def main(args: Array[String]) { 69 | implicit val codec = Codec("UTF-8") 70 | codec.onMalformedInput(CodingErrorAction.REPLACE) 71 | codec.onUnmappableCharacter(CodingErrorAction.REPLACE) 72 | """ + strippedScript + """ 73 | } 74 | } 75 | """ 76 | scriptFile.writeAll(scriptAll) 77 | try compile(scriptFile, err) 78 | finally scriptFile.delete() // in case there was a compilation error 79 | } 80 | 81 | def compile( 82 | scriptFile: File, err: OutputStream): Option[File] = { 83 | // def mainClass = scriptMain(settings) 84 | val compiledPath = Directory makeTemp "scalascript" 85 | 86 | // delete the directory after the user code has finished 87 | // sys.addShutdownHook(compiledPath.deleteRecursively()) 88 | 89 | settings.outdir.value = compiledPath.path 90 | 91 | /** 92 | * Setting settings.script.value informs the compiler this is not a 93 | * self contained compilation unit. 94 | */ 95 | // settings.script.value = mainClass 96 | val reporter = new ConsoleReporter(settings, Console.in, new PrintWriter(err, true)) 97 | val compiler = Global(settings, reporter) 98 | 99 | new compiler.Run compile List(scriptFile.path) 100 | if (reporter.hasErrors) None else 101 | try createjar(compiledPath) 102 | finally compiledPath.deleteRecursively; 103 | } 104 | 105 | def createjar(compiledPath: Directory): Option[File] = { 106 | val jarpath = File.makeTemp("scalascript", ".jar") 107 | jarpath.deleteIfExists() 108 | try { 109 | Jar.create(jarpath, compiledPath, defaultScriptMain) 110 | Some(jarpath) 111 | } catch { case _: Exception => jarpath.delete(); None } 112 | 113 | } 114 | } 115 | 116 | object ScalaScriptProcess { 117 | def javaf(): String = { 118 | val head = Properties.propOrEmpty("java.home") + File.separator + "bin" + File.separator + "java" 119 | if (File.separator == "/") 120 | head + "" 121 | else 122 | head + ".exe" 123 | } 124 | 125 | def create(compiledLocation: File, out: OutputStream, err: OutputStream): Option[ScalaScriptProcess] = { 126 | val policy = new java.io.File("policy") 127 | val sep = Properties.propOrEmpty("path.separator") 128 | val CP = Properties.propOrEmpty("java.class.path") + sep + compiledLocation + sep + "./bin" + sep + "." 129 | var args = List("-Djava.security.manager", "-Djava.security.policy=" + policy.getAbsolutePath ,"-cp", CP, "Main") 130 | if (Path(javaf()).exists) { 131 | val outp = new PrintStream(out, true); 132 | val errp = new PrintStream(err, true); 133 | val pl = ProcessLogger( 134 | outp println _, 135 | errp println _) 136 | Some(new ScalaScriptProcess(Process(javaf(), args), pl)) 137 | } else None 138 | 139 | } 140 | } 141 | 142 | class ScalaScriptProcess(val builder: ProcessBuilder, val logger: ProcessLogger) { 143 | def run(): Process = { 144 | // println(builder.toString) 145 | builder.run(logger); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/scala/com/yankay/scalaTour/Web.scala: -------------------------------------------------------------------------------- 1 | package com.yankay.scalaTour 2 | 3 | import java.io.ByteArrayOutputStream 4 | import scala.actors.Actor 5 | import scala.actors.TIMEOUT 6 | import scala.reflect.io.File 7 | import scala.util.Properties 8 | import org.eclipse.jetty.server.Handler 9 | import org.eclipse.jetty.server.Server 10 | import org.eclipse.jetty.servlet.ServletContextHandler 11 | import org.eclipse.jetty.servlet.ServletHolder 12 | import org.json4s.jackson.JsonMethods 13 | import javax.servlet.http.HttpServlet 14 | import javax.servlet.http.HttpServletRequest 15 | import javax.servlet.http.HttpServletResponse 16 | import org.eclipse.jetty.server.handler.ResourceHandler 17 | import org.eclipse.jetty.servlet.DefaultServlet 18 | import org.eclipse.jetty.servlet.FilterHolder 19 | import org.eclipse.jetty.server.handler.GzipHandler 20 | import org.eclipse.jetty.servlets.GzipFilter 21 | import java.util.EnumSet 22 | import javax.servlet.DispatcherType 23 | import scala.collection.mutable.WeakHashMap 24 | import scala.util.matching.Regex 25 | 26 | object Web { 27 | val cache = new WeakHashMap[String, RunResponse] 28 | 29 | def handler(): Handler = { 30 | val context = new ServletContextHandler 31 | context.setContextPath("/"); 32 | context.setResourceBase("webapp") 33 | context.addServlet(new ServletHolder(new RunServlet()), "/run"); 34 | context.addServlet(new ServletHolder(new DefaultServlet()), "/"); 35 | val fh = new FilterHolder(new GzipFilter()) 36 | fh.setInitParameter("mimeTypes", "text/html,text/css,application/x-javascript,imgage/png") 37 | context.addFilter(fh, "/*", EnumSet.of(DispatcherType.FORWARD, 38 | DispatcherType.INCLUDE, 39 | DispatcherType.REQUEST, 40 | DispatcherType.ASYNC, 41 | DispatcherType.ERROR)) 42 | context 43 | } 44 | 45 | def main(args: Array[String]) { 46 | 47 | val server = new Server(Properties.envOrElse("PORT", "8080").toInt); 48 | server.setHandler(handler()) 49 | println("Start") 50 | server.start() 51 | } 52 | } 53 | 54 | class RunServlet extends HttpServlet { 55 | 56 | def compileAndRun(code: String): RunResponse = { 57 | // println("code:" + code) 58 | val buffer = new ByteArrayOutputStream(); 59 | val file = ScalaScriptCompiler.compile(code, buffer); 60 | val error = new String(buffer.toByteArray()).lines.toList 61 | // println(new String(buffer.toByteArray())) 62 | // println(error); 63 | def replaceErrorCodeNum(src: String): String = { 64 | val reg = new Regex("""main\.scala:([0-9]*): error:.*""") 65 | reg.unapplySeq(src).getOrElse(src) match { 66 | case l :: Nil => "main.scala:" + (l.toString.toInt - 8).toString + src.substring("main.scala:".length() + l.toString.length()) 67 | case _ => src 68 | } 69 | } 70 | val errorMsg = error.map(x => x.replaceAll("/tmp/scala-script.*scala", "main.scala")).map(replaceErrorCodeNum) 71 | 72 | file match { 73 | case Some(f) => { 74 | try { 75 | val (outEvents, errEvents, exitValue) = run(f); 76 | new RunResponse(errorMsg.toList, outEvents, errEvents, exitValue) 77 | } finally { 78 | f.deleteIfExists(); 79 | } 80 | } 81 | case _ => new RunResponse(errorMsg, List(), List(), -1) 82 | } 83 | } 84 | 85 | def run(file: File): (List[String], List[String], Int) = { 86 | val out = new ByteArrayOutputStream(); 87 | val err = new ByteArrayOutputStream(); 88 | val proc = ScalaScriptProcess.create(file, out, err); 89 | proc match { 90 | case Some(p) => { 91 | val pid = p.run(); 92 | val timeout = new TimeoutActor(pid, 10 * 1000) 93 | timeout.start 94 | val existValue = pid.exitValue() 95 | timeout ! existValue 96 | val outEvents = new String(out.toByteArray()).lines.toList 97 | val errEvents = new String(err.toByteArray()).lines.toList.filterNot(_.startsWith("Picked up JAVA_TOOL_OPTIONS")) 98 | existValue match { 99 | case 0 => (outEvents, errEvents, 0) 100 | case x => (outEvents, errEvents ::: List("exit value is " + x), x) 101 | } 102 | 103 | } 104 | case _ => (List(), List(), -1) 105 | } 106 | } 107 | 108 | class TimeoutActor(proc: scala.sys.process.Process, timeout: Long) extends Actor { 109 | def act() { 110 | receiveWithin(timeout) { 111 | case Int => 112 | case TIMEOUT => proc.destroy() 113 | } 114 | } 115 | } 116 | 117 | def json(mode: RunResponse): String = { 118 | import org.json4s._ 119 | import org.json4s.JsonDSL._ 120 | val json = ("Errors" -> mode.errors) ~ ("Events" -> mode.events) ~ ("ErrEvents" -> mode.errEvents) 121 | JsonMethods.pretty(JsonMethods.render(json)) 122 | } 123 | 124 | def memo(f: String => RunResponse) = { 125 | (x: String) => 126 | { 127 | val response = Web.cache.get(x) 128 | response match { 129 | case Some(resp) => resp 130 | case None => { 131 | val resp = f(x) 132 | resp.exitValue match { 133 | case 0 => Web.cache.getOrElseUpdate(x, resp) 134 | case _ => resp 135 | } 136 | } 137 | 138 | } 139 | } 140 | } 141 | override def doGet(req: HttpServletRequest, resp: HttpServletResponse) = { 142 | val code = req.getParameter("code") 143 | if (code == null) 144 | resp.setStatus(404) 145 | else { 146 | var f = memo(compileAndRun) 147 | val model = f(code) 148 | resp.getWriter().print(json(model)) 149 | resp.getWriter().flush() 150 | resp.setStatus(200) 151 | } 152 | } 153 | 154 | override def doPost(req: HttpServletRequest, resp: HttpServletResponse) = { 155 | doGet(req, resp) 156 | } 157 | } 158 | 159 | case class RunResponse(errors: List[String], events: List[String], errEvents: List[String], exitValue: Int) { 160 | 161 | } -------------------------------------------------------------------------------- /src/main/scala/com/yankay/scalaTour/script/TaskMem.scala: -------------------------------------------------------------------------------- 1 | package com.yankay.scalaTour.script 2 | 3 | import java.util.Date 4 | import scala.actors.Actor._ 5 | import scala.actors.TIMEOUT 6 | import org.apache.commons.logging.LogFactory 7 | 8 | object CompileStatus extends Enumeration { 9 | type T = Value 10 | val Perpared, Compileing, Compiled = Value 11 | } 12 | 13 | object RunStatus extends Enumeration { 14 | type T = Value 15 | val Perpared, Running, Runned = Value 16 | } 17 | 18 | case class Task(val taskId: Int, val script: String, 19 | val compiledPath: Option[String], val compileLog: List[String], val compileStatus: CompileStatus.T, 20 | val stdOut: List[String], val stdErr: List[String], 21 | val runStart: Option[Date], val runEnd: Option[Date], 22 | val runStatus: RunStatus.T, val runCode: Option[Int], 23 | val expireAt: Date) 24 | 25 | object TaskMem { 26 | 27 | val logger = LogFactory.getLog("TaskMem") 28 | 29 | var tasks = List[Task]() 30 | 31 | def getById(id: Int): Option[Task] = { 32 | TaskMem.synchronized( 33 | tasks.find(_.taskId == id)) 34 | } 35 | 36 | def getByScript(script: String): Option[Task] = { 37 | TaskMem.synchronized( 38 | tasks.find(_.script == script)) 39 | } 40 | 41 | def putAndReplace(task: Task) = { 42 | logger.debug("putAndReplace " + task) 43 | TaskMem.synchronized( 44 | tasks = task :: tasks.filterNot(_.taskId == task.taskId)) 45 | } 46 | 47 | actor { 48 | loop { 49 | reactWithin(60 * 1000) { 50 | case TIMEOUT => removeExpire 51 | } 52 | } 53 | } 54 | 55 | def removeExpire() = { 56 | logger.debug("removeExpire") 57 | TaskMem.synchronized( 58 | tasks = tasks.filter(_.expireAt.after(new Date()))) 59 | } 60 | 61 | var id = 0 62 | 63 | def generateId(): Int = { 64 | TaskMem.synchronized { 65 | id = id + 1; 66 | return id 67 | } 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/test/scala/com/yankay/scalaTour/ScalaScriptCompilerTest.scala: -------------------------------------------------------------------------------- 1 | package com.yankay.scalaTour 2 | 3 | import java.util.Date 4 | import java.text.SimpleDateFormat 5 | import scala.util.matching.Regex 6 | 7 | object ScalaScriptCompilerTest { 8 | // this only handles functions with a single argument. 9 | 10 | } 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/scala/com/yankay/scalaTour/script/TaskMem.scala: -------------------------------------------------------------------------------- 1 | package com.yankay.scalaTour.script 2 | 3 | import java.util.Date 4 | 5 | object TaskMemTest { 6 | def main(args: Array[String]) { 7 | 8 | } 9 | 10 | 11 | 12 | 13 | } -------------------------------------------------------------------------------- /src/test/scala/test.scala: -------------------------------------------------------------------------------- 1 | 2 | 3 | import scala.util.matching.Regex 4 | 5 | object test { 6 | def main(args: Array[String]) { 7 | import scala.collection.mutable.WeakHashMap 8 | 9 | val cache = new WeakHashMap[Int, Int] 10 | def memo(f: Int => Int) = (x: Int) => cache.getOrElseUpdate(x, f(x)) 11 | 12 | def fibonacci_(in: Int): Int = in match { 13 | case 0 => 0; 14 | case 1 => 1; 15 | case n: Int => memo(fibonacci_)(n - 1) + memo(fibonacci_)(n - 2) 16 | } 17 | 18 | val fibonacci: Int => Int = memo(fibonacci_) 19 | 20 | val t1 = System.currentTimeMillis() 21 | println(fibonacci(40)) 22 | println("it takes " + (System.currentTimeMillis() - t1) + "ms") 23 | 24 | val t2 = System.currentTimeMillis() 25 | println(fibonacci(40)) 26 | println("it takes " + (System.currentTimeMillis() - t2) + "ms") 27 | 28 | } 29 | 30 | } 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /webapp/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankay/scala-tour/7a482eea52e1ee429cb5961cb7ee5272227b44b1/webapp/apple-touch-icon.png -------------------------------------------------------------------------------- /webapp/css/bootstrap-responsive.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.1 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */ 10 | 11 | .clearfix { 12 | *zoom: 1; 13 | } 14 | 15 | .clearfix:before, 16 | .clearfix:after { 17 | display: table; 18 | line-height: 0; 19 | content: ""; 20 | } 21 | 22 | .clearfix:after { 23 | clear: both; 24 | } 25 | 26 | .hide-text { 27 | font: 0/0 a; 28 | color: transparent; 29 | text-shadow: none; 30 | background-color: transparent; 31 | border: 0; 32 | } 33 | 34 | .input-block-level { 35 | display: block; 36 | width: 100%; 37 | min-height: 30px; 38 | -webkit-box-sizing: border-box; 39 | -moz-box-sizing: border-box; 40 | box-sizing: border-box; 41 | } 42 | 43 | @-ms-viewport { 44 | width: device-width; 45 | } 46 | 47 | .hidden { 48 | display: none; 49 | visibility: hidden; 50 | } 51 | 52 | .visible-phone { 53 | display: none !important; 54 | } 55 | 56 | .visible-tablet { 57 | display: none !important; 58 | } 59 | 60 | .hidden-desktop { 61 | display: none !important; 62 | } 63 | 64 | .visible-desktop { 65 | display: inherit !important; 66 | } 67 | 68 | @media (min-width: 768px) and (max-width: 979px) { 69 | .hidden-desktop { 70 | display: inherit !important; 71 | } 72 | .visible-desktop { 73 | display: none !important ; 74 | } 75 | .visible-tablet { 76 | display: inherit !important; 77 | } 78 | .hidden-tablet { 79 | display: none !important; 80 | } 81 | } 82 | 83 | @media (max-width: 767px) { 84 | .hidden-desktop { 85 | display: inherit !important; 86 | } 87 | .visible-desktop { 88 | display: none !important; 89 | } 90 | .visible-phone { 91 | display: inherit !important; 92 | } 93 | .hidden-phone { 94 | display: none !important; 95 | } 96 | } 97 | 98 | .visible-print { 99 | display: none !important; 100 | } 101 | 102 | @media print { 103 | .visible-print { 104 | display: inherit !important; 105 | } 106 | .hidden-print { 107 | display: none !important; 108 | } 109 | } 110 | 111 | @media (min-width: 1200px) { 112 | .row { 113 | margin-left: -30px; 114 | *zoom: 1; 115 | } 116 | .row:before, 117 | .row:after { 118 | display: table; 119 | line-height: 0; 120 | content: ""; 121 | } 122 | .row:after { 123 | clear: both; 124 | } 125 | [class*="span"] { 126 | float: left; 127 | min-height: 1px; 128 | margin-left: 30px; 129 | } 130 | .container, 131 | .navbar-static-top .container, 132 | .navbar-fixed-top .container, 133 | .navbar-fixed-bottom .container { 134 | width: 1170px; 135 | } 136 | .span12 { 137 | width: 1170px; 138 | } 139 | .span11 { 140 | width: 1070px; 141 | } 142 | .span10 { 143 | width: 970px; 144 | } 145 | .span9 { 146 | width: 870px; 147 | } 148 | .span8 { 149 | width: 770px; 150 | } 151 | .span7 { 152 | width: 670px; 153 | } 154 | .span6 { 155 | width: 570px; 156 | } 157 | .span5 { 158 | width: 470px; 159 | } 160 | .span4 { 161 | width: 370px; 162 | } 163 | .span3 { 164 | width: 270px; 165 | } 166 | .span2 { 167 | width: 170px; 168 | } 169 | .span1 { 170 | width: 70px; 171 | } 172 | .offset12 { 173 | margin-left: 1230px; 174 | } 175 | .offset11 { 176 | margin-left: 1130px; 177 | } 178 | .offset10 { 179 | margin-left: 1030px; 180 | } 181 | .offset9 { 182 | margin-left: 930px; 183 | } 184 | .offset8 { 185 | margin-left: 830px; 186 | } 187 | .offset7 { 188 | margin-left: 730px; 189 | } 190 | .offset6 { 191 | margin-left: 630px; 192 | } 193 | .offset5 { 194 | margin-left: 530px; 195 | } 196 | .offset4 { 197 | margin-left: 430px; 198 | } 199 | .offset3 { 200 | margin-left: 330px; 201 | } 202 | .offset2 { 203 | margin-left: 230px; 204 | } 205 | .offset1 { 206 | margin-left: 130px; 207 | } 208 | .row-fluid { 209 | width: 100%; 210 | *zoom: 1; 211 | } 212 | .row-fluid:before, 213 | .row-fluid:after { 214 | display: table; 215 | line-height: 0; 216 | content: ""; 217 | } 218 | .row-fluid:after { 219 | clear: both; 220 | } 221 | .row-fluid [class*="span"] { 222 | display: block; 223 | float: left; 224 | width: 100%; 225 | min-height: 30px; 226 | margin-left: 2.564102564102564%; 227 | *margin-left: 2.5109110747408616%; 228 | -webkit-box-sizing: border-box; 229 | -moz-box-sizing: border-box; 230 | box-sizing: border-box; 231 | } 232 | .row-fluid [class*="span"]:first-child { 233 | margin-left: 0; 234 | } 235 | .row-fluid .controls-row [class*="span"] + [class*="span"] { 236 | margin-left: 2.564102564102564%; 237 | } 238 | .row-fluid .span12 { 239 | width: 100%; 240 | *width: 99.94680851063829%; 241 | } 242 | .row-fluid .span11 { 243 | width: 91.45299145299145%; 244 | *width: 91.39979996362975%; 245 | } 246 | .row-fluid .span10 { 247 | width: 82.90598290598291%; 248 | *width: 82.8527914166212%; 249 | } 250 | .row-fluid .span9 { 251 | width: 74.35897435897436%; 252 | *width: 74.30578286961266%; 253 | } 254 | .row-fluid .span8 { 255 | width: 65.81196581196582%; 256 | *width: 65.75877432260411%; 257 | } 258 | .row-fluid .span7 { 259 | width: 57.26495726495726%; 260 | *width: 57.21176577559556%; 261 | } 262 | .row-fluid .span6 { 263 | width: 48.717948717948715%; 264 | *width: 48.664757228587014%; 265 | } 266 | .row-fluid .span5 { 267 | width: 40.17094017094017%; 268 | *width: 40.11774868157847%; 269 | } 270 | .row-fluid .span4 { 271 | width: 31.623931623931625%; 272 | *width: 31.570740134569924%; 273 | } 274 | .row-fluid .span3 { 275 | width: 23.076923076923077%; 276 | *width: 23.023731587561375%; 277 | } 278 | .row-fluid .span2 { 279 | width: 14.52991452991453%; 280 | *width: 14.476723040552828%; 281 | } 282 | .row-fluid .span1 { 283 | width: 5.982905982905983%; 284 | *width: 5.929714493544281%; 285 | } 286 | .row-fluid .offset12 { 287 | margin-left: 105.12820512820512%; 288 | *margin-left: 105.02182214948171%; 289 | } 290 | .row-fluid .offset12:first-child { 291 | margin-left: 102.56410256410257%; 292 | *margin-left: 102.45771958537915%; 293 | } 294 | .row-fluid .offset11 { 295 | margin-left: 96.58119658119658%; 296 | *margin-left: 96.47481360247316%; 297 | } 298 | .row-fluid .offset11:first-child { 299 | margin-left: 94.01709401709402%; 300 | *margin-left: 93.91071103837061%; 301 | } 302 | .row-fluid .offset10 { 303 | margin-left: 88.03418803418803%; 304 | *margin-left: 87.92780505546462%; 305 | } 306 | .row-fluid .offset10:first-child { 307 | margin-left: 85.47008547008548%; 308 | *margin-left: 85.36370249136206%; 309 | } 310 | .row-fluid .offset9 { 311 | margin-left: 79.48717948717949%; 312 | *margin-left: 79.38079650845607%; 313 | } 314 | .row-fluid .offset9:first-child { 315 | margin-left: 76.92307692307693%; 316 | *margin-left: 76.81669394435352%; 317 | } 318 | .row-fluid .offset8 { 319 | margin-left: 70.94017094017094%; 320 | *margin-left: 70.83378796144753%; 321 | } 322 | .row-fluid .offset8:first-child { 323 | margin-left: 68.37606837606839%; 324 | *margin-left: 68.26968539734497%; 325 | } 326 | .row-fluid .offset7 { 327 | margin-left: 62.393162393162385%; 328 | *margin-left: 62.28677941443899%; 329 | } 330 | .row-fluid .offset7:first-child { 331 | margin-left: 59.82905982905982%; 332 | *margin-left: 59.72267685033642%; 333 | } 334 | .row-fluid .offset6 { 335 | margin-left: 53.84615384615384%; 336 | *margin-left: 53.739770867430444%; 337 | } 338 | .row-fluid .offset6:first-child { 339 | margin-left: 51.28205128205128%; 340 | *margin-left: 51.175668303327875%; 341 | } 342 | .row-fluid .offset5 { 343 | margin-left: 45.299145299145295%; 344 | *margin-left: 45.1927623204219%; 345 | } 346 | .row-fluid .offset5:first-child { 347 | margin-left: 42.73504273504273%; 348 | *margin-left: 42.62865975631933%; 349 | } 350 | .row-fluid .offset4 { 351 | margin-left: 36.75213675213675%; 352 | *margin-left: 36.645753773413354%; 353 | } 354 | .row-fluid .offset4:first-child { 355 | margin-left: 34.18803418803419%; 356 | *margin-left: 34.081651209310785%; 357 | } 358 | .row-fluid .offset3 { 359 | margin-left: 28.205128205128204%; 360 | *margin-left: 28.0987452264048%; 361 | } 362 | .row-fluid .offset3:first-child { 363 | margin-left: 25.641025641025642%; 364 | *margin-left: 25.53464266230224%; 365 | } 366 | .row-fluid .offset2 { 367 | margin-left: 19.65811965811966%; 368 | *margin-left: 19.551736679396257%; 369 | } 370 | .row-fluid .offset2:first-child { 371 | margin-left: 17.094017094017094%; 372 | *margin-left: 16.98763411529369%; 373 | } 374 | .row-fluid .offset1 { 375 | margin-left: 11.11111111111111%; 376 | *margin-left: 11.004728132387708%; 377 | } 378 | .row-fluid .offset1:first-child { 379 | margin-left: 8.547008547008547%; 380 | *margin-left: 8.440625568285142%; 381 | } 382 | input, 383 | textarea, 384 | .uneditable-input { 385 | margin-left: 0; 386 | } 387 | .controls-row [class*="span"] + [class*="span"] { 388 | margin-left: 30px; 389 | } 390 | input.span12, 391 | textarea.span12, 392 | .uneditable-input.span12 { 393 | width: 1156px; 394 | } 395 | input.span11, 396 | textarea.span11, 397 | .uneditable-input.span11 { 398 | width: 1056px; 399 | } 400 | input.span10, 401 | textarea.span10, 402 | .uneditable-input.span10 { 403 | width: 956px; 404 | } 405 | input.span9, 406 | textarea.span9, 407 | .uneditable-input.span9 { 408 | width: 856px; 409 | } 410 | input.span8, 411 | textarea.span8, 412 | .uneditable-input.span8 { 413 | width: 756px; 414 | } 415 | input.span7, 416 | textarea.span7, 417 | .uneditable-input.span7 { 418 | width: 656px; 419 | } 420 | input.span6, 421 | textarea.span6, 422 | .uneditable-input.span6 { 423 | width: 556px; 424 | } 425 | input.span5, 426 | textarea.span5, 427 | .uneditable-input.span5 { 428 | width: 456px; 429 | } 430 | input.span4, 431 | textarea.span4, 432 | .uneditable-input.span4 { 433 | width: 356px; 434 | } 435 | input.span3, 436 | textarea.span3, 437 | .uneditable-input.span3 { 438 | width: 256px; 439 | } 440 | input.span2, 441 | textarea.span2, 442 | .uneditable-input.span2 { 443 | width: 156px; 444 | } 445 | input.span1, 446 | textarea.span1, 447 | .uneditable-input.span1 { 448 | width: 56px; 449 | } 450 | .thumbnails { 451 | margin-left: -30px; 452 | } 453 | .thumbnails > li { 454 | margin-left: 30px; 455 | } 456 | .row-fluid .thumbnails { 457 | margin-left: 0; 458 | } 459 | } 460 | 461 | @media (min-width: 768px) and (max-width: 979px) { 462 | .row { 463 | margin-left: -20px; 464 | *zoom: 1; 465 | } 466 | .row:before, 467 | .row:after { 468 | display: table; 469 | line-height: 0; 470 | content: ""; 471 | } 472 | .row:after { 473 | clear: both; 474 | } 475 | [class*="span"] { 476 | float: left; 477 | min-height: 1px; 478 | margin-left: 20px; 479 | } 480 | .container, 481 | .navbar-static-top .container, 482 | .navbar-fixed-top .container, 483 | .navbar-fixed-bottom .container { 484 | width: 724px; 485 | } 486 | .span12 { 487 | width: 724px; 488 | } 489 | .span11 { 490 | width: 662px; 491 | } 492 | .span10 { 493 | width: 600px; 494 | } 495 | .span9 { 496 | width: 538px; 497 | } 498 | .span8 { 499 | width: 476px; 500 | } 501 | .span7 { 502 | width: 414px; 503 | } 504 | .span6 { 505 | width: 352px; 506 | } 507 | .span5 { 508 | width: 290px; 509 | } 510 | .span4 { 511 | width: 228px; 512 | } 513 | .span3 { 514 | width: 166px; 515 | } 516 | .span2 { 517 | width: 104px; 518 | } 519 | .span1 { 520 | width: 42px; 521 | } 522 | .offset12 { 523 | margin-left: 764px; 524 | } 525 | .offset11 { 526 | margin-left: 702px; 527 | } 528 | .offset10 { 529 | margin-left: 640px; 530 | } 531 | .offset9 { 532 | margin-left: 578px; 533 | } 534 | .offset8 { 535 | margin-left: 516px; 536 | } 537 | .offset7 { 538 | margin-left: 454px; 539 | } 540 | .offset6 { 541 | margin-left: 392px; 542 | } 543 | .offset5 { 544 | margin-left: 330px; 545 | } 546 | .offset4 { 547 | margin-left: 268px; 548 | } 549 | .offset3 { 550 | margin-left: 206px; 551 | } 552 | .offset2 { 553 | margin-left: 144px; 554 | } 555 | .offset1 { 556 | margin-left: 82px; 557 | } 558 | .row-fluid { 559 | width: 100%; 560 | *zoom: 1; 561 | } 562 | .row-fluid:before, 563 | .row-fluid:after { 564 | display: table; 565 | line-height: 0; 566 | content: ""; 567 | } 568 | .row-fluid:after { 569 | clear: both; 570 | } 571 | .row-fluid [class*="span"] { 572 | display: block; 573 | float: left; 574 | width: 100%; 575 | min-height: 30px; 576 | margin-left: 2.7624309392265194%; 577 | *margin-left: 2.709239449864817%; 578 | -webkit-box-sizing: border-box; 579 | -moz-box-sizing: border-box; 580 | box-sizing: border-box; 581 | } 582 | .row-fluid [class*="span"]:first-child { 583 | margin-left: 0; 584 | } 585 | .row-fluid .controls-row [class*="span"] + [class*="span"] { 586 | margin-left: 2.7624309392265194%; 587 | } 588 | .row-fluid .span12 { 589 | width: 100%; 590 | *width: 99.94680851063829%; 591 | } 592 | .row-fluid .span11 { 593 | width: 91.43646408839778%; 594 | *width: 91.38327259903608%; 595 | } 596 | .row-fluid .span10 { 597 | width: 82.87292817679558%; 598 | *width: 82.81973668743387%; 599 | } 600 | .row-fluid .span9 { 601 | width: 74.30939226519337%; 602 | *width: 74.25620077583166%; 603 | } 604 | .row-fluid .span8 { 605 | width: 65.74585635359117%; 606 | *width: 65.69266486422946%; 607 | } 608 | .row-fluid .span7 { 609 | width: 57.18232044198895%; 610 | *width: 57.12912895262725%; 611 | } 612 | .row-fluid .span6 { 613 | width: 48.61878453038674%; 614 | *width: 48.56559304102504%; 615 | } 616 | .row-fluid .span5 { 617 | width: 40.05524861878453%; 618 | *width: 40.00205712942283%; 619 | } 620 | .row-fluid .span4 { 621 | width: 31.491712707182323%; 622 | *width: 31.43852121782062%; 623 | } 624 | .row-fluid .span3 { 625 | width: 22.92817679558011%; 626 | *width: 22.87498530621841%; 627 | } 628 | .row-fluid .span2 { 629 | width: 14.3646408839779%; 630 | *width: 14.311449394616199%; 631 | } 632 | .row-fluid .span1 { 633 | width: 5.801104972375691%; 634 | *width: 5.747913483013988%; 635 | } 636 | .row-fluid .offset12 { 637 | margin-left: 105.52486187845304%; 638 | *margin-left: 105.41847889972962%; 639 | } 640 | .row-fluid .offset12:first-child { 641 | margin-left: 102.76243093922652%; 642 | *margin-left: 102.6560479605031%; 643 | } 644 | .row-fluid .offset11 { 645 | margin-left: 96.96132596685082%; 646 | *margin-left: 96.8549429881274%; 647 | } 648 | .row-fluid .offset11:first-child { 649 | margin-left: 94.1988950276243%; 650 | *margin-left: 94.09251204890089%; 651 | } 652 | .row-fluid .offset10 { 653 | margin-left: 88.39779005524862%; 654 | *margin-left: 88.2914070765252%; 655 | } 656 | .row-fluid .offset10:first-child { 657 | margin-left: 85.6353591160221%; 658 | *margin-left: 85.52897613729868%; 659 | } 660 | .row-fluid .offset9 { 661 | margin-left: 79.8342541436464%; 662 | *margin-left: 79.72787116492299%; 663 | } 664 | .row-fluid .offset9:first-child { 665 | margin-left: 77.07182320441989%; 666 | *margin-left: 76.96544022569647%; 667 | } 668 | .row-fluid .offset8 { 669 | margin-left: 71.2707182320442%; 670 | *margin-left: 71.16433525332079%; 671 | } 672 | .row-fluid .offset8:first-child { 673 | margin-left: 68.50828729281768%; 674 | *margin-left: 68.40190431409427%; 675 | } 676 | .row-fluid .offset7 { 677 | margin-left: 62.70718232044199%; 678 | *margin-left: 62.600799341718584%; 679 | } 680 | .row-fluid .offset7:first-child { 681 | margin-left: 59.94475138121547%; 682 | *margin-left: 59.838368402492065%; 683 | } 684 | .row-fluid .offset6 { 685 | margin-left: 54.14364640883978%; 686 | *margin-left: 54.037263430116376%; 687 | } 688 | .row-fluid .offset6:first-child { 689 | margin-left: 51.38121546961326%; 690 | *margin-left: 51.27483249088986%; 691 | } 692 | .row-fluid .offset5 { 693 | margin-left: 45.58011049723757%; 694 | *margin-left: 45.47372751851417%; 695 | } 696 | .row-fluid .offset5:first-child { 697 | margin-left: 42.81767955801105%; 698 | *margin-left: 42.71129657928765%; 699 | } 700 | .row-fluid .offset4 { 701 | margin-left: 37.01657458563536%; 702 | *margin-left: 36.91019160691196%; 703 | } 704 | .row-fluid .offset4:first-child { 705 | margin-left: 34.25414364640884%; 706 | *margin-left: 34.14776066768544%; 707 | } 708 | .row-fluid .offset3 { 709 | margin-left: 28.45303867403315%; 710 | *margin-left: 28.346655695309746%; 711 | } 712 | .row-fluid .offset3:first-child { 713 | margin-left: 25.69060773480663%; 714 | *margin-left: 25.584224756083227%; 715 | } 716 | .row-fluid .offset2 { 717 | margin-left: 19.88950276243094%; 718 | *margin-left: 19.783119783707537%; 719 | } 720 | .row-fluid .offset2:first-child { 721 | margin-left: 17.12707182320442%; 722 | *margin-left: 17.02068884448102%; 723 | } 724 | .row-fluid .offset1 { 725 | margin-left: 11.32596685082873%; 726 | *margin-left: 11.219583872105325%; 727 | } 728 | .row-fluid .offset1:first-child { 729 | margin-left: 8.56353591160221%; 730 | *margin-left: 8.457152932878806%; 731 | } 732 | input, 733 | textarea, 734 | .uneditable-input { 735 | margin-left: 0; 736 | } 737 | .controls-row [class*="span"] + [class*="span"] { 738 | margin-left: 20px; 739 | } 740 | input.span12, 741 | textarea.span12, 742 | .uneditable-input.span12 { 743 | width: 710px; 744 | } 745 | input.span11, 746 | textarea.span11, 747 | .uneditable-input.span11 { 748 | width: 648px; 749 | } 750 | input.span10, 751 | textarea.span10, 752 | .uneditable-input.span10 { 753 | width: 586px; 754 | } 755 | input.span9, 756 | textarea.span9, 757 | .uneditable-input.span9 { 758 | width: 524px; 759 | } 760 | input.span8, 761 | textarea.span8, 762 | .uneditable-input.span8 { 763 | width: 462px; 764 | } 765 | input.span7, 766 | textarea.span7, 767 | .uneditable-input.span7 { 768 | width: 400px; 769 | } 770 | input.span6, 771 | textarea.span6, 772 | .uneditable-input.span6 { 773 | width: 338px; 774 | } 775 | input.span5, 776 | textarea.span5, 777 | .uneditable-input.span5 { 778 | width: 276px; 779 | } 780 | input.span4, 781 | textarea.span4, 782 | .uneditable-input.span4 { 783 | width: 214px; 784 | } 785 | input.span3, 786 | textarea.span3, 787 | .uneditable-input.span3 { 788 | width: 152px; 789 | } 790 | input.span2, 791 | textarea.span2, 792 | .uneditable-input.span2 { 793 | width: 90px; 794 | } 795 | input.span1, 796 | textarea.span1, 797 | .uneditable-input.span1 { 798 | width: 28px; 799 | } 800 | } 801 | 802 | @media (max-width: 767px) { 803 | body { 804 | padding-right: 20px; 805 | padding-left: 20px; 806 | } 807 | .navbar-fixed-top, 808 | .navbar-fixed-bottom, 809 | .navbar-static-top { 810 | margin-right: -20px; 811 | margin-left: -20px; 812 | } 813 | .container-fluid { 814 | padding: 0; 815 | } 816 | .dl-horizontal dt { 817 | float: none; 818 | width: auto; 819 | clear: none; 820 | text-align: left; 821 | } 822 | .dl-horizontal dd { 823 | margin-left: 0; 824 | } 825 | .container { 826 | width: auto; 827 | } 828 | .row-fluid { 829 | width: 100%; 830 | } 831 | .row, 832 | .thumbnails { 833 | margin-left: 0; 834 | } 835 | .thumbnails > li { 836 | float: none; 837 | margin-left: 0; 838 | } 839 | [class*="span"], 840 | .uneditable-input[class*="span"], 841 | .row-fluid [class*="span"] { 842 | display: block; 843 | float: none; 844 | width: 100%; 845 | margin-left: 0; 846 | -webkit-box-sizing: border-box; 847 | -moz-box-sizing: border-box; 848 | box-sizing: border-box; 849 | } 850 | .span12, 851 | .row-fluid .span12 { 852 | width: 100%; 853 | -webkit-box-sizing: border-box; 854 | -moz-box-sizing: border-box; 855 | box-sizing: border-box; 856 | } 857 | .row-fluid [class*="offset"]:first-child { 858 | margin-left: 0; 859 | } 860 | .input-large, 861 | .input-xlarge, 862 | .input-xxlarge, 863 | input[class*="span"], 864 | select[class*="span"], 865 | textarea[class*="span"], 866 | .uneditable-input { 867 | display: block; 868 | width: 100%; 869 | min-height: 30px; 870 | -webkit-box-sizing: border-box; 871 | -moz-box-sizing: border-box; 872 | box-sizing: border-box; 873 | } 874 | .input-prepend input, 875 | .input-append input, 876 | .input-prepend input[class*="span"], 877 | .input-append input[class*="span"] { 878 | display: inline-block; 879 | width: auto; 880 | } 881 | .controls-row [class*="span"] + [class*="span"] { 882 | margin-left: 0; 883 | } 884 | .modal { 885 | position: fixed; 886 | top: 20px; 887 | right: 20px; 888 | left: 20px; 889 | width: auto; 890 | margin: 0; 891 | } 892 | .modal.fade { 893 | top: -100px; 894 | } 895 | .modal.fade.in { 896 | top: 20px; 897 | } 898 | } 899 | 900 | @media (max-width: 480px) { 901 | .nav-collapse { 902 | -webkit-transform: translate3d(0, 0, 0); 903 | } 904 | .page-header h1 small { 905 | display: block; 906 | line-height: 20px; 907 | } 908 | input[type="checkbox"], 909 | input[type="radio"] { 910 | border: 1px solid #ccc; 911 | } 912 | .form-horizontal .control-label { 913 | float: none; 914 | width: auto; 915 | padding-top: 0; 916 | text-align: left; 917 | } 918 | .form-horizontal .controls { 919 | margin-left: 0; 920 | } 921 | .form-horizontal .control-list { 922 | padding-top: 0; 923 | } 924 | .form-horizontal .form-actions { 925 | padding-right: 10px; 926 | padding-left: 10px; 927 | } 928 | .media .pull-left, 929 | .media .pull-right { 930 | display: block; 931 | float: none; 932 | margin-bottom: 10px; 933 | } 934 | .media-object { 935 | margin-right: 0; 936 | margin-left: 0; 937 | } 938 | .modal { 939 | top: 10px; 940 | right: 10px; 941 | left: 10px; 942 | } 943 | .modal-header .close { 944 | padding: 10px; 945 | margin: -10px; 946 | } 947 | .carousel-caption { 948 | position: static; 949 | } 950 | } 951 | 952 | @media (max-width: 979px) { 953 | body { 954 | padding-top: 0; 955 | } 956 | .navbar-fixed-top, 957 | .navbar-fixed-bottom { 958 | position: static; 959 | } 960 | .navbar-fixed-top { 961 | margin-bottom: 20px; 962 | } 963 | .navbar-fixed-bottom { 964 | margin-top: 20px; 965 | } 966 | .navbar-fixed-top .navbar-inner, 967 | .navbar-fixed-bottom .navbar-inner { 968 | padding: 5px; 969 | } 970 | .navbar .container { 971 | width: auto; 972 | padding: 0; 973 | } 974 | .navbar .brand { 975 | padding-right: 10px; 976 | padding-left: 10px; 977 | margin: 0 0 0 -5px; 978 | } 979 | .nav-collapse { 980 | clear: both; 981 | } 982 | .nav-collapse .nav { 983 | float: none; 984 | margin: 0 0 10px; 985 | } 986 | .nav-collapse .nav > li { 987 | float: none; 988 | } 989 | .nav-collapse .nav > li > a { 990 | margin-bottom: 2px; 991 | } 992 | .nav-collapse .nav > .divider-vertical { 993 | display: none; 994 | } 995 | .nav-collapse .nav .nav-header { 996 | color: #777777; 997 | text-shadow: none; 998 | } 999 | .nav-collapse .nav > li > a, 1000 | .nav-collapse .dropdown-menu a { 1001 | padding: 9px 15px; 1002 | font-weight: bold; 1003 | color: #777777; 1004 | -webkit-border-radius: 3px; 1005 | -moz-border-radius: 3px; 1006 | border-radius: 3px; 1007 | } 1008 | .nav-collapse .btn { 1009 | padding: 4px 10px 4px; 1010 | font-weight: normal; 1011 | -webkit-border-radius: 4px; 1012 | -moz-border-radius: 4px; 1013 | border-radius: 4px; 1014 | } 1015 | .nav-collapse .dropdown-menu li + li a { 1016 | margin-bottom: 2px; 1017 | } 1018 | .nav-collapse .nav > li > a:hover, 1019 | .nav-collapse .nav > li > a:focus, 1020 | .nav-collapse .dropdown-menu a:hover, 1021 | .nav-collapse .dropdown-menu a:focus { 1022 | background-color: #f2f2f2; 1023 | } 1024 | .navbar-inverse .nav-collapse .nav > li > a, 1025 | .navbar-inverse .nav-collapse .dropdown-menu a { 1026 | color: #999999; 1027 | } 1028 | .navbar-inverse .nav-collapse .nav > li > a:hover, 1029 | .navbar-inverse .nav-collapse .nav > li > a:focus, 1030 | .navbar-inverse .nav-collapse .dropdown-menu a:hover, 1031 | .navbar-inverse .nav-collapse .dropdown-menu a:focus { 1032 | background-color: #111111; 1033 | } 1034 | .nav-collapse.in .btn-group { 1035 | padding: 0; 1036 | margin-top: 5px; 1037 | } 1038 | .nav-collapse .dropdown-menu { 1039 | position: static; 1040 | top: auto; 1041 | left: auto; 1042 | display: none; 1043 | float: none; 1044 | max-width: none; 1045 | padding: 0; 1046 | margin: 0 15px; 1047 | background-color: transparent; 1048 | border: none; 1049 | -webkit-border-radius: 0; 1050 | -moz-border-radius: 0; 1051 | border-radius: 0; 1052 | -webkit-box-shadow: none; 1053 | -moz-box-shadow: none; 1054 | box-shadow: none; 1055 | } 1056 | .nav-collapse .open > .dropdown-menu { 1057 | display: block; 1058 | } 1059 | .nav-collapse .dropdown-menu:before, 1060 | .nav-collapse .dropdown-menu:after { 1061 | display: none; 1062 | } 1063 | .nav-collapse .dropdown-menu .divider { 1064 | display: none; 1065 | } 1066 | .nav-collapse .nav > li > .dropdown-menu:before, 1067 | .nav-collapse .nav > li > .dropdown-menu:after { 1068 | display: none; 1069 | } 1070 | .nav-collapse .navbar-form, 1071 | .nav-collapse .navbar-search { 1072 | float: none; 1073 | padding: 10px 15px; 1074 | margin: 10px 0; 1075 | border-top: 1px solid #f2f2f2; 1076 | border-bottom: 1px solid #f2f2f2; 1077 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1078 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1079 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1080 | } 1081 | .navbar-inverse .nav-collapse .navbar-form, 1082 | .navbar-inverse .nav-collapse .navbar-search { 1083 | border-top-color: #111111; 1084 | border-bottom-color: #111111; 1085 | } 1086 | .navbar .nav-collapse .nav.pull-right { 1087 | float: none; 1088 | margin-left: 0; 1089 | } 1090 | .nav-collapse, 1091 | .nav-collapse.collapse { 1092 | height: 0; 1093 | overflow: hidden; 1094 | } 1095 | .navbar .btn-navbar { 1096 | display: block; 1097 | } 1098 | .navbar-static .navbar-inner { 1099 | padding-right: 10px; 1100 | padding-left: 10px; 1101 | } 1102 | } 1103 | 1104 | @media (min-width: 980px) { 1105 | .nav-collapse.collapse { 1106 | height: auto !important; 1107 | overflow: visible !important; 1108 | } 1109 | } 1110 | -------------------------------------------------------------------------------- /webapp/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.1 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.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:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;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,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /webapp/css/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | } 8 | .CodeMirror-scroll { 9 | /* Set scrolling behaviour here */ 10 | overflow: auto; 11 | } 12 | 13 | /* PADDING */ 14 | 15 | .CodeMirror-lines { 16 | padding: 4px 0; /* Vertical padding around content */ 17 | } 18 | .CodeMirror pre { 19 | padding: 0 4px; /* Horizontal padding of content */ 20 | } 21 | 22 | .CodeMirror-scrollbar-filler { 23 | background-color: white; /* The little square between H and V scrollbars */ 24 | } 25 | 26 | /* GUTTER */ 27 | 28 | .CodeMirror-gutters { 29 | border-right: 1px solid #ddd; 30 | background-color: #f7f7f7; 31 | } 32 | .CodeMirror-linenumbers {} 33 | .CodeMirror-linenumber { 34 | padding: 0 3px 0 0px; 35 | min-width: 20px; 36 | text-align: right; 37 | color: #999; 38 | } 39 | 40 | /* CURSOR */ 41 | 42 | .CodeMirror div.CodeMirror-cursor { 43 | border-left: 1px solid black; 44 | z-index: 3; 45 | } 46 | /* Shown when moving in bi-directional text */ 47 | .CodeMirror div.CodeMirror-secondarycursor { 48 | border-left: 1px solid silver; 49 | } 50 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { 51 | width: auto; 52 | border: 0; 53 | background: #7e7; 54 | z-index: 1; 55 | } 56 | /* Can style cursor different in overwrite (non-insert) mode */ 57 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} 58 | 59 | .cm-tab { display: inline-block; } 60 | 61 | /* DEFAULT THEME */ 62 | 63 | .cm-s-default .cm-keyword {color: #708;} 64 | .cm-s-default .cm-atom {color: #219;} 65 | .cm-s-default .cm-number {color: #164;} 66 | .cm-s-default .cm-def {color: #00f;} 67 | .cm-s-default .cm-variable {color: black;} 68 | .cm-s-default .cm-variable-2 {color: #05a;} 69 | .cm-s-default .cm-variable-3 {color: #085;} 70 | .cm-s-default .cm-property {color: black;} 71 | .cm-s-default .cm-operator {color: black;} 72 | .cm-s-default .cm-comment {color: #a50;} 73 | .cm-s-default .cm-string {color: #a11;} 74 | .cm-s-default .cm-string-2 {color: #f50;} 75 | .cm-s-default .cm-meta {color: #555;} 76 | .cm-s-default .cm-error {color: #f00;} 77 | .cm-s-default .cm-qualifier {color: #555;} 78 | .cm-s-default .cm-builtin {color: #30a;} 79 | .cm-s-default .cm-bracket {color: #997;} 80 | .cm-s-default .cm-tag {color: #170;} 81 | .cm-s-default .cm-attribute {color: #00c;} 82 | .cm-s-default .cm-header {color: blue;} 83 | .cm-s-default .cm-quote {color: #090;} 84 | .cm-s-default .cm-hr {color: #999;} 85 | .cm-s-default .cm-link {color: #00c;} 86 | 87 | .cm-negative {color: #d44;} 88 | .cm-positive {color: #292;} 89 | .cm-header, .cm-strong {font-weight: bold;} 90 | .cm-em {font-style: italic;} 91 | .cm-link {text-decoration: underline;} 92 | 93 | .cm-invalidchar {color: #f00;} 94 | 95 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 96 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 97 | 98 | /* STOP */ 99 | 100 | /* The rest of this file contains styles related to the mechanics of 101 | the editor. You probably shouldn't touch them. */ 102 | 103 | .CodeMirror { 104 | line-height: 1; 105 | position: relative; 106 | overflow: hidden; 107 | background: white; 108 | color: black; 109 | } 110 | 111 | .CodeMirror-scroll { 112 | /* 30px is the magic margin used to hide the element's real scrollbars */ 113 | /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */ 114 | margin-bottom: -30px; margin-right: -30px; 115 | padding-bottom: 30px; padding-right: 30px; 116 | height: 100%; 117 | outline: none; /* Prevent dragging from highlighting the element */ 118 | position: relative; 119 | } 120 | .CodeMirror-sizer { 121 | position: relative; 122 | } 123 | 124 | /* The fake, visible scrollbars. Used to force redraw during scrolling 125 | before actuall scrolling happens, thus preventing shaking and 126 | flickering artifacts. */ 127 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler { 128 | position: absolute; 129 | z-index: 6; 130 | display: none; 131 | } 132 | .CodeMirror-vscrollbar { 133 | right: 0; top: 0; 134 | overflow-x: hidden; 135 | overflow-y: scroll; 136 | } 137 | .CodeMirror-hscrollbar { 138 | bottom: 0; left: 0; 139 | overflow-y: hidden; 140 | overflow-x: scroll; 141 | } 142 | .CodeMirror-scrollbar-filler { 143 | right: 0; bottom: 0; 144 | z-index: 6; 145 | } 146 | 147 | .CodeMirror-gutters { 148 | position: absolute; left: 0; top: 0; 149 | height: 100%; 150 | padding-bottom: 30px; 151 | z-index: 3; 152 | } 153 | .CodeMirror-gutter { 154 | height: 100%; 155 | padding-bottom: 30px; 156 | margin-bottom: -32px; 157 | display: inline-block; 158 | /* Hack to make IE7 behave */ 159 | *zoom:1; 160 | *display:inline; 161 | } 162 | .CodeMirror-gutter-elt { 163 | position: absolute; 164 | cursor: default; 165 | z-index: 4; 166 | } 167 | 168 | .CodeMirror-lines { 169 | cursor: text; 170 | } 171 | .CodeMirror pre { 172 | /* Reset some styles that the rest of the page might have set */ 173 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 174 | border-width: 0; 175 | background: transparent; 176 | font-family: inherit; 177 | font-size: inherit; 178 | margin: 0; 179 | white-space: pre; 180 | word-wrap: normal; 181 | line-height: inherit; 182 | color: inherit; 183 | z-index: 2; 184 | position: relative; 185 | overflow: visible; 186 | } 187 | .CodeMirror-wrap pre { 188 | word-wrap: break-word; 189 | white-space: pre-wrap; 190 | word-break: normal; 191 | } 192 | .CodeMirror-linebackground { 193 | position: absolute; 194 | left: 0; right: 0; top: 0; bottom: 0; 195 | z-index: 0; 196 | } 197 | 198 | .CodeMirror-linewidget { 199 | position: relative; 200 | z-index: 2; 201 | overflow: auto; 202 | } 203 | 204 | .CodeMirror-widget { 205 | display: inline-block; 206 | } 207 | 208 | .CodeMirror-wrap .CodeMirror-scroll { 209 | overflow-x: hidden; 210 | } 211 | 212 | .CodeMirror-measure { 213 | position: absolute; 214 | width: 100%; height: 0px; 215 | overflow: hidden; 216 | visibility: hidden; 217 | } 218 | .CodeMirror-measure pre { position: static; } 219 | 220 | .CodeMirror div.CodeMirror-cursor { 221 | position: absolute; 222 | visibility: hidden; 223 | border-right: none; 224 | width: 0; 225 | } 226 | .CodeMirror-focused div.CodeMirror-cursor { 227 | visibility: visible; 228 | } 229 | 230 | .CodeMirror-selected { background: #d9d9d9; } 231 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 232 | 233 | .cm-searching { 234 | background: #ffa; 235 | background: rgba(255, 255, 0, .4); 236 | } 237 | 238 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 239 | .CodeMirror span { *vertical-align: text-bottom; } 240 | 241 | @media print { 242 | /* Hide the cursor when printing */ 243 | .CodeMirror div.CodeMirror-cursor { 244 | visibility: hidden; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /webapp/css/elegant.css: -------------------------------------------------------------------------------- 1 | .cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom {color: #762;} 2 | .cm-s-elegant span.cm-comment {color: #262; font-style: italic; line-height: 1em;} 3 | .cm-s-elegant span.cm-meta {color: #555; font-style: italic; line-height: 1em;} 4 | .cm-s-elegant span.cm-variable {color: black;} 5 | .cm-s-elegant span.cm-variable-2 {color: #b11;} 6 | .cm-s-elegant span.cm-qualifier {color: #555;} 7 | .cm-s-elegant span.cm-keyword {color: #730;} 8 | .cm-s-elegant span.cm-builtin {color: #30a;} 9 | .cm-s-elegant span.cm-error {background-color: #fdd;} 10 | .cm-s-elegant span.cm-link {color: #762;} 11 | -------------------------------------------------------------------------------- /webapp/css/impress-demo.css: -------------------------------------------------------------------------------- 1 | /* 2 | So you like the style of impress.js demo? 3 | Or maybe you are just curious how it was done? 4 | 5 | You couldn't find a better place to find out! 6 | 7 | Welcome to the stylesheet impress.js demo presentation. 8 | 9 | Please remember that it is not meant to be a part of impress.js and is 10 | not required by impress.js. 11 | I expect that anyone creating a presentation for impress.js would create 12 | their own set of styles. 13 | 14 | But feel free to read through it and learn how to get the most of what 15 | impress.js provides. 16 | 17 | And let me be your guide. 18 | 19 | Shall we begin? 20 | */ 21 | 22 | 23 | /* 24 | We start with a good ol' reset. 25 | That's the one by Eric Meyer http://meyerweb.com/eric/tools/css/reset/ 26 | 27 | You can probably argue if it is needed here, or not, but for sure it 28 | doesn't do any harm and gives us a fresh start. 29 | */ 30 | 31 | html, body, div, span, applet, object, iframe, 32 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 33 | a, abbr, acronym, address, big, cite, code, 34 | del, dfn, em, img, ins, kbd, q, s, samp, 35 | small, strike, strong, sub, sup, tt, var, 36 | b, u, i, center, 37 | dl, dt, dd, ol, ul, li, 38 | fieldset, form, label, legend, 39 | table, caption, tbody, tfoot, thead, tr, th, td, 40 | article, aside, canvas, details, embed, 41 | figure, figcaption, footer, header, hgroup, 42 | menu, nav, output, ruby, section, summary, 43 | time, mark, audio, video { 44 | margin: 0; 45 | padding: 0; 46 | border: 0; 47 | font-size: 100%; 48 | font: inherit; 49 | vertical-align: baseline; 50 | } 51 | 52 | /* HTML5 display-role reset for older browsers */ 53 | article, aside, details, figcaption, figure, 54 | footer, header, hgroup, menu, nav, section { 55 | display: block; 56 | } 57 | body { 58 | line-height: 1; 59 | } 60 | ol, ul { 61 | list-style: none; 62 | } 63 | blockquote, q { 64 | quotes: none; 65 | } 66 | blockquote:before, blockquote:after, 67 | q:before, q:after { 68 | content: ''; 69 | content: none; 70 | } 71 | 72 | table { 73 | border-collapse: collapse; 74 | border-spacing: 0; 75 | } 76 | 77 | /* 78 | Now here is when interesting things start to appear. 79 | 80 | We set up styles with default font and nice gradient in the background. 81 | And yes, there is a lot of repetition there because of -prefixes but we don't 82 | want to leave anybody behind. 83 | */ 84 | body { 85 | font-family: 'PT Sans', sans-serif; 86 | min-height: 640px; 87 | 88 | background: rgb(215, 215, 215); 89 | background: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 500, from(rgb(240, 240, 240)), to(rgb(190, 190, 190))); 90 | background: -webkit-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 91 | background: -moz-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 92 | background: -ms-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 93 | background: -o-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 94 | background: radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 95 | } 96 | 97 | /* 98 | Now let's bring some text styles back ... 99 | */ 100 | b, strong { font-weight: bold } 101 | i, em { font-style: italic } 102 | 103 | /* 104 | ... and give links a nice look. 105 | */ 106 | a { 107 | color: inherit; 108 | text-decoration: none; 109 | padding: 0 0.1em; 110 | background: rgba(255,255,255,0.5); 111 | text-shadow: -1px -1px 2px rgba(100,100,100,0.9); 112 | border-radius: 0.2em; 113 | 114 | -webkit-transition: 0.5s; 115 | -moz-transition: 0.5s; 116 | -ms-transition: 0.5s; 117 | -o-transition: 0.5s; 118 | transition: 0.5s; 119 | } 120 | 121 | a:hover, 122 | a:focus { 123 | background: rgba(255,255,255,1); 124 | text-shadow: -1px -1px 2px rgba(100,100,100,0.5); 125 | } 126 | 127 | /* 128 | Because the main point behind the impress.js demo is to demo impress.js 129 | we display a fallback message for users with browsers that don't support 130 | all the features required by it. 131 | 132 | All of the content will be still fully accessible for them, but I want 133 | them to know that they are missing something - that's what the demo is 134 | about, isn't it? 135 | 136 | And then we hide the message, when support is detected in the browser. 137 | */ 138 | 139 | .fallback-message { 140 | font-family: sans-serif; 141 | line-height: 1.3; 142 | 143 | width: 780px; 144 | padding: 10px 10px 0; 145 | margin: 20px auto; 146 | 147 | border: 1px solid #E4C652; 148 | border-radius: 10px; 149 | background: #EEDC94; 150 | } 151 | 152 | .fallback-message p { 153 | margin-bottom: 10px; 154 | } 155 | 156 | .impress-supported .fallback-message { 157 | display: none; 158 | } 159 | 160 | .impress-not-supported #impress { 161 | display: none; 162 | } 163 | .impress-not-supported .hint { 164 | display: none; 165 | } 166 | 167 | 168 | /* 169 | Now let's style the presentation steps. 170 | 171 | We start with basics to make sure it displays correctly in everywhere ... 172 | */ 173 | 174 | .step { 175 | position: relative; 176 | width: 1024px; 177 | margin: 20px auto; 178 | 179 | -webkit-box-sizing: border-box; 180 | -moz-box-sizing: border-box; 181 | -ms-box-sizing: border-box; 182 | -o-box-sizing: border-box; 183 | box-sizing: border-box; 184 | 185 | font-family: 'PT Serif', georgia, serif; 186 | /* 187 | padding: 40px; 188 | font-size: 48px; 189 | line-height: 1.5; 190 | */ 191 | } 192 | 193 | /* 194 | ... and we enhance the styles for impress.js. 195 | 196 | Basically we remove the margin and make inactive steps a little bit transparent. 197 | */ 198 | .impress-enabled .step { 199 | margin: 20px 0 0 0; 200 | opacity: 0; 201 | 202 | -webkit-transition: opacity 1s; 203 | -moz-transition: opacity 1s; 204 | -ms-transition: opacity 1s; 205 | -o-transition: opacity 1s; 206 | transition: opacity 1s; 207 | } 208 | 209 | .impress-enabled .step.active { opacity: 1 } 210 | 211 | /* 212 | These 'slide' step styles were heavily inspired by HTML5 Slides: 213 | http://html5slides.googlecode.com/svn/trunk/styles.css 214 | 215 | ;) 216 | 217 | They cover everything what you see on first three steps of the demo. 218 | */ 219 | .slide { 220 | display: block; 221 | 222 | width: 1024px; 223 | height: 600px; 224 | color: rgb(102, 102, 102); 225 | text-shadow: 0 2px 2px rgba(0, 0, 0, .1); 226 | 227 | font-family: 'Open Sans', Arial, sans-serif; 228 | 229 | /* 230 | padding: 40px 60px; 231 | 232 | font-size: 30px; 233 | line-height: 36px; 234 | letter-spacing: -1px;*/ 235 | } 236 | 237 | .slide_background{ 238 | background-color: white; 239 | border: 1px solid rgba(0, 0, 0, .3); 240 | border-radius: 10px; 241 | box-shadow: 0 2px 6px rgba(0, 0, 0, .1); 242 | } 243 | 244 | .slide q { 245 | display: block; 246 | line-height: 72px; 247 | 248 | margin-top: 100px; 249 | /* 250 | font-size: 50px; 251 | */ 252 | } 253 | 254 | .slide q strong { 255 | white-space: nowrap; 256 | } 257 | 258 | /* 259 | And now we start to style each step separately. 260 | 261 | I agree that this may be not the most efficient, object-oriented and 262 | scalable way of styling, but most of steps have quite a custom look 263 | and typography tricks here and there, so they had to be styled separately. 264 | 265 | First is the title step with a big

(no room for padding) and some 266 | 3D positioning along Z axis. 267 | */ 268 | 269 | #title { 270 | padding: 0; 271 | } 272 | 273 | #title .try { 274 | font-size: 64px; 275 | position: absolute; 276 | top: -0.5em; 277 | left: 1.5em; 278 | 279 | -webkit-transform: translateZ(20px); 280 | -moz-transform: translateZ(20px); 281 | -ms-transform: translateZ(20px); 282 | -o-transform: translateZ(20px); 283 | transform: translateZ(20px); 284 | } 285 | 286 | #title h1 { 287 | font-size: 190px; 288 | 289 | -webkit-transform: translateZ(50px); 290 | -moz-transform: translateZ(50px); 291 | -ms-transform: translateZ(50px); 292 | -o-transform: translateZ(50px); 293 | transform: translateZ(50px); 294 | } 295 | 296 | #title .footnote { 297 | font-size: 32px; 298 | } 299 | 300 | /* 301 | Second step is nothing special, just a text with a link, so it doesn't need 302 | any special styling. 303 | 304 | Let's move to 'big thoughts' with centered text and custom font sizes. 305 | */ 306 | #big { 307 | width: 600px; 308 | text-align: center; 309 | font-size: 60px; 310 | line-height: 1; 311 | } 312 | 313 | #big b { 314 | display: block; 315 | font-size: 250px; 316 | line-height: 250px; 317 | } 318 | 319 | #big .thoughts { 320 | font-size: 90px; 321 | line-height: 150px; 322 | } 323 | 324 | /* 325 | 'Tiny ideas' just need some tiny styling. 326 | */ 327 | #tiny { 328 | width: 500px; 329 | text-align: center; 330 | } 331 | 332 | /* 333 | This step has some animated text ... 334 | */ 335 | #ing { width: 500px } 336 | 337 | /* 338 | ... so we define display to `inline-block` to enable transforms and 339 | transition duration to 0.5s ... 340 | */ 341 | #ing b { 342 | display: inline-block; 343 | -webkit-transition: 0.5s; 344 | -moz-transition: 0.5s; 345 | -ms-transition: 0.5s; 346 | -o-transition: 0.5s; 347 | transition: 0.5s; 348 | } 349 | 350 | /* 351 | ... and we want 'positioning` word to move up a bit when the step gets 352 | `present` class ... 353 | */ 354 | #ing.present .positioning { 355 | -webkit-transform: translateY(-10px); 356 | -moz-transform: translateY(-10px); 357 | -ms-transform: translateY(-10px); 358 | -o-transform: translateY(-10px); 359 | transform: translateY(-10px); 360 | } 361 | 362 | /* 363 | ... 'rotating' to rotate a quarter of a second later ... 364 | */ 365 | #ing.present .rotating { 366 | -webkit-transform: rotate(-10deg); 367 | -moz-transform: rotate(-10deg); 368 | -ms-transform: rotate(-10deg); 369 | -o-transform: rotate(-10deg); 370 | transform: rotate(-10deg); 371 | 372 | -webkit-transition-delay: 0.25s; 373 | -moz-transition-delay: 0.25s; 374 | -ms-transition-delay: 0.25s; 375 | -o-transition-delay: 0.25s; 376 | transition-delay: 0.25s; 377 | } 378 | 379 | /* 380 | ... and 'scaling' to scale down after another quarter of a second. 381 | */ 382 | #ing.present .scaling { 383 | -webkit-transform: scale(0.7); 384 | -moz-transform: scale(0.7); 385 | -ms-transform: scale(0.7); 386 | -o-transform: scale(0.7); 387 | transform: scale(0.7); 388 | 389 | -webkit-transition-delay: 0.5s; 390 | -moz-transition-delay: 0.5s; 391 | -ms-transition-delay: 0.5s; 392 | -o-transition-delay: 0.5s; 393 | transition-delay: 0.5s; 394 | } 395 | 396 | /* 397 | The 'imagination' step is again some boring font-sizing. 398 | */ 399 | 400 | #imagination { 401 | width: 600px; 402 | } 403 | 404 | #imagination .imagination { 405 | font-size: 78px; 406 | } 407 | 408 | /* 409 | There is nothing really special about 'use the source, Luke' step, too, 410 | except maybe of the Yoda background. 411 | 412 | As you can see below I've 'hard-coded' it in data URL. 413 | That's not the best way to serve images, but because that's just this one 414 | I decided it will be OK to have it this way. 415 | 416 | Just make sure you don't blindly copy this approach. 417 | */ 418 | #source { 419 | width: 700px; 420 | padding-bottom: 300px; 421 | 422 | /* Yoda Icon :: Pixel Art from Star Wars http://www.pixeljoint.com/pixelart/1423.htm */ 423 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAEYCAMAAACwUBm+AAAAAXNSR0IArs4c6QAAAKtQTFRFsAAAvbWSLUUrLEQqY1s8UYJMqJ1vNTEgOiIdIzYhjIFVLhsXZ6lgSEIsP2U8JhcCVzMsSXZEgXdOO145XJdWOl03LzAYMk4vSXNExr+hwcuxRTs1Qmk+RW9Am49eFRANQz4pUoNMQWc+OSMDTz0wLBsCNVMxa2NBOyUDUoNNSnlEWo9VRGxAVzYFl6tXCggHbLNmMUIcHhwTXkk5f3VNRT8wUT8xAAAACQocRBWFFwAAAAF0Uk5TAEDm2GYAAAPCSURBVHja7d3JctNAFIZRMwRCCGEmzPM8z/D+T8bu/ptbXXJFdij5fMt2Wuo+2UgqxVmtttq5WVotLzBgwIABAwYMGDCn0qVqbo69psPqVpWx+1XG5iaavF8wYMCAAQMGDBgwi4DJ6Y6qkxB1HNlcN3a92gbR5P2CAQMGDBgwYMCAWSxMlrU+UY5yu2l9okfV4bAxUVbf7TJnAwMGDBgwYMCAAbMLMHeqbGR82Zy+VR1Ht81nVca6R+UdTLaU24Ruzd3qM/e4yjnAgAEDBgwYMGDA7AJMd1l/3NRdVGcj3eX/2WEhCmDGxnM7yqygu8XIPjJj8iN/MGDAgAEDBgwYMAuDGb8q0RGlLCHLv1t9qDKWn3vdNHVuEI6HPaxO9Jo3GDBgwIABAwYMmIXBdC9ShGgMk+XnkXUeuGcsP/e1+lhNnZsL/G5Vs3OAAQMGDBgwYMCAWSxMR3SzOmraG5atdy9wZKzb+vg16qyqe2FltbnAgAEDBgwYMGDALAxmTJSuN3WA76rnVca6GTnemGN1WoEBAwYMGDBgwIBZGMxUomy4+xO899V4LAg5Xnc2MGDAgAEDBgwYMGA218Wq+2K1LDqvY9xZu8zN8fICdM6btYABAwYMGDBgwIABMzfH0+pGU5afze2tXebmeAfVz+p8BQYMGDBgwIABAwbMPBzZ+oWmfJrln1273FhkbHzee9WWbw7AgAEDBgwYMGDALAKm43hcdctKgblcPamOhuXnXlY5Xs6bsW4FGyQCAwYMGDBgwIABswiYMceZKgvMo+h8mrHLTdn676rj+FEFoTtHd8MwOxEYMGDAgAEDBgyYRcBM5UhXqiymW3R3c9ARhWO/OmjqfjVZy+xEYMCAAQMGDBgwYBYG073OnCV0RFNhMhaOa9WfKmOB6XjHMN1tQmaAAQMGDBgwYMCA2VWY7vXjz1U4croAzgPztwIDBgwYMGDAgAEDZhswh035NBw59Dww3RgYMGDAgAEDBgwYMJuD6f4tXT7NUqfCdBvZLkxXdgQGDBgwYMCAAQNmt2DGj8WzwAfV/w7T/aq7mxwwYMCAAQMGDBgwuwqTOo7uTwTngflSzQ3TdaJvAwEDBgwYMGDAgAED5gSvgbyo5oHZ4Pc+gwEDBgwYMGDAgAEzhOm+5G0qTGaAAQMGDBgwYMCAAXNaMOcnls3tNwWm+zRzp54NDBgwYMCAAQMGDJh5YNL36k1TLuGvVq+qnKMbS5n7tulT9asCAwYMGDBgwIABA2ZumKuztLnjgQEDBgwYMGDAgNl5mH/4/ltKA6vBNAAAAABJRU5ErkJggg==); 424 | background-position: bottom right; 425 | background-repeat: no-repeat; 426 | } 427 | 428 | #source q { 429 | font-size: 60px; 430 | } 431 | 432 | /* 433 | And the "it's in 3D" step again brings some 3D typography - just for fun. 434 | 435 | Because we want to position elements in 3D we set transform-style to 436 | `preserve-3d` on the paragraph. 437 | It is not needed by webkit browsers, but it is in Firefox. It's hard to say 438 | which behaviour is correct as 3D transforms spec is not very clear about it. 439 | */ 440 | #its-in-3d p { 441 | -webkit-transform-style: preserve-3d; 442 | -moz-transform-style: preserve-3d; /* Y U need this Firefox?! */ 443 | -ms-transform-style: preserve-3d; 444 | -o-transform-style: preserve-3d; 445 | transform-style: preserve-3d; 446 | } 447 | 448 | /* 449 | Below we position each word separately along Z axis and we want it to transition 450 | to default position in 0.5s when the step gets `present` class. 451 | 452 | Quite a simple idea, but lot's of styles and prefixes. 453 | */ 454 | #its-in-3d span, 455 | #its-in-3d b { 456 | display: inline-block; 457 | -webkit-transform: translateZ(40px); 458 | -moz-transform: translateZ(40px); 459 | -ms-transform: translateZ(40px); 460 | -o-transform: translateZ(40px); 461 | transform: translateZ(40px); 462 | 463 | -webkit-transition: 0.5s; 464 | -moz-transition: 0.5s; 465 | -ms-transition: 0.5s; 466 | -o-transition: 0.5s; 467 | transition: 0.5s; 468 | } 469 | 470 | #its-in-3d .have { 471 | -webkit-transform: translateZ(-40px); 472 | -moz-transform: translateZ(-40px); 473 | -ms-transform: translateZ(-40px); 474 | -o-transform: translateZ(-40px); 475 | transform: translateZ(-40px); 476 | } 477 | 478 | #its-in-3d .you { 479 | -webkit-transform: translateZ(20px); 480 | -moz-transform: translateZ(20px); 481 | -ms-transform: translateZ(20px); 482 | -o-transform: translateZ(20px); 483 | transform: translateZ(20px); 484 | } 485 | 486 | #its-in-3d .noticed { 487 | -webkit-transform: translateZ(-40px); 488 | -moz-transform: translateZ(-40px); 489 | -ms-transform: translateZ(-40px); 490 | -o-transform: translateZ(-40px); 491 | transform: translateZ(-40px); 492 | } 493 | 494 | #its-in-3d .its { 495 | -webkit-transform: translateZ(60px); 496 | -moz-transform: translateZ(60px); 497 | -ms-transform: translateZ(60px); 498 | -o-transform: translateZ(60px); 499 | transform: translateZ(60px); 500 | } 501 | 502 | #its-in-3d .in { 503 | -webkit-transform: translateZ(-10px); 504 | -moz-transform: translateZ(-10px); 505 | -ms-transform: translateZ(-10px); 506 | -o-transform: translateZ(-10px); 507 | transform: translateZ(-10px); 508 | } 509 | 510 | #its-in-3d .footnote { 511 | font-size: 32px; 512 | 513 | -webkit-transform: translateZ(-10px); 514 | -moz-transform: translateZ(-10px); 515 | -ms-transform: translateZ(-10px); 516 | -o-transform: translateZ(-10px); 517 | transform: translateZ(-10px); 518 | } 519 | 520 | #its-in-3d.present span, 521 | #its-in-3d.present b { 522 | -webkit-transform: translateZ(0px); 523 | -moz-transform: translateZ(0px); 524 | -ms-transform: translateZ(0px); 525 | -o-transform: translateZ(0px); 526 | transform: translateZ(0px); 527 | } 528 | 529 | /* 530 | The last step is an overview. 531 | There is no content in it, so we make sure it's not visible because we want 532 | to be able to click on other steps. 533 | 534 | */ 535 | #overview { display: none } 536 | 537 | /* 538 | We also make other steps visible and give them a pointer cursor using the 539 | `impress-on-` class. 540 | */ 541 | .impress-on-overview .step { 542 | opacity: 1; 543 | cursor: pointer; 544 | } 545 | 546 | 547 | /* 548 | Now, when we have all the steps styled let's give users a hint how to navigate 549 | around the presentation. 550 | 551 | The best way to do this would be to use JavaScript, show a delayed hint for a 552 | first time users, then hide it and store a status in cookie or localStorage... 553 | 554 | But I wanted to have some CSS fun and avoid additional scripting... 555 | 556 | Let me explain it first, so maybe the transition magic will be more readable 557 | when you read the code. 558 | 559 | First of all I wanted the hint to appear only when user is idle for a while. 560 | You can't detect the 'idle' state in CSS, but I delayed a appearing of the 561 | hint by 5s using transition-delay. 562 | 563 | You also can't detect in CSS if the user is a first-time visitor, so I had to 564 | make an assumption that I'll only show the hint on the first step. And when 565 | the step is changed hide the hint, because I can assume that user already 566 | knows how to navigate. 567 | 568 | To summarize it - hint is shown when the user is on the first step for longer 569 | than 5 seconds. 570 | 571 | The other problem I had was caused by the fact that I wanted the hint to fade 572 | in and out. It can be easily achieved by transitioning the opacity property. 573 | But that also meant that the hint was always on the screen, even if totally 574 | transparent. It covered part of the screen and you couldn't correctly clicked 575 | through it. 576 | Unfortunately you cannot transition between display `block` and `none` in pure 577 | CSS, so I needed a way to not only fade out the hint but also move it out of 578 | the screen. 579 | 580 | I solved this problem by positioning the hint below the bottom of the screen 581 | with CSS transform and moving it up to show it. But I also didn't want this move 582 | to be visible. I wanted the hint only to fade in and out visually, so I delayed 583 | the fade in transition, so it starts when the hint is already in its correct 584 | position on the screen. 585 | 586 | I know, it sounds complicated ... maybe it would be easier with the code? 587 | */ 588 | 589 | .hint { 590 | /* 591 | We hide the hint until presentation is started and from browsers not supporting 592 | impress.js, as they will have a linear scrollable view ... 593 | */ 594 | display: none; 595 | 596 | /* 597 | ... and give it some fixed position and nice styles. 598 | */ 599 | position: fixed; 600 | left: 0; 601 | right: 0; 602 | bottom: 200px; 603 | 604 | background: rgba(0,0,0,0.5); 605 | color: #EEE; 606 | text-align: center; 607 | 608 | font-size: 50px; 609 | padding: 20px; 610 | 611 | z-index: 100; 612 | 613 | /* 614 | By default we don't want the hint to be visible, so we make it transparent ... 615 | */ 616 | opacity: 0; 617 | 618 | /* 619 | ... and position it below the bottom of the screen (relative to it's fixed position) 620 | */ 621 | -webkit-transform: translateY(400px); 622 | -moz-transform: translateY(400px); 623 | -ms-transform: translateY(400px); 624 | -o-transform: translateY(400px); 625 | transform: translateY(400px); 626 | 627 | /* 628 | Now let's imagine that the hint is visible and we want to fade it out and move out 629 | of the screen. 630 | 631 | So we define the transition on the opacity property with 1s duration and another 632 | transition on transform property delayed by 1s so it will happen after the fade out 633 | on opacity finished. 634 | 635 | This way user will not see the hint moving down. 636 | */ 637 | -webkit-transition: opacity 1s, -webkit-transform 0.5s 1s; 638 | -moz-transition: opacity 1s, -moz-transform 0.5s 1s; 639 | -ms-transition: opacity 1s, -ms-transform 0.5s 1s; 640 | -o-transition: opacity 1s, -o-transform 0.5s 1s; 641 | transition: opacity 1s, transform 0.5s 1s; 642 | } 643 | 644 | /* 645 | Now we 'enable' the hint when presentation is initialized ... 646 | */ 647 | .impress-enabled .hint { display: block } 648 | 649 | /* 650 | ... and we will show it when the first step (with id 'bored') is active. 651 | */ 652 | .impress-on-welcome .hint { 653 | /* 654 | We remove the transparency and position the hint in its default fixed 655 | position. 656 | */ 657 | opacity: 1; 658 | 659 | -webkit-transform: translateY(0px); 660 | -moz-transform: translateY(0px); 661 | -ms-transform: translateY(0px); 662 | -o-transform: translateY(0px); 663 | transform: translateY(0px); 664 | 665 | /* 666 | Now for fade in transition we have the oposite situation from the one 667 | above. 668 | 669 | First after 4.5s delay we animate the transform property to move the hint 670 | into its correct position and after that we fade it in with opacity 671 | transition. 672 | */ 673 | -webkit-transition: opacity 1s 5s, -webkit-transform 0.5s 4.5s; 674 | -moz-transition: opacity 1s 5s, -moz-transform 0.5s 4.5s; 675 | -ms-transition: opacity 1s 5s, -ms-transform 0.5s 4.5s; 676 | -o-transition: opacity 1s 5s, -o-transform 0.5s 4.5s; 677 | transition: opacity 1s 5s, transform 0.5s 4.5s; 678 | 679 | } 680 | 681 | /* 682 | And as the last thing there is a workaround for quite strange bug. 683 | It happens a lot in Chrome. I don't remember if I've seen it in Firefox. 684 | 685 | Sometimes the element positioned in 3D (especially when it's moved back 686 | along Z axis) is not clickable, because it falls 'behind' the 687 | element. 688 | 689 | To prevent this, I decided to make non clickable by setting 690 | pointer-events property to `none` value. 691 | Value if this property is inherited, so to make everything else clickable 692 | I bring it back on the #impress element. 693 | 694 | If you want to know more about `pointer-events` here are some docs: 695 | https://developer.mozilla.org/en/CSS/pointer-events 696 | 697 | There is one very important thing to notice about this workaround - it makes 698 | everything 'unclickable' except what's in #impress element. 699 | 700 | So use it wisely ... or don't use at all. 701 | */ 702 | .impress-enabled { pointer-events: none } 703 | .impress-enabled #impress { pointer-events: auto } 704 | 705 | /* 706 | There is one funny thing I just realized. 707 | 708 | Thanks to this workaround above everything except #impress element is invisible 709 | for click events. That means that the hint element is also not clickable. 710 | So basically all of this transforms and delayed transitions trickery was probably 711 | not needed at all... 712 | 713 | But it was fun to learn about it, wasn't it? 714 | */ 715 | 716 | /* 717 | That's all I have for you in this file. 718 | Thanks for reading. I hope you enjoyed it at least as much as I enjoyed writing it 719 | for you. 720 | */ 721 | 722 | -------------------------------------------------------------------------------- /webapp/css/scala-tour.css: -------------------------------------------------------------------------------- 1 | 2 | .workspace { 3 | position: fixed; 4 | top: 0; 5 | bottom: 0; 6 | left: 0; 7 | right: 350px; 8 | min-height: 200px; 9 | text-shadow: 0 0 0 black 10 | 11 | } 12 | 13 | .workspace-top { 14 | position: absolute; 15 | top: 0; 16 | bottom: 30%; 17 | right: 0; 18 | left: 0; 19 | } 20 | 21 | .workspace-bottom { 22 | position: absolute; 23 | top: 70%; 24 | bottom: 0; 25 | right: 0; 26 | left: 0; 27 | } 28 | 29 | 30 | .workspace-editor { 31 | position: absolute; 32 | top: .6em; 33 | bottom: .3em; 34 | right: .6em; 35 | left: .6em; 36 | border: 1px solid #375EAB; 37 | background: #FFFFD8; 38 | -webkit-border-top-left-radius: 2px; 39 | -webkit-border-top-right-radius: 2px; 40 | -moz-border-radius-topleft: 2px; 41 | -moz-border-radius-topright: 2px; 42 | border-top-left-radius: 2px; 43 | border-top-right-radius: 2px; 44 | } 45 | 46 | .editor { 47 | position: absolute; 48 | top: 5px; 49 | bottom: 5px; 50 | left: 5px; 51 | right: 5px; 52 | border: none; 53 | outline: none; 54 | resize: none; 55 | background: #FFFFD8; 56 | font-size: 16px; 57 | } 58 | 59 | .output { 60 | position: absolute; 61 | top: .3em; 62 | bottom: .6em; 63 | right: .6em; 64 | left: .6em; 65 | border: 1px solid #375EAB; 66 | background: #FFFFFF; 67 | -webkit-border-bottom-left-radius: 5px; 68 | -webkit-border-bottom-right-radius: 5px; 69 | -moz-border-radius-bottomleft: 5px; 70 | -moz-border-radius-tottomright: 5px; 71 | border-bottom-left-radius: 5px; 72 | border-bottom-right-radius: 5px; 73 | overflow: auto; 74 | } 75 | .output pre { 76 | margin: 5px; 77 | padding: 0; 78 | background: none; 79 | font-size: 16px; 80 | } 81 | .output .loading { 82 | color: #999; 83 | } 84 | .output .stdout, .output pre { 85 | color: black; 86 | } 87 | .output .stderr, .output .error { 88 | color: #a11; 89 | } 90 | .output .system, .output .exit { 91 | color: #375EAB; 92 | } 93 | .output .stderr, .output .err { 94 | color: #a11; 95 | } 96 | .output img { 97 | margin: 5px; 98 | padding: 0; 99 | border: none; 100 | } 101 | 102 | pre, code { 103 | font-family: Menlo, monospace; 104 | font-size: 14px; 105 | border: 0px solid black; 106 | } 107 | 108 | div.source { 109 | display: none; 110 | } 111 | 112 | .CodeMirror { 113 | position: absolute; 114 | top: 0; 115 | bottom: 0; 116 | left: 0; 117 | right: 0; 118 | background: none; 119 | font-family: Menlo, monospace; 120 | } 121 | .CodeMirror { 122 | height: auto; 123 | } 124 | 125 | .CodeMirror-scroll { 126 | height: 100%; 127 | } 128 | 129 | .CodeMirror-gutter { 130 | background: inherit; 131 | border-right: 1px solid #999; 132 | } 133 | 134 | .CodeMirror-gutter-text { 135 | color: #999; 136 | } 137 | 138 | .CodeMirror-matchingbracket { 139 | color: #000 !important; 140 | text-decoration: underline; 141 | } 142 | 143 | .CodeMirror-focused div.CodeMirror-selected { 144 | background: #d0d0d0; 145 | } 146 | 147 | .CodeMirror .errLine { 148 | background: #FDD !important; 149 | } 150 | 151 | 152 | 153 | pre, code { 154 | font-family: Menlo, monospace; 155 | font-size: 14px; 156 | } 157 | 158 | 159 | .controls { 160 | position: absolute; 161 | z-index: 10; 162 | top: 1.2em; 163 | right: 2em; 164 | text-align: right; 165 | } 166 | 167 | .output , .error { 168 | color: #a11; 169 | } 170 | 171 | #brower-not-support{ 172 | margin-top: 100px; 173 | font-size: 200%; 174 | } 175 | 176 | #welcome h1 { 177 | font-size: 96px; 178 | } 179 | 180 | 181 | #welcome p { 182 | font-size: 48px; 183 | line-height:3; 184 | } 185 | 186 | .scala-red { 187 | color:rgb(192, 32, 0); 188 | } 189 | 190 | #logo img{ 191 | margin-right: 4px; 192 | } 193 | 194 | .navbar a { 195 | background: rgba(255,255,255,0); 196 | } 197 | 198 | .navbar .lang { 199 | display: block; 200 | float: right; 201 | padding: 10px 10px 10px; 202 | color: #777777; 203 | text-shadow: 0 1px 0 #ffffff; 204 | } 205 | 206 | .navbar .doc { 207 | display: block; 208 | float: right; 209 | padding: 10px 10px 10px; 210 | 211 | } 212 | 213 | .navbar .doc a{ 214 | color: #777777; 215 | text-shadow: 0 1px 0 #ffffff; 216 | } 217 | 218 | 219 | .slide p{ 220 | font-family: Helvetica, Arial, sans-serif; 221 | font-size: 16px; 222 | line-height: 1.6em; 223 | color: #222; 224 | } 225 | 226 | 227 | .impress-enabled .deeply{ 228 | -webkit-transition: opacity 0s; 229 | -moz-transition: opacity 0s; 230 | -ms-transition: opacity 0s; 231 | -o-transition: opacity 0s; 232 | transition: opacity 0s; 233 | } 234 | .onload{ 235 | opacity:0; 236 | } 237 | 238 | .chapter h2{ 239 | font-size: 120px; 240 | } 241 | 242 | .chapter .btn{ 243 | margin-left: 20px; 244 | margin-top: 150px; 245 | } 246 | 247 | .active .chapter .btn{ 248 | } 249 | 250 | 251 | .step-nav{ 252 | position: absolute; 253 | top: 90%; 254 | bottom: 0; 255 | right: 0; 256 | } 257 | 258 | #designed-by { 259 | position: absolute; 260 | top: 0; 261 | } 262 | 263 | #designed-by a { 264 | background-color: rgba(0, 0, 0, 0); 265 | } 266 | 267 | #compass { 268 | position: absolute; 269 | width: 300px; 270 | top: 0; 271 | bottom: 0; 272 | left: 0; 273 | right: 0; 274 | } 275 | 276 | #about { 277 | font-size: 24px; 278 | line-height: 3; 279 | } 280 | #about li{ 281 | line-height: 2; 282 | } 283 | #about a{ 284 | background: rgba(255,255,255,0); 285 | } 286 | 287 | #using-actor p a{ 288 | background: rgba(255,255,255,0); 289 | } 290 | 291 | .contents h2{ 292 | font-size: 36px; 293 | line-height: 2; 294 | } 295 | 296 | .contents li{ 297 | list-style-image:url('../img/glyphicons_065_tag.png'); 298 | } 299 | .contents .overview-li{ 300 | list-style-image:url('../img/glyphicons_221_unshare.png'); 301 | } 302 | 303 | .contents li{ 304 | line-height: 2; 305 | } 306 | 307 | .contents li a { 308 | text-shadow: 0 2px 2px rgba(0, 0, 0, .1); 309 | font-size: 24px; 310 | color: inherit; 311 | background: rgba(255,255,255,0); 312 | } 313 | 314 | .important{ 315 | font-weight:bold; 316 | } 317 | 318 | #disqus_thread { 319 | height:600px; 320 | overflow: auto; 321 | } 322 | 323 | .deeply h2{ 324 | font-size: 30px; 325 | } -------------------------------------------------------------------------------- /webapp/css/solarized.css: -------------------------------------------------------------------------------- 1 | /* 2 | Solarized theme for code-mirror 3 | http://ethanschoonover.com/solarized 4 | */ 5 | 6 | /* 7 | Solarized color pallet 8 | http://ethanschoonover.com/solarized/img/solarized-palette.png 9 | */ 10 | 11 | .solarized.base03 { color: #002b36; } 12 | .solarized.base02 { color: #073642; } 13 | .solarized.base01 { color: #586e75; } 14 | .solarized.base00 { color: #657b83; } 15 | .solarized.base0 { color: #839496; } 16 | .solarized.base1 { color: #93a1a1; } 17 | .solarized.base2 { color: #eee8d5; } 18 | .solarized.base3 { color: #fdf6e3; } 19 | .solarized.solar-yellow { color: #b58900; } 20 | .solarized.solar-orange { color: #cb4b16; } 21 | .solarized.solar-red { color: #dc322f; } 22 | .solarized.solar-magenta { color: #d33682; } 23 | .solarized.solar-violet { color: #6c71c4; } 24 | .solarized.solar-blue { color: #268bd2; } 25 | .solarized.solar-cyan { color: #2aa198; } 26 | .solarized.solar-green { color: #859900; } 27 | 28 | /* Color scheme for code-mirror */ 29 | 30 | .cm-s-solarized { 31 | line-height: 1.45em; 32 | font-family: Menlo,Monaco,"Andale Mono","lucida console","Courier New",monospace !important; 33 | color-profile: sRGB; 34 | rendering-intent: auto; 35 | } 36 | .cm-s-solarized.cm-s-dark { 37 | color: #839496; 38 | background-color: #002b36; 39 | text-shadow: #002b36 0 1px; 40 | } 41 | .cm-s-solarized.cm-s-light { 42 | background-color: #FFFFD8; 43 | color: #657b83; 44 | text-shadow: #eee8d5 0 1px; 45 | } 46 | 47 | .cm-s-solarized .CodeMirror-widget { 48 | text-shadow: none; 49 | } 50 | 51 | 52 | .cm-s-solarized .cm-keyword { color: #cb4b16 } 53 | .cm-s-solarized .cm-atom { color: #d33682; } 54 | .cm-s-solarized .cm-number { color: #d33682; } 55 | .cm-s-solarized .cm-def { color: #2aa198; } 56 | 57 | .cm-s-solarized .cm-variable { color: #268bd2; } 58 | .cm-s-solarized .cm-variable-2 { color: #b58900; } 59 | .cm-s-solarized .cm-variable-3 { color: #6c71c4; } 60 | 61 | .cm-s-solarized .cm-property { color: #2aa198; } 62 | .cm-s-solarized .cm-operator {color: #6c71c4;} 63 | 64 | .cm-s-solarized .cm-comment { color: #586e75; font-style:italic; } 65 | 66 | .cm-s-solarized .cm-string { color: #859900; } 67 | .cm-s-solarized .cm-string-2 { color: #b58900; } 68 | 69 | .cm-s-solarized .cm-meta { color: #859900; } 70 | .cm-s-solarized .cm-error, 71 | .cm-s-solarized .cm-invalidchar { 72 | color: #586e75; 73 | border-bottom: 1px dotted #dc322f; 74 | } 75 | .cm-s-solarized .cm-qualifier { color: #b58900; } 76 | .cm-s-solarized .cm-builtin { color: #d33682; } 77 | .cm-s-solarized .cm-bracket { color: #cb4b16; } 78 | .cm-s-solarized .CodeMirror-matchingbracket { color: #859900; } 79 | .cm-s-solarized .CodeMirror-nonmatchingbracket { color: #dc322f; } 80 | .cm-s-solarized .cm-tag { color: #93a1a1 } 81 | .cm-s-solarized .cm-attribute { color: #2aa198; } 82 | .cm-s-solarized .cm-header { color: #586e75; } 83 | .cm-s-solarized .cm-quote { color: #93a1a1; } 84 | .cm-s-solarized .cm-hr { 85 | color: transparent; 86 | border-top: 1px solid #586e75; 87 | display: block; 88 | } 89 | .cm-s-solarized .cm-link { color: #93a1a1; cursor: pointer; } 90 | .cm-s-solarized .cm-special { color: #6c71c4; } 91 | .cm-s-solarized .cm-em { 92 | color: #999; 93 | text-decoration: underline; 94 | text-decoration-style: dotted; 95 | } 96 | .cm-s-solarized .cm-strong { color: #eee; } 97 | .cm-s-solarized .cm-tab:before { 98 | content: "➤"; /*visualize tab character*/ 99 | color: #586e75; 100 | } 101 | 102 | .cm-s-solarized.cm-s-dark .CodeMirror-focused .CodeMirror-selected { 103 | background: #386774; 104 | color: inherit; 105 | } 106 | 107 | .cm-s-solarized.cm-s-dark ::selection { 108 | background: #386774; 109 | color: inherit; 110 | } 111 | 112 | .cm-s-solarized.cm-s-dark .CodeMirror-selected { 113 | background: #586e75; 114 | } 115 | 116 | .cm-s-solarized.cm-s-light .CodeMirror-focused .CodeMirror-selected { 117 | background: #eee8d5; 118 | color: inherit; 119 | } 120 | 121 | .cm-s-solarized.cm-s-light ::selection { 122 | background: #eee8d5; 123 | color: inherit; 124 | } 125 | 126 | .cm-s-solarized.cm-s-light .CodeMirror-selected { 127 | background: #93a1a1; 128 | } 129 | 130 | 131 | 132 | /* Editor styling */ 133 | 134 | 135 | 136 | /* Little shadow on the view-port of the buffer view */ 137 | .cm-s-solarized.CodeMirror { 138 | -moz-box-shadow: inset 7px 0 12px -6px #000; 139 | -webkit-box-shadow: inset 7px 0 12px -6px #000; 140 | box-shadow: inset 7px 0 12px -6px #000; 141 | } 142 | 143 | /* Gutter border and some shadow from it */ 144 | /*.cm-s-solarized .CodeMirror-gutters { 145 | padding: 0 15px 0 10px; 146 | box-shadow: 0 10px 20px black; 147 | border-right: 1px solid;*/ 148 | } 149 | */ 150 | /* Gutter colors and line number styling based of color scheme (dark / light) */ 151 | 152 | /* Dark */ 153 | .cm-s-solarized.cm-s-dark .CodeMirror-gutters { 154 | background-color: #073642; 155 | border-color: #00232c; 156 | } 157 | 158 | .cm-s-solarized.cm-s-dark .CodeMirror-linenumber { 159 | text-shadow: #021014 0 -1px; 160 | } 161 | 162 | /* Light */ 163 | /*.cm-s-solarized.cm-s-light .CodeMirror-gutters { 164 | background-color: #eee8d5; 165 | border-color: #eee8d5; 166 | }*/ 167 | 168 | /* Common */ 169 | .cm-s-solarized .CodeMirror-linenumber { 170 | color: #586e75; 171 | } 172 | 173 | .cm-s-solarized .CodeMirror-gutter .CodeMirror-gutter-text { 174 | color: #586e75; 175 | } 176 | 177 | .cm-s-solarized .CodeMirror-lines { 178 | padding-left: 5px; 179 | } 180 | 181 | .cm-s-solarized .CodeMirror-lines .CodeMirror-cursor { 182 | border-left: 1px solid #819090; 183 | } 184 | 185 | /* 186 | Active line. Negative margin compensates left padding of the text in the 187 | view-port 188 | */ 189 | .cm-s-solarized .activeline { 190 | margin-left: -20px; 191 | } 192 | 193 | .cm-s-solarized.cm-s-dark .activeline { 194 | background: rgba(255, 255, 255, 0.05); 195 | 196 | } 197 | .cm-s-solarized.cm-s-light .activeline { 198 | background: rgba(0, 0, 0, 0.05); 199 | } 200 | 201 | -------------------------------------------------------------------------------- /webapp/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankay/scala-tour/7a482eea52e1ee429cb5961cb7ee5272227b44b1/webapp/favicon.png -------------------------------------------------------------------------------- /webapp/img/Lambda_lc.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankay/scala-tour/7a482eea52e1ee429cb5961cb7ee5272227b44b1/webapp/img/Lambda_lc.svg.png -------------------------------------------------------------------------------- /webapp/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankay/scala-tour/7a482eea52e1ee429cb5961cb7ee5272227b44b1/webapp/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /webapp/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankay/scala-tour/7a482eea52e1ee429cb5961cb7ee5272227b44b1/webapp/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /webapp/img/glyphicons_065_tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankay/scala-tour/7a482eea52e1ee429cb5961cb7ee5272227b44b1/webapp/img/glyphicons_065_tag.png -------------------------------------------------------------------------------- /webapp/img/glyphicons_221_unshare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankay/scala-tour/7a482eea52e1ee429cb5961cb7ee5272227b44b1/webapp/img/glyphicons_221_unshare.png -------------------------------------------------------------------------------- /webapp/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankay/scala-tour/7a482eea52e1ee429cb5961cb7ee5272227b44b1/webapp/img/logo.png -------------------------------------------------------------------------------- /webapp/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2012 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||s.toggleClass("open"),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e.fn[this.type].defaults,r={},i;this._options&&e.each(this._options,function(e,t){n[e]!=t&&(r[e]=t)},this),i=e(t.currentTarget)[this.type](r).data(this.type);if(!i.options.delay||!i.options.delay.show)return i.show();clearTimeout(this.timeout),i.hoverState="in",this.timeout=setTimeout(function(){i.hoverState=="in"&&i.show()},i.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},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(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'

'}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),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=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length"+t+""})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;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(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;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()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'',item:'
  • ',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery); -------------------------------------------------------------------------------- /webapp/js/clike.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("clike", function(config, parserConfig) { 2 | var indentUnit = config.indentUnit, 3 | statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, 4 | dontAlignCalls = parserConfig.dontAlignCalls, 5 | keywords = parserConfig.keywords || {}, 6 | builtin = parserConfig.builtin || {}, 7 | blockKeywords = parserConfig.blockKeywords || {}, 8 | atoms = parserConfig.atoms || {}, 9 | hooks = parserConfig.hooks || {}, 10 | multiLineStrings = parserConfig.multiLineStrings; 11 | var isOperatorChar = /[+\-*&%=<>!?|\/]/; 12 | 13 | var curPunc; 14 | 15 | function tokenBase(stream, state) { 16 | var ch = stream.next(); 17 | if (hooks[ch]) { 18 | var result = hooks[ch](stream, state); 19 | if (result !== false) return result; 20 | } 21 | if (ch == '"' || ch == "'") { 22 | state.tokenize = tokenString(ch); 23 | return state.tokenize(stream, state); 24 | } 25 | if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 26 | curPunc = ch; 27 | return null; 28 | } 29 | if (/\d/.test(ch)) { 30 | stream.eatWhile(/[\w\.]/); 31 | return "number"; 32 | } 33 | if (ch == "/") { 34 | if (stream.eat("*")) { 35 | state.tokenize = tokenComment; 36 | return tokenComment(stream, state); 37 | } 38 | if (stream.eat("/")) { 39 | stream.skipToEnd(); 40 | return "comment"; 41 | } 42 | } 43 | if (isOperatorChar.test(ch)) { 44 | stream.eatWhile(isOperatorChar); 45 | return "operator"; 46 | } 47 | stream.eatWhile(/[\w\$_]/); 48 | var cur = stream.current(); 49 | if (keywords.propertyIsEnumerable(cur)) { 50 | if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; 51 | return "keyword"; 52 | } 53 | if (builtin.propertyIsEnumerable(cur)) { 54 | if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; 55 | return "builtin"; 56 | } 57 | if (atoms.propertyIsEnumerable(cur)) return "atom"; 58 | return "variable"; 59 | } 60 | 61 | function tokenString(quote) { 62 | return function(stream, state) { 63 | var escaped = false, next, end = false; 64 | while ((next = stream.next()) != null) { 65 | if (next == quote && !escaped) {end = true; break;} 66 | escaped = !escaped && next == "\\"; 67 | } 68 | if (end || !(escaped || multiLineStrings)) 69 | state.tokenize = null; 70 | return "string"; 71 | }; 72 | } 73 | 74 | function tokenComment(stream, state) { 75 | var maybeEnd = false, ch; 76 | while (ch = stream.next()) { 77 | if (ch == "/" && maybeEnd) { 78 | state.tokenize = null; 79 | break; 80 | } 81 | maybeEnd = (ch == "*"); 82 | } 83 | return "comment"; 84 | } 85 | 86 | function Context(indented, column, type, align, prev) { 87 | this.indented = indented; 88 | this.column = column; 89 | this.type = type; 90 | this.align = align; 91 | this.prev = prev; 92 | } 93 | function pushContext(state, col, type) { 94 | var indent = state.indented; 95 | if (state.context && state.context.type == "statement") 96 | indent = state.context.indented; 97 | return state.context = new Context(indent, col, type, null, state.context); 98 | } 99 | function popContext(state) { 100 | var t = state.context.type; 101 | if (t == ")" || t == "]" || t == "}") 102 | state.indented = state.context.indented; 103 | return state.context = state.context.prev; 104 | } 105 | 106 | // Interface 107 | 108 | return { 109 | startState: function(basecolumn) { 110 | return { 111 | tokenize: null, 112 | context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), 113 | indented: 0, 114 | startOfLine: true 115 | }; 116 | }, 117 | 118 | token: function(stream, state) { 119 | var ctx = state.context; 120 | if (stream.sol()) { 121 | if (ctx.align == null) ctx.align = false; 122 | state.indented = stream.indentation(); 123 | state.startOfLine = true; 124 | } 125 | if (stream.eatSpace()) return null; 126 | curPunc = null; 127 | var style = (state.tokenize || tokenBase)(stream, state); 128 | if (style == "comment" || style == "meta") return style; 129 | if (ctx.align == null) ctx.align = true; 130 | 131 | if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement") popContext(state); 132 | else if (curPunc == "{") pushContext(state, stream.column(), "}"); 133 | else if (curPunc == "[") pushContext(state, stream.column(), "]"); 134 | else if (curPunc == "(") pushContext(state, stream.column(), ")"); 135 | else if (curPunc == "}") { 136 | while (ctx.type == "statement") ctx = popContext(state); 137 | if (ctx.type == "}") ctx = popContext(state); 138 | while (ctx.type == "statement") ctx = popContext(state); 139 | } 140 | else if (curPunc == ctx.type) popContext(state); 141 | else if (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || (ctx.type == "statement" && curPunc == "newstatement")) 142 | pushContext(state, stream.column(), "statement"); 143 | state.startOfLine = false; 144 | return style; 145 | }, 146 | 147 | indent: function(state, textAfter) { 148 | if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; 149 | var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); 150 | if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; 151 | var closing = firstChar == ctx.type; 152 | if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); 153 | else if (dontAlignCalls && ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit; 154 | else if (ctx.align) return ctx.column + (closing ? 0 : 1); 155 | else return ctx.indented + (closing ? 0 : indentUnit); 156 | }, 157 | 158 | electricChars: "{}" 159 | }; 160 | }); 161 | 162 | (function() { 163 | function words(str) { 164 | var obj = {}, words = str.split(" "); 165 | for (var i = 0; i < words.length; ++i) obj[words[i]] = true; 166 | return obj; 167 | } 168 | var cKeywords = "auto if break int case long char register continue return default short do sizeof " + 169 | "double static else struct entry switch extern typedef float union for unsigned " + 170 | "goto while enum void const signed volatile"; 171 | 172 | function cppHook(stream, state) { 173 | if (!state.startOfLine) return false; 174 | for (;;) { 175 | if (stream.skipTo("\\")) { 176 | stream.next(); 177 | if (stream.eol()) { 178 | state.tokenize = cppHook; 179 | break; 180 | } 181 | } else { 182 | stream.skipToEnd(); 183 | state.tokenize = null; 184 | break; 185 | } 186 | } 187 | return "meta"; 188 | } 189 | 190 | // C#-style strings where "" escapes a quote. 191 | function tokenAtString(stream, state) { 192 | var next; 193 | while ((next = stream.next()) != null) { 194 | if (next == '"' && !stream.eat('"')) { 195 | state.tokenize = null; 196 | break; 197 | } 198 | } 199 | return "string"; 200 | } 201 | 202 | function mimes(ms, mode) { 203 | for (var i = 0; i < ms.length; ++i) CodeMirror.defineMIME(ms[i], mode); 204 | } 205 | 206 | mimes(["text/x-csrc", "text/x-c", "text/x-chdr"], { 207 | name: "clike", 208 | keywords: words(cKeywords), 209 | blockKeywords: words("case do else for if switch while struct"), 210 | atoms: words("null"), 211 | hooks: {"#": cppHook} 212 | }); 213 | mimes(["text/x-c++src", "text/x-c++hdr"], { 214 | name: "clike", 215 | keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " + 216 | "static_cast typeid catch operator template typename class friend private " + 217 | "this using const_cast inline public throw virtual delete mutable protected " + 218 | "wchar_t"), 219 | blockKeywords: words("catch class do else finally for if struct switch try while"), 220 | atoms: words("true false null"), 221 | hooks: {"#": cppHook} 222 | }); 223 | CodeMirror.defineMIME("text/x-java", { 224 | name: "clike", 225 | keywords: words("abstract assert boolean break byte case catch char class const continue default " + 226 | "do double else enum extends final finally float for goto if implements import " + 227 | "instanceof int interface long native new package private protected public " + 228 | "return short static strictfp super switch synchronized this throw throws transient " + 229 | "try void volatile while"), 230 | blockKeywords: words("catch class do else finally for if switch try while"), 231 | atoms: words("true false null"), 232 | hooks: { 233 | "@": function(stream) { 234 | stream.eatWhile(/[\w\$_]/); 235 | return "meta"; 236 | } 237 | } 238 | }); 239 | CodeMirror.defineMIME("text/x-csharp", { 240 | name: "clike", 241 | keywords: words("abstract as base break case catch checked class const continue" + 242 | " default delegate do else enum event explicit extern finally fixed for" + 243 | " foreach goto if implicit in interface internal is lock namespace new" + 244 | " operator out override params private protected public readonly ref return sealed" + 245 | " sizeof stackalloc static struct switch this throw try typeof unchecked" + 246 | " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + 247 | " global group into join let orderby partial remove select set value var yield"), 248 | blockKeywords: words("catch class do else finally for foreach if struct switch try while"), 249 | builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" + 250 | " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" + 251 | " UInt64 bool byte char decimal double short int long object" + 252 | " sbyte float string ushort uint ulong"), 253 | atoms: words("true false null"), 254 | hooks: { 255 | "@": function(stream, state) { 256 | if (stream.eat('"')) { 257 | state.tokenize = tokenAtString; 258 | return tokenAtString(stream, state); 259 | } 260 | stream.eatWhile(/[\w\$_]/); 261 | return "meta"; 262 | } 263 | } 264 | }); 265 | CodeMirror.defineMIME("text/x-scala", { 266 | name: "clike", 267 | keywords: words( 268 | 269 | /* scala */ 270 | "abstract case catch class def do else extends false final finally for forSome if " + 271 | "implicit import lazy match new null object override package private protected return " + 272 | "sealed super this throw trait try trye type val var while with yield _ : = => <- <: " + 273 | "<% >: # @ " + 274 | 275 | /* package scala */ 276 | "assert assume require print println printf readLine readBoolean readByte readShort " + 277 | "readChar readInt readLong readFloat readDouble " + 278 | 279 | "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " + 280 | "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " + 281 | "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " + 282 | "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " + 283 | "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector :: #:: " + 284 | 285 | /* package java.lang */ 286 | "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + 287 | "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + 288 | "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + 289 | "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void" 290 | 291 | 292 | ), 293 | blockKeywords: words("catch class do else finally for forSome if match switch try while"), 294 | atoms: words("true false null"), 295 | hooks: { 296 | "@": function(stream) { 297 | stream.eatWhile(/[\w\$_]/); 298 | return "meta"; 299 | } 300 | } 301 | }); 302 | }()); 303 | -------------------------------------------------------------------------------- /webapp/js/go.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("go", function(config) { 2 | var indentUnit = config.indentUnit; 3 | 4 | var keywords = { 5 | "break":true, "case":true, "chan":true, "const":true, "continue":true, 6 | "default":true, "defer":true, "else":true, "fallthrough":true, "for":true, 7 | "func":true, "go":true, "goto":true, "if":true, "import":true, 8 | "interface":true, "map":true, "package":true, "range":true, "return":true, 9 | "select":true, "struct":true, "switch":true, "type":true, "var":true, 10 | "bool":true, "byte":true, "complex64":true, "complex128":true, 11 | "float32":true, "float64":true, "int8":true, "int16":true, "int32":true, 12 | "int64":true, "string":true, "uint8":true, "uint16":true, "uint32":true, 13 | "uint64":true, "int":true, "uint":true, "uintptr":true 14 | }; 15 | 16 | var atoms = { 17 | "true":true, "false":true, "iota":true, "nil":true, "append":true, 18 | "cap":true, "close":true, "complex":true, "copy":true, "imag":true, 19 | "len":true, "make":true, "new":true, "panic":true, "print":true, 20 | "println":true, "real":true, "recover":true 21 | }; 22 | 23 | var isOperatorChar = /[+\-*&^%:=<>!|\/]/; 24 | 25 | var curPunc; 26 | 27 | function tokenBase(stream, state) { 28 | var ch = stream.next(); 29 | if (ch == '"' || ch == "'" || ch == "`") { 30 | state.tokenize = tokenString(ch); 31 | return state.tokenize(stream, state); 32 | } 33 | if (/[\d\.]/.test(ch)) { 34 | if (ch == ".") { 35 | stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/); 36 | } else if (ch == "0") { 37 | stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/); 38 | } else { 39 | stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/); 40 | } 41 | return "number"; 42 | } 43 | if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 44 | curPunc = ch; 45 | return null; 46 | } 47 | if (ch == "/") { 48 | if (stream.eat("*")) { 49 | state.tokenize = tokenComment; 50 | return tokenComment(stream, state); 51 | } 52 | if (stream.eat("/")) { 53 | stream.skipToEnd(); 54 | return "comment"; 55 | } 56 | } 57 | if (isOperatorChar.test(ch)) { 58 | stream.eatWhile(isOperatorChar); 59 | return "operator"; 60 | } 61 | stream.eatWhile(/[\w\$_]/); 62 | var cur = stream.current(); 63 | if (keywords.propertyIsEnumerable(cur)) { 64 | if (cur == "case" || cur == "default") curPunc = "case"; 65 | return "keyword"; 66 | } 67 | if (atoms.propertyIsEnumerable(cur)) return "atom"; 68 | return "variable"; 69 | } 70 | 71 | function tokenString(quote) { 72 | return function(stream, state) { 73 | var escaped = false, next, end = false; 74 | while ((next = stream.next()) != null) { 75 | if (next == quote && !escaped) {end = true; break;} 76 | escaped = !escaped && next == "\\"; 77 | } 78 | if (end || !(escaped || quote == "`")) 79 | state.tokenize = tokenBase; 80 | return "string"; 81 | }; 82 | } 83 | 84 | function tokenComment(stream, state) { 85 | var maybeEnd = false, ch; 86 | while (ch = stream.next()) { 87 | if (ch == "/" && maybeEnd) { 88 | state.tokenize = tokenBase; 89 | break; 90 | } 91 | maybeEnd = (ch == "*"); 92 | } 93 | return "comment"; 94 | } 95 | 96 | function Context(indented, column, type, align, prev) { 97 | this.indented = indented; 98 | this.column = column; 99 | this.type = type; 100 | this.align = align; 101 | this.prev = prev; 102 | } 103 | function pushContext(state, col, type) { 104 | return state.context = new Context(state.indented, col, type, null, state.context); 105 | } 106 | function popContext(state) { 107 | var t = state.context.type; 108 | if (t == ")" || t == "]" || t == "}") 109 | state.indented = state.context.indented; 110 | return state.context = state.context.prev; 111 | } 112 | 113 | // Interface 114 | 115 | return { 116 | startState: function(basecolumn) { 117 | return { 118 | tokenize: null, 119 | context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), 120 | indented: 0, 121 | startOfLine: true 122 | }; 123 | }, 124 | 125 | token: function(stream, state) { 126 | var ctx = state.context; 127 | if (stream.sol()) { 128 | if (ctx.align == null) ctx.align = false; 129 | state.indented = stream.indentation(); 130 | state.startOfLine = true; 131 | if (ctx.type == "case") ctx.type = "}"; 132 | } 133 | if (stream.eatSpace()) return null; 134 | curPunc = null; 135 | var style = (state.tokenize || tokenBase)(stream, state); 136 | if (style == "comment") return style; 137 | if (ctx.align == null) ctx.align = true; 138 | 139 | if (curPunc == "{") pushContext(state, stream.column(), "}"); 140 | else if (curPunc == "[") pushContext(state, stream.column(), "]"); 141 | else if (curPunc == "(") pushContext(state, stream.column(), ")"); 142 | else if (curPunc == "case") ctx.type = "case"; 143 | else if (curPunc == "}" && ctx.type == "}") ctx = popContext(state); 144 | else if (curPunc == ctx.type) popContext(state); 145 | state.startOfLine = false; 146 | return style; 147 | }, 148 | 149 | indent: function(state, textAfter) { 150 | if (state.tokenize != tokenBase && state.tokenize != null) return 0; 151 | var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); 152 | if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) { 153 | state.context.type = "}"; 154 | return ctx.indented; 155 | } 156 | var closing = firstChar == ctx.type; 157 | if (ctx.align) return ctx.column + (closing ? 0 : 1); 158 | else return ctx.indented + (closing ? 0 : indentUnit); 159 | }, 160 | 161 | electricChars: "{}:" 162 | }; 163 | }); 164 | 165 | CodeMirror.defineMIME("text/x-go", "go"); 166 | -------------------------------------------------------------------------------- /webapp/js/matchbrackets.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var ie_lt8 = /MSIE \d/.test(navigator.userAgent) && 3 | (document.documentMode == null || document.documentMode < 8); 4 | 5 | var Pos = CodeMirror.Pos; 6 | // Disable brace matching in long lines, since it'll cause hugely slow updates 7 | var maxLineLen = 1000; 8 | 9 | var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; 10 | function findMatchingBracket(cm) { 11 | var cur = cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1; 12 | var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; 13 | if (!match) return null; 14 | var forward = match.charAt(1) == ">", d = forward ? 1 : -1; 15 | var style = cm.getTokenAt(Pos(cur.line, pos + 1)).type; 16 | 17 | var stack = [line.text.charAt(pos)], re = /[(){}[\]]/; 18 | function scan(line, lineNo, start) { 19 | if (!line.text) return; 20 | var pos = forward ? 0 : line.text.length - 1, end = forward ? line.text.length : -1; 21 | if (start != null) pos = start + d; 22 | for (; pos != end; pos += d) { 23 | var ch = line.text.charAt(pos); 24 | if (re.test(ch) && cm.getTokenAt(Pos(lineNo, pos + 1)).type == style) { 25 | var match = matching[ch]; 26 | if (match.charAt(1) == ">" == forward) stack.push(ch); 27 | else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false}; 28 | else if (!stack.length) return {pos: pos, match: true}; 29 | } 30 | } 31 | } 32 | for (var i = cur.line, found, e = forward ? Math.min(i + 100, cm.lineCount()) : Math.max(-1, i - 100); i != e; i+=d) { 33 | if (i == cur.line) found = scan(line, i, pos); 34 | else found = scan(cm.getLineHandle(i), i); 35 | if (found) break; 36 | } 37 | return {from: Pos(cur.line, pos), to: found && Pos(i, found.pos), match: found && found.match}; 38 | } 39 | 40 | function matchBrackets(cm, autoclear) { 41 | var found = findMatchingBracket(cm); 42 | if (!found || cm.getLine(found.from.line).length > maxLineLen || 43 | found.to && cm.getLine(found.to.line).length > maxLineLen) 44 | return; 45 | 46 | var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; 47 | var one = cm.markText(found.from, Pos(found.from.line, found.from.ch + 1), {className: style}); 48 | var two = found.to && cm.markText(found.to, Pos(found.to.line, found.to.ch + 1), {className: style}); 49 | // Kludge to work around the IE bug from issue #1193, where text 50 | // input stops going to the textare whever this fires. 51 | if (ie_lt8 && cm.state.focused) cm.display.input.focus(); 52 | var clear = function() { 53 | cm.operation(function() { one.clear(); two && two.clear(); }); 54 | }; 55 | if (autoclear) setTimeout(clear, 800); 56 | else return clear; 57 | } 58 | 59 | var currentlyHighlighted = null; 60 | function doMatchBrackets(cm) { 61 | cm.operation(function() { 62 | if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;} 63 | if (!cm.somethingSelected()) currentlyHighlighted = matchBrackets(cm, false); 64 | }); 65 | } 66 | 67 | CodeMirror.defineOption("matchBrackets", false, function(cm, val) { 68 | if (val) cm.on("cursorActivity", doMatchBrackets); 69 | else cm.off("cursorActivity", doMatchBrackets); 70 | }); 71 | 72 | CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);}); 73 | CodeMirror.defineExtension("findMatchingBracket", function(){return findMatchingBracket(this);}); 74 | })(); 75 | -------------------------------------------------------------------------------- /webapp/js/scala-tour.js: -------------------------------------------------------------------------------- 1 | function loading(outputDiv) { 2 | // $(".output").html('
    '+('waiting')+'
    '); 3 | outputDiv.html('
    ' + ('waiting') + '
    '); 4 | } 5 | 6 | function runFunc(codeStr, outputDiv) { 7 | $.ajax("/run?code=" + encodeURIComponent(codeStr), { 8 | type: "GET", 9 | dataType: "json", 10 | success: function(data) { 11 | if (!data) { 12 | return; 13 | } 14 | if (data.Errors && data.Errors.length > 0) { 15 | setOutput(outputDiv, null, null, data.Errors); 16 | return; 17 | } 18 | setOutput(outputDiv, data.Events, data.ErrEvents, false); 19 | }, 20 | error: function() { 21 | outputDiv.addClass("error").text( 22 | "Error communicating with remote server."); 23 | } 24 | }); 25 | } 26 | 27 | function setOutput(output, events, errevents, error) { 28 | output.empty(); 29 | if (events) { 30 | 31 | var msg = "" 32 | for (var i = 0; i < events.length; i++) { 33 | msg += events[i] + "\n" 34 | } 35 | output.text(msg); 36 | 37 | msg = "" 38 | for (var i = 0; i < errevents.length; i++) { 39 | msg += errevents[i] + "\n" 40 | } 41 | 42 | if (msg != "") { 43 | var err = $(''); 44 | err.text(msg); 45 | err.appendTo(output); 46 | } 47 | 48 | var exit = $(''); 49 | exit.text("\nProgram exited."); 50 | exit.appendTo(output); 51 | } 52 | // Display errors. 53 | if (error) { 54 | var errorText = "" 55 | for (var i = 0; i < error.length; i++) { 56 | errorText += error[i] + "\n" 57 | } 58 | output.addClass("error").text(errorText); 59 | } 60 | } 61 | 62 | var editors = [] 63 | var editorsMap = {} 64 | 65 | $(".run").click(function() { 66 | var editorText = $(this).parent().parent().parent().find(".editor"); 67 | var outputDiv = $(this).parent().parent().parent().find(".output"); 68 | loading(outputDiv); 69 | var editor = editorsMap[getElementPath(editorText)] 70 | runFunc(editor.getValue(), outputDiv.find("pre")); 71 | }); 72 | 73 | function getElementPath(element) { 74 | return "//" + $(element).parents().andSelf().map(function() { 75 | var $this = $(this); 76 | var tagName = this.nodeName; 77 | if ($this.siblings(tagName).length > 0) { 78 | tagName += "[" + $this.prevAll(tagName).length + "]"; 79 | } 80 | return tagName; 81 | }).get().join("/").toUpperCase(); 82 | } 83 | 84 | 85 | 86 | document.addEventListener("impress:add-active", function(event) { 87 | //restore 88 | var editorsRemove=[] 89 | 90 | for(var i = 0; i < editors.length; i++){ 91 | var editor=editors[i] 92 | editorsRemove.push(editor) 93 | } 94 | editors = [] 95 | editorsMap = {} 96 | //add 97 | var textareas = $(".active .editor").get() 98 | for (var i = 0; i < textareas.length; i++) { 99 | var textarea = textareas[i] 100 | var editor = CodeMirror.fromTextArea(textarea, { 101 | theme: "solarized light", 102 | matchBrackets: true, 103 | indentUnit: 2, 104 | tabSize: 2, 105 | indentWithTabs: false, 106 | mode: "text/x-scala", 107 | smartIndent :false, 108 | lineNumbers: false 109 | }); 110 | editors.push(editor) 111 | editorsMap[getElementPath(textarea)]=editor 112 | } 113 | 114 | for(var i = 0; i < editorsRemove.length; i++){ 115 | var editor=editorsRemove[i] 116 | window.setTimeout(function() { 117 | editor.save() 118 | editor.toTextArea() 119 | }, 1000 /* but after 2000 ms */); 120 | } 121 | 122 | 123 | }, false); 124 | 125 | 126 | document.getElementById("body").className = document.getElementById("body").className.replace('onload', ' '); 127 | impress().init(); 128 | 129 | 130 | 131 | // var textareas = document.getElementsByClassName("editor") 132 | 133 | 134 | // $(window).load(function() { 135 | 136 | 137 | 138 | 139 | 140 | // }); --------------------------------------------------------------------------------