Commit 7cdcf5a8 authored by Pascal Hartig's avatar Pascal Hartig

[WIP] Elm 0.15 Update

parent f183e30a
module Task where
import Html (..)
import Html.Attributes (..)
import Html.Events (..)
import Json.Decode as Json
import LocalChannel as LC
import Maybe
import Signal
import String
-- MODEL
type alias Model =
{ description : String
, completed : Bool
, edits : Maybe String
, id : Int
}
init : String -> Int -> Model
init desc id =
{ description = desc
, completed = False
, edits = Nothing
, id = id
}
-- UPDATE
type Action
= Focus
| Edit String
| Cancel
| Commit
| Completed Bool
| Delete
update : Action -> Model -> Maybe Model
update update task =
case update of
Focus ->
Just { task | edits <- Just task.description }
Edit description ->
Just { task | edits <- Just description }
Cancel ->
Just { task | edits <- Nothing }
Commit ->
case task.edits of
Nothing ->
Just task
Just rawDescription ->
let description = String.trim rawDescription in
if String.isEmpty description then Nothing else
Just
{ task |
edits <- Nothing,
description <- description
}
Completed bool ->
Just { task | completed <- bool }
Delete ->
Nothing
-- VIEW
view : LC.LocalChannel (Int, Action) -> Model -> Html
view channel task =
let className =
(if task.completed then "completed " else "") ++
case task.edits of
Just _ -> "editing"
Nothing -> ""
description =
Maybe.withDefault task.description task.edits
in
li
[ class className ]
[ div
[ class "view" ]
[ input
[ class "toggle"
, type' "checkbox"
, checked task.completed
, onClick (LC.send channel (task.id, Completed (not task.completed)))
]
[]
, label
[ onDoubleClick (LC.send channel (task.id, Focus)) ]
[ text description ]
, button
[ class "destroy"
, onClick (LC.send channel (task.id, Delete))
]
[]
]
, input
[ class "edit"
, value description
, name "title"
, id ("todo-" ++ toString task.id)
, on "input" targetValue (\desc -> LC.send channel (task.id, Edit desc))
, onBlur (LC.send channel (task.id, Commit))
, onFinish
(LC.send channel (task.id, Commit))
(LC.send channel (task.id, Cancel))
]
[]
]
onFinish : Signal.Message -> Signal.Message -> Attribute
onFinish enterMessage escapeMessage =
let select key =
case key of
13 -> Ok enterMessage
27 -> Ok escapeMessage
_ -> Err "Not a 'finish' key, such as ENTER or ESCAPE"
in
on "keydown" (Json.customDecoder keyCode select) identity
...@@ -3,9 +3,9 @@ module Todo where ...@@ -3,9 +3,9 @@ module Todo where
This application is broken up into four distinct parts: This application is broken up into four distinct parts:
1. Model - a full description of the application as data 1. Model - a full definition of the application's state
2. Update - a way to update the model based on user actions 2. Update - a way to step the application state forward
3. View - a way to visualize our model with HTML 3. View - a way to visualize our application state with HTML
4. Inputs - the signals necessary to manage events 4. Inputs - the signals necessary to manage events
This clean division of concerns is a core part of Elm. You can read more about This clean division of concerns is a core part of Elm. You can read more about
...@@ -16,30 +16,43 @@ document for notes on structuring more complex GUIs with Elm: ...@@ -16,30 +16,43 @@ document for notes on structuring more complex GUIs with Elm:
http://elm-lang.org/learn/Architecture.elm http://elm-lang.org/learn/Architecture.elm
-} -}
import Html (..) import Html exposing (..)
import Html.Attributes (..) import Html.Attributes exposing (..)
import Html.Events (..) import Html.Events exposing (..)
import Html.Lazy (lazy, lazy2) import Html.Lazy exposing (lazy, lazy2, lazy3)
import List import Json.Decode as Json
import LocalChannel as LC import Signal exposing (Signal, Address)
import Maybe
import Signal
import String import String
import Task
import Window import Window
---- MODEL ----
-- MODEL
-- The full application state of our todo app. -- The full application state of our todo app.
type alias Model = type alias Model =
{ tasks : List Task.Model { tasks : List Task
, field : String , field : String
, uid : Int , uid : Int
, visibility : String , visibility : String
} }
type alias Task =
{ description : String
, completed : Bool
, editing : Bool
, id : Int
}
newTask : String -> Int -> Task
newTask desc id =
{ description = desc
, completed = False
, editing = False
, id = id
}
emptyModel : Model emptyModel : Model
emptyModel = emptyModel =
{ tasks = [] { tasks = []
...@@ -49,7 +62,7 @@ emptyModel = ...@@ -49,7 +62,7 @@ emptyModel =
} }
-- UPDATE ---- UPDATE ----
-- A description of the kinds of actions that can be performed on the model of -- A description of the kinds of actions that can be performed on the model of
-- our application. See the following post for more info on this pattern and -- our application. See the following post for more info on this pattern and
...@@ -57,84 +70,115 @@ emptyModel = ...@@ -57,84 +70,115 @@ emptyModel =
type Action type Action
= NoOp = NoOp
| UpdateField String | UpdateField String
| EditingTask Int Bool
| UpdateTask Int String
| Add | Add
| UpdateTask (Int, Task.Action) | Delete Int
| DeleteComplete | DeleteComplete
| Check Int Bool
| CheckAll Bool | CheckAll Bool
| ChangeVisibility String | ChangeVisibility String
-- How we update our Model on any given Action -- How we update our Model on a given Action?
update : Action -> Model -> Model update : Action -> Model -> Model
update action model = update action model =
case action of case action of
NoOp -> model NoOp -> model
Add ->
{ model |
uid <- model.uid + 1,
field <- "",
tasks <-
if String.isEmpty model.field
then model.tasks
else model.tasks ++ [newTask model.field model.uid]
}
UpdateField str -> UpdateField str ->
{ model | field <- str } { model | field <- str }
Add -> EditingTask id isEditing ->
let description = String.trim model.field in let updateTask t = if t.id == id then { t | editing <- isEditing } else t
if String.isEmpty description then model else
{ model |
uid <- model.uid + 1,
field <- "",
tasks <- model.tasks ++ [Task.init description model.uid]
}
UpdateTask (id, taskAction) ->
let updateTask t =
if t.id == id then Task.update taskAction t else Just t
in in
{ model | tasks <- List.filterMap updateTask model.tasks } { model | tasks <- List.map updateTask model.tasks }
UpdateTask id task ->
let updateTask t = if t.id == id then { t | description <- task } else t
in
{ model | tasks <- List.map updateTask model.tasks }
Delete id ->
{ model | tasks <- List.filter (\t -> t.id /= id) model.tasks }
DeleteComplete -> DeleteComplete ->
{ model | tasks <- List.filter (not << .completed) model.tasks } { model | tasks <- List.filter (not << .completed) model.tasks }
CheckAll bool -> Check id isCompleted ->
let updateTask t = { t | completed <- bool } let updateTask t = if t.id == id then { t | completed <- isCompleted } else t
in { model | tasks <- List.map updateTask model.tasks } in
{ model | tasks <- List.map updateTask model.tasks }
CheckAll isCompleted ->
let updateTask t = { t | completed <- isCompleted }
in
{ model | tasks <- List.map updateTask model.tasks }
ChangeVisibility visibility -> ChangeVisibility visibility ->
{ model | visibility <- visibility } { model | visibility <- visibility }
-- VIEW ---- VIEW ----
view : Model -> Html view : Address Action -> Model -> Html
view model = view address model =
div div
[ class "todomvc-wrapper" [ class "todomvc-wrapper"
, style [ ("visibility", "hidden") ] , style [ ("visibility", "hidden") ]
] ]
[ section [ section
[ id "todoapp" ] [ class "todoapp" ]
[ lazy taskEntry model.field [ lazy2 taskEntry address model.field
, lazy2 taskList model.visibility model.tasks , lazy3 taskList address model.visibility model.tasks
, lazy2 controls model.visibility model.tasks , lazy3 controls address model.visibility model.tasks
] ]
, infoFooter , infoFooter
] ]
taskEntry : String -> Html
taskEntry task = onEnter : Address a -> a -> Attribute
onEnter address value =
on "keydown"
(Json.customDecoder keyCode is13)
(\_ -> Signal.message address value)
is13 : Int -> Result String ()
is13 code =
if code == 13 then Ok () else Err "not the right key code"
taskEntry : Address Action -> String -> Html
taskEntry address task =
header header
[ id "header" ] [ class "header" ]
[ h1 [] [ text "todos" ] [ h1 [] [ text "todos" ]
, input , input
[ id "new-todo" [ class "new-todo"
, placeholder "What needs to be done?" , placeholder "What needs to be done?"
, autofocus True , autofocus True
, value task , value task
, name "newTodo" , name "newTodo"
, on "input" targetValue (Signal.send actions << UpdateField) , on "input" targetValue (Signal.message address << UpdateField)
, Task.onFinish (Signal.send actions Add) (Signal.send actions NoOp) , onEnter address Add
] ]
[] []
] ]
taskList : String -> List Task.Model -> Html
taskList visibility tasks = taskList : Address Action -> String -> List Task -> Html
taskList address visibility tasks =
let isVisible todo = let isVisible todo =
case visibility of case visibility of
"Completed" -> todo.completed "Completed" -> todo.completed
...@@ -146,124 +190,157 @@ taskList visibility tasks = ...@@ -146,124 +190,157 @@ taskList visibility tasks =
cssVisibility = if List.isEmpty tasks then "hidden" else "visible" cssVisibility = if List.isEmpty tasks then "hidden" else "visible"
in in
section section
[ id "main" [ class "main"
, style [ ("visibility", cssVisibility) ] , style [ ("visibility", cssVisibility) ]
] ]
[ input [ input
[ id "toggle-all" [ class "toggle-all"
, type' "checkbox" , type' "checkbox"
, name "toggle" , name "toggle"
, checked allCompleted , checked allCompleted
, onClick (Signal.send actions (CheckAll (not allCompleted))) , onClick address (CheckAll (not allCompleted))
] ]
[] []
, label , label
[ for "toggle-all" ] [ for "toggle-all" ]
[ text "Mark all as complete" ] [ text "Mark all as complete" ]
, ul , ul
[ id "todo-list" ] [ class "todo-list" ]
(List.map (Task.view taskActions) (List.filter isVisible tasks)) (List.map (todoItem address) (List.filter isVisible tasks))
] ]
controls : String -> List Task.Model -> Html
controls visibility tasks = todoItem : Address Action -> Task -> Html
todoItem address todo =
li
[ classList [ ("completed", todo.completed), ("editing", todo.editing) ] ]
[ div
[ class "view" ]
[ input
[ class "toggle"
, type' "checkbox"
, checked todo.completed
, onClick address (Check todo.id (not todo.completed))
]
[]
, label
[ onDoubleClick address (EditingTask todo.id True) ]
[ text todo.description ]
, button
[ class "destroy"
, onClick address (Delete todo.id)
]
[]
]
, input
[ class "edit"
, value todo.description
, name "title"
, class ("todo-" ++ toString todo.id)
, on "input" targetValue (Signal.message address << UpdateTask todo.id)
, onBlur address (EditingTask todo.id False)
, onEnter address (EditingTask todo.id False)
]
[]
]
controls : Address Action -> String -> List Task -> Html
controls address visibility tasks =
let tasksCompleted = List.length (List.filter .completed tasks) let tasksCompleted = List.length (List.filter .completed tasks)
tasksLeft = List.length tasks - tasksCompleted tasksLeft = List.length tasks - tasksCompleted
item_ = if tasksLeft == 1 then " item" else " items" item_ = if tasksLeft == 1 then " item" else " items"
in in
footer footer
[ id "footer" [ class "footer"
, hidden (List.isEmpty tasks) , hidden (List.isEmpty tasks)
] ]
[ span [ span
[ id "todo-count" ] [ class "todo-count" ]
[ strong [] [ text (toString tasksLeft) ] [ strong [] [ text (toString tasksLeft) ]
, text (item_ ++ " left") , text (item_ ++ " left")
] ]
, ul , ul
[ id "filters" ] [ class "filters" ]
[ visibilitySwap "#/" "All" visibility [ visibilitySwap address "#/" "All" visibility
, text " " , text " "
, visibilitySwap "#/active" "Active" visibility , visibilitySwap address "#/active" "Active" visibility
, text " " , text " "
, visibilitySwap "#/completed" "Completed" visibility , visibilitySwap address "#/completed" "Completed" visibility
] ]
, button , button
[ class "clear-completed" [ class "clear-completed"
, id "clear-completed" , id "clear-completed"
, hidden (tasksCompleted == 0) , hidden (tasksCompleted == 0)
, onClick (Signal.send actions DeleteComplete) , onClick address DeleteComplete
] ]
[ text ("Clear completed (" ++ toString tasksCompleted ++ ")") ] [ text ("Clear completed (" ++ toString tasksCompleted ++ ")") ]
] ]
visibilitySwap : String -> String -> String -> Html
visibilitySwap uri visibility actualVisibility = visibilitySwap : Address Action -> String -> String -> String -> Html
let className = if visibility == actualVisibility then "selected" else "" in visibilitySwap address uri visibility actualVisibility =
li li
[ onClick (Signal.send actions (ChangeVisibility visibility)) ] [ onClick address (ChangeVisibility visibility) ]
[ a [ class className, href uri ] [ text visibility ] ] [ a [ href uri, classList [("selected", visibility == actualVisibility)] ] [ text visibility ] ]
infoFooter : Html infoFooter : Html
infoFooter = infoFooter =
footer [ id "info" ] footer [ class "info" ]
[ p [] [ text "Double-click to edit a todo" ] [ p [] [ text "Double-click to edit a todo" ]
, p [] [ text "Written by " , p []
, a [ href "https://github.com/evancz" ] [ text "Evan Czaplicki" ] [ text "Written by "
] , a [ href "https://github.com/evancz" ] [ text "Evan Czaplicki" ]
, p [] [ text "Part of " ]
, a [ href "http://todomvc.com" ] [ text "TodoMVC" ] , p []
] [ text "Part of "
, a [ href "http://todomvc.com" ] [ text "TodoMVC" ]
]
] ]
-- SIGNALS ---- INPUTS ----
-- wire the entire application together -- wire the entire application together
main : Signal Html main : Signal Html
main = main =
Signal.map view model Signal.map (view actions.address) model
-- manage the model of our application over time -- manage the model of our application over time
model : Signal Model model : Signal Model
model = model =
Signal.foldp update initialModel allActions Signal.foldp update initialModel actions.signal
initialModel : Model initialModel : Model
initialModel = initialModel =
Maybe.withDefault emptyModel savedModel Maybe.withDefault emptyModel getStorage
allActions : Signal Action -- actions from user input
allActions = actions : Signal.Mailbox Action
Signal.merge actions =
(Signal.subscribe actions) Signal.mailbox NoOp
(Signal.map ChangeVisibility route)
-- interactions with localStorage
port savedModel : Maybe Model
port save : Signal Model port focus : Signal String
port save = model port focus =
let needsFocus act =
case act of
EditingTask id bool -> bool
_ -> False
-- routing toSelector (EditingTask id _) = ("#todo-" ++ toString id)
port route : Signal String in
actions.signal
|> Signal.filter needsFocus (EditingTask 0 True)
|> Signal.map toSelector
-- actions from user input
actions : Signal.Channel Action
-- interactions with localStorage to save the model
taskActions : LC.LocalChannel (Int, Task.Action) port getStorage : Maybe Model
port setStorage : Signal Model
port focus : Signal (Maybe Int) port setStorage = model
port focus =
let toSelector action =
case action of
UpdateTask (id, Task.Focus) -> Just id
_ -> Nothing
in
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
], ],
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"elm-lang/core": "1.0.0 <= v < 2.0.0", "elm-lang/core": "2.0.0 <= v < 3.0.0",
"evancz/elm-html": "1.0.0 <= v < 2.0.0", "evancz/elm-html": "3.0.0 <= v < 4.0.0"
"evancz/local-channel": "1.0.0 <= v < 2.0.0" },
} "elm-version": "0.15.0 <= v < 0.16.0"
} }
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -19,44 +19,22 @@ ...@@ -19,44 +19,22 @@
<script src="elm.js"></script> <script src="elm.js"></script>
<script> <script>
(function () { (function () {
var result = localStorage.getItem('elm-todo-model'); var storedState = localStorage.getItem('elm-todo-state');
var savedModel = result ? JSON.parse(result) : null; var startingState = storedState ? JSON.parse(storedState) : null;
var todomvc = Elm.fullscreen(Elm.Todo, { var todomvc = Elm.fullscreen(Elm.Todo, { getStorage: startingState });
savedModel: savedModel, todomvc.ports.focus.subscribe(function (selector) {
route: getRoute()
});
todomvc.ports.save.subscribe(function (model) {
localStorage.setItem('elm-todo-model', JSON.stringify(model));
});
// Routing
window.addEventListener('popstate', function () {
todomvc.ports.route.send(getRoute());
}, false);
function getRoute() {
var hash = location.href.split('#')[1] || '';
var route = hash.replace('/', '');
if (['all', 'active', 'completed'].indexOf(route) >= 0) {
return route[0].toUpperCase() + route.substr(1);
}
return 'All';
}
// Setting focus manually
todomvc.ports.focus.subscribe(function (id) {
setTimeout(function () { setTimeout(function () {
if (id === null) { var nodes = document.querySelectorAll(selector);
return; if (nodes.length === 1 && document.activeElement !== nodes[0]) {
} nodes[0].focus()
var node = document.getElementById('todo-' + id);
if (document.activeElement !== node) {
node.focus();
} }
}, 50); }, 50);
}); });
todomvc.ports.setStorage.subscribe(function (state) {
localStorage.setItem('elm-todo-state', JSON.stringify(state));
});
}()); }());
</script> </script>
<script async src="node_modules/todomvc-common/base.js"></script> <script async src="node_modules/todomvc-common/base.js"></script>
......
...@@ -15,11 +15,9 @@ button { ...@@ -15,11 +15,9 @@ button {
font-weight: inherit; font-weight: inherit;
color: inherit; color: inherit;
-webkit-appearance: none; -webkit-appearance: none;
-ms-appearance: none;
appearance: none; appearance: none;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased; -moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
font-smoothing: antialiased; font-smoothing: antialiased;
} }
...@@ -33,7 +31,6 @@ body { ...@@ -33,7 +31,6 @@ body {
margin: 0 auto; margin: 0 auto;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased; -moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
font-smoothing: antialiased; font-smoothing: antialiased;
font-weight: 300; font-weight: 300;
} }
...@@ -47,7 +44,7 @@ input[type="checkbox"] { ...@@ -47,7 +44,7 @@ input[type="checkbox"] {
display: none; display: none;
} }
#todoapp { .todoapp {
background: #fff; background: #fff;
margin: 130px 0 40px 0; margin: 130px 0 40px 0;
position: relative; position: relative;
...@@ -55,25 +52,25 @@ input[type="checkbox"] { ...@@ -55,25 +52,25 @@ input[type="checkbox"] {
0 25px 50px 0 rgba(0, 0, 0, 0.1); 0 25px 50px 0 rgba(0, 0, 0, 0.1);
} }
#todoapp input::-webkit-input-placeholder { .todoapp input::-webkit-input-placeholder {
font-style: italic; font-style: italic;
font-weight: 300; font-weight: 300;
color: #e6e6e6; color: #e6e6e6;
} }
#todoapp input::-moz-placeholder { .todoapp input::-moz-placeholder {
font-style: italic; font-style: italic;
font-weight: 300; font-weight: 300;
color: #e6e6e6; color: #e6e6e6;
} }
#todoapp input::input-placeholder { .todoapp input::input-placeholder {
font-style: italic; font-style: italic;
font-weight: 300; font-weight: 300;
color: #e6e6e6; color: #e6e6e6;
} }
#todoapp h1 { .todoapp h1 {
position: absolute; position: absolute;
top: -155px; top: -155px;
width: 100%; width: 100%;
...@@ -83,11 +80,10 @@ input[type="checkbox"] { ...@@ -83,11 +80,10 @@ input[type="checkbox"] {
color: rgba(175, 47, 47, 0.15); color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility; -webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility; -moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
} }
#new-todo, .new-todo,
.edit { .edit {
position: relative; position: relative;
margin: 0; margin: 0;
...@@ -102,22 +98,20 @@ input[type="checkbox"] { ...@@ -102,22 +98,20 @@ input[type="checkbox"] {
padding: 6px; padding: 6px;
border: 1px solid #999; border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-ms-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased; -moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
font-smoothing: antialiased; font-smoothing: antialiased;
} }
#new-todo { .new-todo {
padding: 16px 16px 16px 60px; padding: 16px 16px 16px 60px;
border: none; border: none;
background: rgba(0, 0, 0, 0.003); background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
} }
#main { .main {
position: relative; position: relative;
z-index: 2; z-index: 2;
border-top: 1px solid #e6e6e6; border-top: 1px solid #e6e6e6;
...@@ -127,7 +121,7 @@ label[for='toggle-all'] { ...@@ -127,7 +121,7 @@ label[for='toggle-all'] {
display: none; display: none;
} }
#toggle-all { .toggle-all {
position: absolute; position: absolute;
top: -55px; top: -55px;
left: -12px; left: -12px;
...@@ -137,50 +131,50 @@ label[for='toggle-all'] { ...@@ -137,50 +131,50 @@ label[for='toggle-all'] {
border: none; /* Mobile Safari */ border: none; /* Mobile Safari */
} }
#toggle-all:before { .toggle-all:before {
content: '❯'; content: '❯';
font-size: 22px; font-size: 22px;
color: #e6e6e6; color: #e6e6e6;
padding: 10px 27px 10px 27px; padding: 10px 27px 10px 27px;
} }
#toggle-all:checked:before { .toggle-all:checked:before {
color: #737373; color: #737373;
} }
#todo-list { .todo-list {
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
} }
#todo-list li { .todo-list li {
position: relative; position: relative;
font-size: 24px; font-size: 24px;
border-bottom: 1px solid #ededed; border-bottom: 1px solid #ededed;
} }
#todo-list li:last-child { .todo-list li:last-child {
border-bottom: none; border-bottom: none;
} }
#todo-list li.editing { .todo-list li.editing {
border-bottom: none; border-bottom: none;
padding: 0; padding: 0;
} }
#todo-list li.editing .edit { .todo-list li.editing .edit {
display: block; display: block;
width: 506px; width: 506px;
padding: 13px 17px 12px 17px; padding: 13px 17px 12px 17px;
margin: 0 0 0 43px; margin: 0 0 0 43px;
} }
#todo-list li.editing .view { .todo-list li.editing .view {
display: none; display: none;
} }
#todo-list li .toggle { .todo-list li .toggle {
text-align: center; text-align: center;
width: 40px; width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */ /* auto, since non-WebKit browsers doesn't support input styling */
...@@ -191,19 +185,18 @@ label[for='toggle-all'] { ...@@ -191,19 +185,18 @@ label[for='toggle-all'] {
margin: auto 0; margin: auto 0;
border: none; /* Mobile Safari */ border: none; /* Mobile Safari */
-webkit-appearance: none; -webkit-appearance: none;
-ms-appearance: none;
appearance: none; appearance: none;
} }
#todo-list li .toggle:after { .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>'); 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 { .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>'); 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 { .todo-list li label {
white-space: pre; white-space: pre;
word-break: break-word; word-break: break-word;
padding: 15px 60px 15px 15px; padding: 15px 60px 15px 15px;
...@@ -213,12 +206,12 @@ label[for='toggle-all'] { ...@@ -213,12 +206,12 @@ label[for='toggle-all'] {
transition: color 0.4s; transition: color 0.4s;
} }
#todo-list li.completed label { .todo-list li.completed label {
color: #d9d9d9; color: #d9d9d9;
text-decoration: line-through; text-decoration: line-through;
} }
#todo-list li .destroy { .todo-list li .destroy {
display: none; display: none;
position: absolute; position: absolute;
top: 0; top: 0;
...@@ -233,27 +226,27 @@ label[for='toggle-all'] { ...@@ -233,27 +226,27 @@ label[for='toggle-all'] {
transition: color 0.2s ease-out; transition: color 0.2s ease-out;
} }
#todo-list li .destroy:hover { .todo-list li .destroy:hover {
color: #af5b5e; color: #af5b5e;
} }
#todo-list li .destroy:after { .todo-list li .destroy:after {
content: '×'; content: '×';
} }
#todo-list li:hover .destroy { .todo-list li:hover .destroy {
display: block; display: block;
} }
#todo-list li .edit { .todo-list li .edit {
display: none; display: none;
} }
#todo-list li.editing:last-child { .todo-list li.editing:last-child {
margin-bottom: -1px; margin-bottom: -1px;
} }
#footer { .footer {
color: #777; color: #777;
padding: 10px 15px; padding: 10px 15px;
height: 20px; height: 20px;
...@@ -261,7 +254,7 @@ label[for='toggle-all'] { ...@@ -261,7 +254,7 @@ label[for='toggle-all'] {
border-top: 1px solid #e6e6e6; border-top: 1px solid #e6e6e6;
} }
#footer:before { .footer:before {
content: ''; content: '';
position: absolute; position: absolute;
right: 0; right: 0;
...@@ -276,16 +269,16 @@ label[for='toggle-all'] { ...@@ -276,16 +269,16 @@ label[for='toggle-all'] {
0 17px 2px -6px rgba(0, 0, 0, 0.2); 0 17px 2px -6px rgba(0, 0, 0, 0.2);
} }
#todo-count { .todo-count {
float: left; float: left;
text-align: left; text-align: left;
} }
#todo-count strong { .todo-count strong {
font-weight: 300; font-weight: 300;
} }
#filters { .filters {
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
...@@ -294,11 +287,11 @@ label[for='toggle-all'] { ...@@ -294,11 +287,11 @@ label[for='toggle-all'] {
left: 0; left: 0;
} }
#filters li { .filters li {
display: inline; display: inline;
} }
#filters li a { .filters li a {
color: inherit; color: inherit;
margin: 3px; margin: 3px;
padding: 3px 7px; padding: 3px 7px;
...@@ -307,39 +300,30 @@ label[for='toggle-all'] { ...@@ -307,39 +300,30 @@ label[for='toggle-all'] {
border-radius: 3px; border-radius: 3px;
} }
#filters li a.selected, .filters li a.selected,
#filters li a:hover { .filters li a:hover {
border-color: rgba(175, 47, 47, 0.1); border-color: rgba(175, 47, 47, 0.1);
} }
#filters li a.selected { .filters li a.selected {
border-color: rgba(175, 47, 47, 0.2); border-color: rgba(175, 47, 47, 0.2);
} }
#clear-completed, .clear-completed,
html #clear-completed:active { html .clear-completed:active {
float: right; float: right;
position: relative; position: relative;
line-height: 20px; line-height: 20px;
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
visibility: hidden;
position: relative; position: relative;
} }
#clear-completed::after { .clear-completed:hover {
visibility: visible;
content: 'Clear completed';
position: absolute;
right: 0;
white-space: nowrap;
}
#clear-completed:hover::after {
text-decoration: underline; text-decoration: underline;
} }
#info { .info {
margin: 65px auto 0; margin: 65px auto 0;
color: #bfbfbf; color: #bfbfbf;
font-size: 10px; font-size: 10px;
...@@ -347,17 +331,17 @@ html #clear-completed:active { ...@@ -347,17 +331,17 @@ html #clear-completed:active {
text-align: center; text-align: center;
} }
#info p { .info p {
line-height: 1; line-height: 1;
} }
#info a { .info a {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
font-weight: 400; font-weight: 400;
} }
#info a:hover { .info a:hover {
text-decoration: underline; text-decoration: underline;
} }
...@@ -366,16 +350,16 @@ html #clear-completed:active { ...@@ -366,16 +350,16 @@ html #clear-completed:active {
Can't use it globally since it destroys checkboxes in Firefox Can't use it globally since it destroys checkboxes in Firefox
*/ */
@media screen and (-webkit-min-device-pixel-ratio:0) { @media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all, .toggle-all,
#todo-list li .toggle { .todo-list li .toggle {
background: none; background: none;
} }
#todo-list li .toggle { .todo-list li .toggle {
height: 40px; height: 40px;
} }
#toggle-all { .toggle-all {
-webkit-transform: rotate(90deg); -webkit-transform: rotate(90deg);
transform: rotate(90deg); transform: rotate(90deg);
-webkit-appearance: none; -webkit-appearance: none;
...@@ -384,11 +368,11 @@ html #clear-completed:active { ...@@ -384,11 +368,11 @@ html #clear-completed:active {
} }
@media (max-width: 430px) { @media (max-width: 430px) {
#footer { .footer {
height: 50px; height: 50px;
} }
#filters { .filters {
bottom: 10px; bottom: 10px;
} }
} }
...@@ -114,7 +114,12 @@ ...@@ -114,7 +114,12 @@
})({}); })({});
if (location.hostname === 'todomvc.com') { if (location.hostname === 'todomvc.com') {
window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script')); (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-31081062-1', 'auto');
ga('send', 'pageview');
} }
/* jshint ignore:end */ /* jshint ignore:end */
...@@ -228,7 +233,7 @@ ...@@ -228,7 +233,7 @@
xhr.onload = function (e) { xhr.onload = function (e) {
var parsedResponse = JSON.parse(e.target.responseText); var parsedResponse = JSON.parse(e.target.responseText);
if (parsedResponse instanceof Array) { if (parsedResponse instanceof Array) {
var count = parsedResponse.length var count = parsedResponse.length;
if (count !== 0) { if (count !== 0) {
issueLink.innerHTML = 'This app has ' + count + ' open issues'; issueLink.innerHTML = 'This app has ' + count + ' open issues';
document.getElementById('issue-count').style.display = 'inline'; document.getElementById('issue-count').style.display = 'inline';
......
{ {
"private": true, "private": true,
"dependencies": { "dependencies": {
"todomvc-app-css": "^1.0.0", "todomvc-app-css": "^2.0.1",
"todomvc-common": "^1.0.1" "todomvc-common": "^1.0.1"
} }
} }
...@@ -46,7 +46,7 @@ Run the following commands from the root of this project: ...@@ -46,7 +46,7 @@ Run the following commands from the root of this project:
```bash ```bash
elm-package install elm-package install
elm-make Todo.elm --output build/Todo.js elm-make Todo.elm
``` ```
Then open `index.html` in your browser! Then open `index.html` in your browser!
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment