summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-07-27 20:36:52 +0800
committerLi Haoyi <haoyi.sg@gmail.com>2018-07-27 20:44:11 +0800
commit755eb8cd839ef1c34886db12056ac1aa56c0caaa (patch)
tree0d366a7aa0276f2315642c19550f00839d1c49ce
parent483fe1725cbf6c29f349bdcfd1169ca3fb561210 (diff)
downloadcask-755eb8cd839ef1c34886db12056ac1aa56c0caaa.tar.gz
cask-755eb8cd839ef1c34886db12056ac1aa56c0caaa.tar.bz2
cask-755eb8cd839ef1c34886db12056ac1aa56c0caaa.zip
WIP get a todo example working
-rw-r--r--build.sc6
-rw-r--r--cask/test/src/test/cask/FormJsonPost.scala5
-rw-r--r--cask/test/src/test/cask/MultipartFileUploads.scala2
-rw-r--r--example/todo/resources/todo/app.js35
-rw-r--r--example/todo/resources/todo/base.css141
-rw-r--r--example/todo/resources/todo/index.css378
-rw-r--r--example/todo/src/todo/Server.scala105
7 files changed, 668 insertions, 4 deletions
diff --git a/build.sc b/build.sc
index be26aa4..36c4020 100644
--- a/build.sc
+++ b/build.sc
@@ -24,3 +24,9 @@ object cask extends ScalaModule{
)
}
}
+object example extends Module{
+ object todo extends ScalaModule{
+ def scalaVersion = "2.12.6"
+ def moduleDeps = Seq(cask)
+ }
+} \ No newline at end of file
diff --git a/cask/test/src/test/cask/FormJsonPost.scala b/cask/test/src/test/cask/FormJsonPost.scala
index 2874a52..0be4480 100644
--- a/cask/test/src/test/cask/FormJsonPost.scala
+++ b/cask/test/src/test/cask/FormJsonPost.scala
@@ -1,16 +1,15 @@
package test.cask
import cask.FormValue
-import io.undertow.server.HttpServerExchange
object FormJsonPost extends cask.MainRoutes{
@cask.postJson("/json")
- def jsonEndpoint(x: HttpServerExchange, value1: ujson.Js.Value, value2: Seq[Int]) = {
+ def jsonEndpoint(value1: ujson.Js.Value, value2: Seq[Int]) = {
"OK " + value1 + " " + value2
}
@cask.postForm("/form")
- def formEndpoint(x: HttpServerExchange, value1: FormValue, value2: Seq[Int]) = {
+ def formEndpoint(value1: FormValue, value2: Seq[Int]) = {
"OK " + value1 + " " + value2
}
diff --git a/cask/test/src/test/cask/MultipartFileUploads.scala b/cask/test/src/test/cask/MultipartFileUploads.scala
index 6b4f692..11b6ec6 100644
--- a/cask/test/src/test/cask/MultipartFileUploads.scala
+++ b/cask/test/src/test/cask/MultipartFileUploads.scala
@@ -6,7 +6,7 @@ import io.undertow.server.handlers.form.FormData
object MultipartFileUploads extends cask.MainRoutes{
// curl -F "image=@build.sc" localhost:8080/upload
@cask.post("/upload")
- def uploadFile(exchange: HttpServerExchange, formData: FormData) = {
+ def uploadFile(formData: FormData) = {
val file = formData.getFirst("image")
file.getFileName
}
diff --git a/example/todo/resources/todo/app.js b/example/todo/resources/todo/app.js
new file mode 100644
index 0000000..df8dc7a
--- /dev/null
+++ b/example/todo/resources/todo/app.js
@@ -0,0 +1,35 @@
+var state = "all";
+var newTodoInput = document.getElementsByClassName("new-todo")[0];
+var todoList = document.getElementsByClassName("todo-list")[0];
+newTodoInput.addEventListener(
+ "keydown",
+ function(evt){
+ if (evt.keyCode === 13) {
+ fetch("/add/" + state, {
+ method: "POST",
+ body: newTodoInput.value
+ })
+ .then(function(response){ return response.text()})
+ .then(function (text) {
+ newTodoInput.value = "";
+ todoList.innerHTML = text;
+ })
+ }
+ }
+);
+newTodoInput.addEventListener(
+ "mousedown",
+ function(evt){
+ if (evt.keyCode === 13) {
+ fetch("/add/" + state, {
+ method: "POST",
+ body: newTodoInput.value
+ })
+ .then(function(response){ return response.text()})
+ .then(function (text) {
+ newTodoInput.value = "";
+ todoList.innerHTML = text;
+ })
+ }
+ }
+); \ No newline at end of file
diff --git a/example/todo/resources/todo/base.css b/example/todo/resources/todo/base.css
new file mode 100644
index 0000000..deec144
--- /dev/null
+++ b/example/todo/resources/todo/base.css
@@ -0,0 +1,141 @@
+hr {
+ margin: 20px 0;
+ border: 0;
+ border-top: 1px dashed #c5c5c5;
+ border-bottom: 1px dashed #f7f7f7;
+}
+
+.learn a {
+ font-weight: normal;
+ text-decoration: none;
+ color: #b83f45;
+}
+
+.learn a:hover {
+ text-decoration: underline;
+ color: #787e7e;
+}
+
+.learn h3,
+.learn h4,
+.learn h5 {
+ margin: 10px 0;
+ font-weight: 500;
+ line-height: 1.2;
+ color: #000;
+}
+
+.learn h3 {
+ font-size: 24px;
+}
+
+.learn h4 {
+ font-size: 18px;
+}
+
+.learn h5 {
+ margin-bottom: 0;
+ font-size: 14px;
+}
+
+.learn ul {
+ padding: 0;
+ margin: 0 0 30px 25px;
+}
+
+.learn li {
+ line-height: 20px;
+}
+
+.learn p {
+ font-size: 15px;
+ font-weight: 300;
+ line-height: 1.3;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#issue-count {
+ display: none;
+}
+
+.quote {
+ border: none;
+ margin: 20px 0 60px 0;
+}
+
+.quote p {
+ font-style: italic;
+}
+
+.quote p:before {
+ content: '“';
+ font-size: 50px;
+ opacity: .15;
+ position: absolute;
+ top: -20px;
+ left: 3px;
+}
+
+.quote p:after {
+ content: '”';
+ font-size: 50px;
+ opacity: .15;
+ position: absolute;
+ bottom: -42px;
+ right: 3px;
+}
+
+.quote footer {
+ position: absolute;
+ bottom: -40px;
+ right: 0;
+}
+
+.quote footer img {
+ border-radius: 3px;
+}
+
+.quote footer a {
+ margin-left: 5px;
+ vertical-align: middle;
+}
+
+.speech-bubble {
+ position: relative;
+ padding: 10px;
+ background: rgba(0, 0, 0, .04);
+ border-radius: 5px;
+}
+
+.speech-bubble:after {
+ content: '';
+ position: absolute;
+ top: 100%;
+ right: 30px;
+ border: 13px solid transparent;
+ border-top-color: rgba(0, 0, 0, .04);
+}
+
+.learn-bar > .learn {
+ position: absolute;
+ width: 272px;
+ top: 8px;
+ left: -300px;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: rgba(255, 255, 255, .6);
+ transition-property: left;
+ transition-duration: 500ms;
+}
+
+@media (min-width: 899px) {
+ .learn-bar {
+ width: auto;
+ padding-left: 300px;
+ }
+
+ .learn-bar > .learn {
+ left: 8px;
+ }
+} \ No newline at end of file
diff --git a/example/todo/resources/todo/index.css b/example/todo/resources/todo/index.css
new file mode 100644
index 0000000..208a762
--- /dev/null
+++ b/example/todo/resources/todo/index.css
@@ -0,0 +1,378 @@
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+button {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+ font-size: 100%;
+ vertical-align: baseline;
+ font-family: inherit;
+ font-weight: inherit;
+ color: inherit;
+ -webkit-appearance: none;
+ appearance: none;
+ -webkit-font-smoothing: antialiased;
+ -moz-font-smoothing: antialiased;
+ font-smoothing: antialiased;
+}
+
+body {
+ font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ line-height: 1.4em;
+ background: #f5f5f5;
+ color: #4d4d4d;
+ min-width: 230px;
+ max-width: 550px;
+ margin: 0 auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-font-smoothing: antialiased;
+ font-smoothing: antialiased;
+ font-weight: 300;
+}
+
+button,
+input[type="checkbox"] {
+ outline: none;
+}
+
+.hidden {
+ display: none;
+}
+
+.todoapp {
+ background: #fff;
+ margin: 130px 0 40px 0;
+ position: relative;
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
+ 0 25px 50px 0 rgba(0, 0, 0, 0.1);
+}
+
+.todoapp input::-webkit-input-placeholder {
+ font-style: italic;
+ font-weight: 300;
+ color: #e6e6e6;
+}
+
+.todoapp input::-moz-placeholder {
+ font-style: italic;
+ font-weight: 300;
+ color: #e6e6e6;
+}
+
+.todoapp input::input-placeholder {
+ font-style: italic;
+ font-weight: 300;
+ color: #e6e6e6;
+}
+
+.todoapp h1 {
+ position: absolute;
+ top: -155px;
+ width: 100%;
+ font-size: 100px;
+ font-weight: 100;
+ text-align: center;
+ color: rgba(175, 47, 47, 0.15);
+ -webkit-text-rendering: optimizeLegibility;
+ -moz-text-rendering: optimizeLegibility;
+ text-rendering: optimizeLegibility;
+}
+
+.new-todo,
+.edit {
+ position: relative;
+ margin: 0;
+ width: 100%;
+ font-size: 24px;
+ font-family: inherit;
+ font-weight: inherit;
+ line-height: 1.4em;
+ border: 0;
+ outline: none;
+ color: inherit;
+ padding: 6px;
+ border: 1px solid #999;
+ box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
+ box-sizing: border-box;
+ -webkit-font-smoothing: antialiased;
+ -moz-font-smoothing: antialiased;
+ font-smoothing: antialiased;
+}
+
+.new-todo {
+ padding: 16px 16px 16px 60px;
+ border: none;
+ background: rgba(0, 0, 0, 0.003);
+ box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
+}
+
+.main {
+ position: relative;
+ z-index: 2;
+ border-top: 1px solid #e6e6e6;
+}
+
+label[for='toggle-all'] {
+ display: none;
+}
+
+.toggle-all {
+ position: absolute;
+ top: -55px;
+ left: -12px;
+ width: 60px;
+ height: 34px;
+ text-align: center;
+ border: none; /* Mobile Safari */
+}
+
+.toggle-all:before {
+ content: '❯';
+ font-size: 22px;
+ color: #e6e6e6;
+ padding: 10px 27px 10px 27px;
+}
+
+.toggle-all:checked:before {
+ color: #737373;
+}
+
+.todo-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.todo-list li {
+ position: relative;
+ font-size: 24px;
+ border-bottom: 1px solid #ededed;
+}
+
+.todo-list li:last-child {
+ border-bottom: none;
+}
+
+.todo-list li.editing {
+ border-bottom: none;
+ padding: 0;
+}
+
+.todo-list li.editing .edit {
+ display: block;
+ width: 506px;
+ padding: 13px 17px 12px 17px;
+ margin: 0 0 0 43px;
+}
+
+.todo-list li.editing .view {
+ display: none;
+}
+
+.todo-list li .toggle {
+ text-align: center;
+ width: 40px;
+ /* auto, since non-WebKit browsers doesn't support input styling */
+ height: auto;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ margin: auto 0;
+ border: none; /* Mobile Safari */
+ -webkit-appearance: none;
+ appearance: none;
+}
+
+.todo-list li .toggle:after {
+ content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
+}
+
+.todo-list li .toggle:checked:after {
+ content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
+}
+
+.todo-list li label {
+ white-space: pre-line;
+ word-break: break-all;
+ padding: 15px 60px 15px 15px;
+ margin-left: 45px;
+ display: block;
+ line-height: 1.2;
+ transition: color 0.4s;
+}
+
+.todo-list li.completed label {
+ color: #d9d9d9;
+ text-decoration: line-through;
+}
+
+.todo-list li .destroy {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 10px;
+ bottom: 0;
+ width: 40px;
+ height: 40px;
+ margin: auto 0;
+ font-size: 30px;
+ color: #cc9a9a;
+ margin-bottom: 11px;
+ transition: color 0.2s ease-out;
+}
+
+.todo-list li .destroy:hover {
+ color: #af5b5e;
+}
+
+.todo-list li .destroy:after {
+ content: '×';
+}
+
+.todo-list li:hover .destroy {
+ display: block;
+}
+
+.todo-list li .edit {
+ display: none;
+}
+
+.todo-list li.editing:last-child {
+ margin-bottom: -1px;
+}
+
+.footer {
+ color: #777;
+ padding: 10px 15px;
+ height: 20px;
+ text-align: center;
+ border-top: 1px solid #e6e6e6;
+}
+
+.footer:before {
+ content: '';
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ height: 50px;
+ overflow: hidden;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
+ 0 8px 0 -3px #f6f6f6,
+ 0 9px 1px -3px rgba(0, 0, 0, 0.2),
+ 0 16px 0 -6px #f6f6f6,
+ 0 17px 2px -6px rgba(0, 0, 0, 0.2);
+}
+
+.todo-count {
+ float: left;
+ text-align: left;
+}
+
+.todo-count strong {
+ font-weight: 300;
+}
+
+.filters {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ position: absolute;
+ right: 0;
+ left: 0;
+}
+
+.filters li {
+ display: inline;
+}
+
+.filters li a {
+ color: inherit;
+ margin: 3px;
+ padding: 3px 7px;
+ text-decoration: none;
+ border: 1px solid transparent;
+ border-radius: 3px;
+}
+
+.filters li a.selected,
+.filters li a:hover {
+ border-color: rgba(175, 47, 47, 0.1);
+}
+
+.filters li a.selected {
+ border-color: rgba(175, 47, 47, 0.2);
+}
+
+.clear-completed,
+html .clear-completed:active {
+ float: right;
+ position: relative;
+ line-height: 20px;
+ text-decoration: none;
+ cursor: pointer;
+ position: relative;
+}
+
+.clear-completed:hover {
+ text-decoration: underline;
+}
+
+.info {
+ margin: 65px auto 0;
+ color: #bfbfbf;
+ font-size: 10px;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ text-align: center;
+}
+
+.info p {
+ line-height: 1;
+}
+
+.info a {
+ color: inherit;
+ text-decoration: none;
+ font-weight: 400;
+}
+
+.info a:hover {
+ text-decoration: underline;
+}
+
+/*
+ Hack to remove background from Mobile Safari.
+ Can't use it globally since it destroys checkboxes in Firefox
+*/
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+ .toggle-all,
+ .todo-list li .toggle {
+ background: none;
+ }
+
+ .todo-list li .toggle {
+ height: 40px;
+ }
+
+ .toggle-all {
+ -webkit-transform: rotate(90deg);
+ transform: rotate(90deg);
+ -webkit-appearance: none;
+ appearance: none;
+ }
+}
+
+@media (max-width: 430px) {
+ .footer {
+ height: 50px;
+ }
+
+ .filters {
+ bottom: 10px;
+ }
+} \ No newline at end of file
diff --git a/example/todo/src/todo/Server.scala b/example/todo/src/todo/Server.scala
new file mode 100644
index 0000000..7dd64ae
--- /dev/null
+++ b/example/todo/src/todo/Server.scala
@@ -0,0 +1,105 @@
+package todo
+import scalatags.Text.all._
+import scalatags.Text.tags2
+import scalatags.Text.tags2.section
+case class Todo(checked: Boolean, text: String)
+object Server extends cask.MainRoutes{
+ var todos = Seq(
+ Todo(true, "Get started with Cask"),
+ Todo(false, "Profit!")
+ )
+
+ @cask.get("/list/:state")
+ def list(state: String) = list0(state).render
+
+ @cask.post("/add/:state")
+ def add(state: String, request: cask.Request) = {
+ todos = Seq(Todo(false, new String(request.data.readAllBytes()))) ++ todos
+ list0(state).render
+ }
+
+ def list0(state: String) = {
+ val filteredTodos = state match{
+ case "all" => todos
+ case "active" => todos.filter(!_.checked)
+ case "completed" => todos.filter(_.checked)
+ }
+ frag(
+ for((todo, i) <- filteredTodos.zipWithIndex) yield li(if (todo.checked) cls := "completed" else (),
+ div(cls := "view",
+ input(cls := "toggle", `type` := "checkbox", if (todo.checked) checked else ()),
+ label(todo.text),
+ button(cls := "destroy", data("todo-index") := i)
+ ),
+ input(cls := "edit", value := todo.text)
+ )
+ )
+ }
+
+ @cask.get("/")
+ def index() = {
+ cask.Response(
+ "<!doctype html>" + html(lang := "en",
+ head(
+ meta(charset := "utf-8"),
+ meta(name := "viewport", content := "width=device-width, initial-scale=1"),
+ tags2.title("Template • TodoMVC"),
+ link(rel := "stylesheet", href := "/static/base.css"),
+ link(rel := "stylesheet", href := "/static/index.css")
+ ),
+ body(
+ section(cls := "todoapp",
+ header(cls := "header",
+ h1("todos"),
+ input(cls := "new-todo", placeholder := "What needs to be done?", autofocus := "")
+ ),
+ section(cls := "main",
+ input(id := "toggle-all", cls := "toggle-all", `type` := "checkbox"),
+ label(`for` := "toggle-all","Mark all as complete"),
+ ul(cls := "todo-list",
+ list0("all")
+ )
+ ),
+ footer(cls := "footer",
+ span(cls := "todo-count",
+ strong("0"),
+ "item left"
+ ),
+ ul(cls := "filters",
+ li(
+ a(cls := "selected", href := "#/","All")
+ ),
+ li(
+ a(href := "#/active","Active")
+ ),
+ li(
+ a(href := "#/completed","Completed")
+ )
+ ),
+ button(cls := "clear-completed","Clear completed")
+ )
+ ),
+ footer(cls := "info",
+ p("Double-click to edit a todo"),
+ p("Template by",
+ a(href := "http://sindresorhus.com","Sindre Sorhus")
+ ),
+ p("Created by",
+ a(href := "http://todomvc.com","you")
+ ),
+ p("Part of",
+ a(href := "http://todomvc.com","TodoMVC")
+ )
+ ),
+ script(src := "node_modules/todomvc-common/base.js"),
+ script(src := "/static/app.js")
+ )
+ )
+ )
+ }
+
+ @cask.static("/static")
+ def static() = "example/todo/resources/todo"
+
+ initialize()
+}