mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2024-11-27 21:29:55 +03:00
Add beginning of todo example.
This commit is contained in:
parent
d79f8a5403
commit
56ee2cf145
@ -37,7 +37,20 @@ route =
|
||||
RouteBuilder.serverRender
|
||||
{ head = head
|
||||
, data = data
|
||||
, action = \_ -> Request.skip "No action."
|
||||
, action =
|
||||
\_ ->
|
||||
MySession.withSession
|
||||
(Request.expectFormPost (\{ field } -> field "name"))
|
||||
(\name session ->
|
||||
( session
|
||||
|> Result.withDefault Nothing
|
||||
|> Maybe.withDefault Session.empty
|
||||
|> Session.insert "userId" name
|
||||
|> Session.withFlash "message" ("Welcome " ++ name ++ "!")
|
||||
, Route.redirectTo Route.Todos
|
||||
)
|
||||
|> DataSource.succeed
|
||||
)
|
||||
}
|
||||
|> RouteBuilder.buildNoState { view = view }
|
||||
|
||||
@ -50,40 +63,26 @@ type alias Request =
|
||||
|
||||
data : RouteParams -> Request.Parser (DataSource (Response Data ErrorPage))
|
||||
data routeParams =
|
||||
Request.oneOf
|
||||
[ MySession.withSession
|
||||
(Request.expectFormPost (\{ field } -> field "name"))
|
||||
(\name session ->
|
||||
( session
|
||||
|> Result.withDefault Nothing
|
||||
|> Maybe.withDefault Session.empty
|
||||
|> Session.insert "name" name
|
||||
|> Session.withFlash "message" ("Welcome " ++ name ++ "!")
|
||||
, Route.redirectTo Route.Index
|
||||
)
|
||||
|> DataSource.succeed
|
||||
)
|
||||
, MySession.withSession
|
||||
(Request.succeed ())
|
||||
(\() session ->
|
||||
case session of
|
||||
Ok (Just okSession) ->
|
||||
( okSession
|
||||
, okSession
|
||||
|> Session.get "name"
|
||||
|> Data
|
||||
|> Server.Response.render
|
||||
)
|
||||
|> DataSource.succeed
|
||||
MySession.withSession
|
||||
(Request.succeed ())
|
||||
(\() session ->
|
||||
case session of
|
||||
Ok (Just okSession) ->
|
||||
( okSession
|
||||
, okSession
|
||||
|> Session.get "name"
|
||||
|> Data
|
||||
|> Server.Response.render
|
||||
)
|
||||
|> DataSource.succeed
|
||||
|
||||
_ ->
|
||||
( Session.empty
|
||||
, { username = Nothing }
|
||||
|> Server.Response.render
|
||||
)
|
||||
|> DataSource.succeed
|
||||
)
|
||||
]
|
||||
_ ->
|
||||
( Session.empty
|
||||
, { username = Nothing }
|
||||
|> Server.Response.render
|
||||
)
|
||||
|> DataSource.succeed
|
||||
)
|
||||
|
||||
|
||||
head :
|
||||
|
392
examples/trails/app/Route/Todos.elm
Normal file
392
examples/trails/app/Route/Todos.elm
Normal file
@ -0,0 +1,392 @@
|
||||
module Route.Todos exposing (ActionData, Data, Model, Msg, route)
|
||||
|
||||
import Api.InputObject
|
||||
import Api.Mutation
|
||||
import Api.Object exposing (Todos)
|
||||
import Api.Object.Todos
|
||||
import Api.Query
|
||||
import DataSource exposing (DataSource)
|
||||
import Effect exposing (Effect)
|
||||
import ErrorPage exposing (ErrorPage)
|
||||
import Graphql.Operation exposing (RootQuery)
|
||||
import Graphql.OptionalArgument exposing (OptionalArgument(..))
|
||||
import Graphql.SelectionSet as SelectionSet exposing (SelectionSet)
|
||||
import Head
|
||||
import Head.Seo as Seo
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events
|
||||
import MySession
|
||||
import Pages.Msg
|
||||
import Pages.PageUrl exposing (PageUrl)
|
||||
import Pages.Url
|
||||
import Path exposing (Path)
|
||||
import Request.Hasura
|
||||
import Route
|
||||
import RouteBuilder exposing (StatefulRoute, StatelessRoute, StaticPayload)
|
||||
import Server.Request as Request
|
||||
import Server.Response as Response exposing (Response)
|
||||
import Server.Session as Session
|
||||
import Shared
|
||||
import Time
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
type Msg
|
||||
= NoOp
|
||||
|
||||
|
||||
type alias RouteParams =
|
||||
{}
|
||||
|
||||
|
||||
route : StatefulRoute RouteParams Data ActionData Model Msg
|
||||
route =
|
||||
RouteBuilder.serverRender
|
||||
{ head = head
|
||||
, data = data
|
||||
, action = action
|
||||
}
|
||||
|> RouteBuilder.buildWithLocalState
|
||||
{ view = view
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
, init = init
|
||||
}
|
||||
|
||||
|
||||
init :
|
||||
Maybe PageUrl
|
||||
-> Shared.Model
|
||||
-> StaticPayload Data ActionData RouteParams
|
||||
-> ( Model, Effect Msg )
|
||||
init maybePageUrl sharedModel static =
|
||||
( {}, Effect.none )
|
||||
|
||||
|
||||
update :
|
||||
PageUrl
|
||||
-> Shared.Model
|
||||
-> StaticPayload Data ActionData RouteParams
|
||||
-> Msg
|
||||
-> Model
|
||||
-> ( Model, Effect Msg )
|
||||
update pageUrl sharedModel static msg model =
|
||||
case msg of
|
||||
NoOp ->
|
||||
( model, Effect.none )
|
||||
|
||||
|
||||
subscriptions : Maybe PageUrl -> RouteParams -> Path -> Shared.Model -> Model -> Sub Msg
|
||||
subscriptions maybePageUrl routeParams path sharedModel model =
|
||||
Sub.none
|
||||
|
||||
|
||||
type alias Data =
|
||||
{ todos : List Todo
|
||||
}
|
||||
|
||||
|
||||
type alias ActionData =
|
||||
{}
|
||||
|
||||
|
||||
todosByUserId : Int -> SelectionSet (List Todo) RootQuery
|
||||
todosByUserId userId =
|
||||
Api.Query.todos
|
||||
(\optionals ->
|
||||
{ optionals
|
||||
| where_ =
|
||||
Present
|
||||
(Api.InputObject.buildTodos_bool_exp
|
||||
(\whereOptionals ->
|
||||
{ whereOptionals
|
||||
| user_id =
|
||||
Api.InputObject.buildInt_comparison_exp
|
||||
(\intOptionals ->
|
||||
{ intOptionals | eq_ = Present <| userId }
|
||||
)
|
||||
|> Present
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
(SelectionSet.map3 Todo
|
||||
Api.Object.Todos.title
|
||||
Api.Object.Todos.is_completed
|
||||
Api.Object.Todos.id
|
||||
)
|
||||
|
||||
|
||||
data : RouteParams -> Request.Parser (DataSource (Response Data ErrorPage))
|
||||
data routeParams =
|
||||
Request.requestTime
|
||||
|> MySession.expectSessionOrRedirect
|
||||
(\requestTime session ->
|
||||
let
|
||||
maybeUserId : Maybe Int
|
||||
maybeUserId =
|
||||
session
|
||||
|> Session.get "userId"
|
||||
|> Maybe.andThen String.toInt
|
||||
in
|
||||
case maybeUserId of
|
||||
Just userId ->
|
||||
todosByUserId userId
|
||||
|> Request.Hasura.dataSource (requestTime |> Time.posixToMillis |> String.fromInt)
|
||||
|> DataSource.map
|
||||
(\todos ->
|
||||
( session
|
||||
, Response.render { todos = todos }
|
||||
)
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
( session, Route.redirectTo Route.Login )
|
||||
|> DataSource.succeed
|
||||
)
|
||||
|
||||
|
||||
action : RouteParams -> Request.Parser (DataSource (Response ActionData ErrorPage))
|
||||
action routeParams =
|
||||
let
|
||||
userId =
|
||||
1
|
||||
in
|
||||
Request.expectFormPost
|
||||
(\{ field } ->
|
||||
Request.oneOf
|
||||
[ field "newTodo"
|
||||
|> Request.map
|
||||
(\title ->
|
||||
createTodo userId title
|
||||
|> Request.Hasura.mutationDataSource ""
|
||||
|> DataSource.map
|
||||
(\_ -> Response.render {})
|
||||
)
|
||||
, field "deleteId"
|
||||
|> Request.map
|
||||
(\deleteId ->
|
||||
-- TODO use RBAC here in Hasura?
|
||||
deleteTodo userId (deleteId |> String.toInt |> Maybe.withDefault 0)
|
||||
|> Request.Hasura.mutationDataSource ""
|
||||
|> DataSource.map
|
||||
(\_ -> Response.render {})
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
createTodo : Int -> String -> SelectionSet (Maybe ()) Graphql.Operation.RootMutation
|
||||
createTodo userId title =
|
||||
Api.Mutation.insert_todos_one identity
|
||||
{ object =
|
||||
Api.InputObject.buildTodos_insert_input
|
||||
(\optionals ->
|
||||
{ optionals
|
||||
| title = Present title
|
||||
, user_id = Present userId
|
||||
}
|
||||
)
|
||||
}
|
||||
SelectionSet.empty
|
||||
|
||||
|
||||
deleteTodo : Int -> Int -> SelectionSet (Maybe ()) Graphql.Operation.RootMutation
|
||||
deleteTodo userId todoId =
|
||||
Api.Mutation.delete_todos_by_pk { id = todoId }
|
||||
SelectionSet.empty
|
||||
|
||||
|
||||
head :
|
||||
StaticPayload Data ActionData RouteParams
|
||||
-> List Head.Tag
|
||||
head static =
|
||||
Seo.summary
|
||||
{ canonicalUrlOverride = Nothing
|
||||
, siteName = "elm-pages"
|
||||
, image =
|
||||
{ url = Pages.Url.external "TODO"
|
||||
, alt = "elm-pages logo"
|
||||
, dimensions = Nothing
|
||||
, mimeType = Nothing
|
||||
}
|
||||
, description = "TODO"
|
||||
, locale = Nothing
|
||||
, title = "Todo List"
|
||||
}
|
||||
|> Seo.website
|
||||
|
||||
|
||||
view :
|
||||
Maybe PageUrl
|
||||
-> Shared.Model
|
||||
-> Model
|
||||
-> StaticPayload Data ActionData RouteParams
|
||||
-> View (Pages.Msg.Msg Msg)
|
||||
view maybeUrl sharedModel model static =
|
||||
{ title = "Todo List"
|
||||
, body =
|
||||
[ Html.div
|
||||
[ Attr.class "todomvc-wrapper"
|
||||
]
|
||||
[ Html.section
|
||||
[ Attr.class "todoapp"
|
||||
]
|
||||
[ Html.header
|
||||
[ Attr.class "header"
|
||||
]
|
||||
[ Html.h1 []
|
||||
[ Html.text "todos" ]
|
||||
, Html.form
|
||||
[ Attr.method "POST"
|
||||
, Pages.Msg.fetcherOnSubmit
|
||||
]
|
||||
[ Html.input
|
||||
[ Attr.class "new-todo"
|
||||
, Attr.placeholder "What needs to be done?"
|
||||
, Attr.autofocus True
|
||||
, Attr.name "newTodo"
|
||||
]
|
||||
[]
|
||||
, Html.button [] [ Html.text "Create" ]
|
||||
]
|
||||
]
|
||||
, Html.section
|
||||
[ Attr.class "main"
|
||||
, Attr.style "visibility" "visible"
|
||||
]
|
||||
[ Html.input
|
||||
[ Attr.class "toggle-all"
|
||||
, Attr.id "toggle-all"
|
||||
, Attr.type_ "checkbox"
|
||||
, Attr.name "toggle"
|
||||
]
|
||||
[]
|
||||
, Html.label
|
||||
[ Attr.for "toggle-all"
|
||||
]
|
||||
[ Html.text "Mark all as complete" ]
|
||||
, Html.ul
|
||||
[ Attr.class "todo-list"
|
||||
]
|
||||
(static.data.todos
|
||||
|> List.map todoItemView
|
||||
)
|
||||
]
|
||||
, Html.footer
|
||||
[ Attr.class "footer"
|
||||
]
|
||||
[ Html.span
|
||||
[ Attr.class "todo-count"
|
||||
]
|
||||
[ Html.strong []
|
||||
[ Html.text "3" ]
|
||||
, Html.text " items left"
|
||||
]
|
||||
, Html.ul
|
||||
[ Attr.class "filters"
|
||||
]
|
||||
[ Html.li []
|
||||
[ Html.a
|
||||
[ Attr.class "selected"
|
||||
, Attr.href "#/"
|
||||
]
|
||||
[ Html.text "All" ]
|
||||
]
|
||||
, Html.li []
|
||||
[ Html.a
|
||||
[ Attr.class ""
|
||||
, Attr.href "#/active"
|
||||
]
|
||||
[ Html.text "Active" ]
|
||||
]
|
||||
, Html.li []
|
||||
[ Html.a
|
||||
[ Attr.class ""
|
||||
, Attr.href "#/completed"
|
||||
]
|
||||
[ Html.text "Completed" ]
|
||||
]
|
||||
]
|
||||
, Html.button
|
||||
[ Attr.class "clear-completed"
|
||||
, Attr.hidden True
|
||||
]
|
||||
[ Html.text "Clear completed (0)" ]
|
||||
]
|
||||
]
|
||||
, Html.footer
|
||||
[ Attr.class "info"
|
||||
]
|
||||
[ Html.p []
|
||||
[ Html.text "Double-click to edit a todo" ]
|
||||
, Html.p []
|
||||
[ Html.text "Written by "
|
||||
, Html.a
|
||||
[ Attr.href "https://github.com/dillonkearns"
|
||||
]
|
||||
[ Html.text "Dillon Kearns" ]
|
||||
]
|
||||
, Html.p []
|
||||
[ Html.text "Part of "
|
||||
, Html.a
|
||||
[ Attr.href "http://todomvc.com"
|
||||
]
|
||||
[ Html.text "TodoMVC" ]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
type alias Todo =
|
||||
{ title : String
|
||||
, complete : Bool
|
||||
, id : Int
|
||||
}
|
||||
|
||||
|
||||
todoItemView : Todo -> Html (Pages.Msg.Msg Msg)
|
||||
todoItemView todo =
|
||||
Html.li []
|
||||
[ Html.div
|
||||
[ Attr.class "view"
|
||||
, Pages.Msg.fetcherOnSubmit
|
||||
]
|
||||
[ Html.form
|
||||
[ Attr.method "POST"
|
||||
]
|
||||
[ Html.input
|
||||
[ Attr.class "toggle"
|
||||
, Attr.type_ "checkbox"
|
||||
, Attr.checked todo.complete
|
||||
|
||||
--, Html.Events.onCheck (\_ -> Pages.Msg.Submit )
|
||||
]
|
||||
[]
|
||||
, Html.label []
|
||||
[ Html.text todo.title ]
|
||||
]
|
||||
, Html.form [ Attr.method "POST", Pages.Msg.fetcherOnSubmit ]
|
||||
[ Html.button
|
||||
[ Attr.class "destroy"
|
||||
]
|
||||
[]
|
||||
, Html.input [ Attr.type_ "hidden", Attr.name "deleteId", Attr.value (String.fromInt todo.id) ] []
|
||||
]
|
||||
]
|
||||
, Html.input
|
||||
[ Attr.class "edit"
|
||||
, Attr.name "title"
|
||||
|
||||
--, Attr.id "todo-0"
|
||||
]
|
||||
[]
|
||||
]
|
@ -1,80 +1,520 @@
|
||||
@import url("https://rsms.me/inter/inter.css");
|
||||
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap");
|
||||
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;
|
||||
}
|
||||
}
|
||||
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-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Inter var" !important;
|
||||
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-osx-font-smoothing: grayscale;
|
||||
font-weight: 300;
|
||||
width: auto;
|
||||
|
||||
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.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;
|
||||
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-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.toggle-all {
|
||||
text-align: center;
|
||||
border: none; /* Mobile Safari */
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.toggle-all + label {
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
font-size: 0;
|
||||
position: absolute;
|
||||
top: -52px;
|
||||
left: -13px;
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.toggle-all + label:before {
|
||||
content: '❯';
|
||||
font-size: 22px;
|
||||
color: #e6e6e6;
|
||||
padding: 10px 27px 10px 27px;
|
||||
}
|
||||
|
||||
.toggle-all:checked + label: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: 12px 16px;
|
||||
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 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.todo-list li .toggle + label {
|
||||
/*
|
||||
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
|
||||
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
|
||||
*/
|
||||
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center left;
|
||||
}
|
||||
|
||||
.todo-list li .toggle:checked + label {
|
||||
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
|
||||
}
|
||||
|
||||
.todo-list li label {
|
||||
word-break: break-all;
|
||||
padding: 15px 15px 15px 60px;
|
||||
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: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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
input:invalid {
|
||||
border: 2px dashed red;
|
||||
}
|
||||
|
||||
input:valid {
|
||||
border: 2px solid black;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
input[type=password], input[type=text], input[type=date], input[type=email] {
|
||||
border-radius: 10px !important;
|
||||
border-color: #ccc !important;
|
||||
.todo-list li .toggle {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
border-radius: 4px !important;
|
||||
border-color: #ccc !important;
|
||||
}
|
||||
@media (max-width: 430px) {
|
||||
.footer {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
main.color-app {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
|
||||
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 18px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
main.color-app {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 0.8em;
|
||||
padding: 0.8em;
|
||||
}
|
||||
|
||||
main.color-app .content {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 0.5em;
|
||||
max-width: 90vw;
|
||||
padding: 2rem 3rem;
|
||||
width: 475px;
|
||||
color: var(--selected-color);
|
||||
}
|
||||
|
||||
main.color-app .content h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main.color-app .content ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
main.color-app .content li {
|
||||
margin-left: -1rem;
|
||||
margin-right: -1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
main.color-app .content li:nth-child(odd) {
|
||||
background: rgb(100 10 80 / 0.1);
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.filters {
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user