Bump part3 and part4
@@ -10,4 +10,7 @@ Then open [http://localhost:3000](http://localhost:3000) in your browser.
|
||||
|
||||
## Exercise
|
||||
|
||||
Open `src/Article/Feed.elm` in your editor and resolve the TODO there.
|
||||
Resolve the TODOs in these files:
|
||||
* `src/Page/Home.elm`
|
||||
* `src/Page/Settings.elm`
|
||||
* `src/Page/Article.elm`
|
||||
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
|
Before Width: | Height: | Size: 736 B |
|
Before Width: | Height: | Size: 985 B |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M235 6959 c22 -22 365 -365 761 -762 l721 -722 1652 0 c908 -1 1651
|
||||
2 1651 5 0 3 -341 346 -758 763 l-757 757 -1655 0 -1654 0 39 -41z"/>
|
||||
<path d="M3900 6995 c0 -3 698 -704 1550 -1557 l1550 -1551 0 1557 0 1556
|
||||
-1550 0 c-852 0 -1550 -2 -1550 -5z"/>
|
||||
<path d="M0 3495 l0 -3310 1655 1655 c909 910 1653 1655 1652 1657 -5 5 -3235
|
||||
3237 -3269 3270 l-38 37 0 -3309z"/>
|
||||
<path d="M2008 5199 c-5 -3 329 -345 743 -758 l752 -752 753 753 c414 414 751
|
||||
755 748 757 -6 7 -2985 7 -2996 0z"/>
|
||||
<path d="M4522 4327 c-452 -453 -822 -827 -822 -831 0 -4 369 -376 821 -828
|
||||
l821 -821 829 829 829 829 -822 822 c-453 453 -825 823 -828 823 -3 0 -375
|
||||
-370 -828 -823z"/>
|
||||
<path d="M1853 1655 c-904 -905 -1643 -1647 -1643 -1650 0 -3 1484 -5 3297 -5
|
||||
l3298 0 -1650 1650 c-907 908 -1652 1650 -1655 1650 -3 0 -744 -741 -1647
|
||||
-1645z"/>
|
||||
<path d="M6265 2384 c-396 -398 -722 -726 -724 -728 -2 -3 326 -335 728 -737
|
||||
l731 -732 0 1461 c0 804 -3 1462 -7 1461 -5 -1 -332 -327 -728 -725z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 72 KiB |
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"summary": "helpful summary of your project, less than 80 characters",
|
||||
"repository": "https://github.com/user/project.git",
|
||||
"license": "BSD3",
|
||||
"source-directories": [
|
||||
"."
|
||||
],
|
||||
"exposed-modules": [],
|
||||
"dependencies": {
|
||||
"elm-lang/core": "5.1.1 <= v < 6.0.0",
|
||||
"elm-lang/html": "2.0.0 <= v < 3.0.0"
|
||||
},
|
||||
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||
}
|
||||
@@ -110,50 +110,11 @@ viewArticles timeZone (Model { articles, sources, session }) =
|
||||
FeedSources.selected sources
|
||||
|
||||
pagination =
|
||||
viewPaginatedList articles (limit feedSource)
|
||||
PaginatedList.view ClickedFeedPage articles (limit feedSource)
|
||||
in
|
||||
List.append articlesHtml [ pagination ]
|
||||
|
||||
|
||||
{-| 👉 TODO Move this logic into PaginatedList.view and make it reusable,
|
||||
so we can use it on other pages too!
|
||||
💡 HINT: Make `PaginatedList.view` return `Html msg` instead of `Html Msg`. (The function will need to accept an extra argument for this to work.)
|
||||
-}
|
||||
viewPaginatedList : PaginatedList a -> Int -> Html Msg
|
||||
viewPaginatedList paginatedList resultsPerPage =
|
||||
let
|
||||
totalPages =
|
||||
ceiling (toFloat (PaginatedList.total paginatedList) / toFloat resultsPerPage)
|
||||
|
||||
activePage =
|
||||
PaginatedList.page paginatedList
|
||||
|
||||
viewPageLink currentPage =
|
||||
pageLink currentPage (currentPage == activePage)
|
||||
in
|
||||
if totalPages > 1 then
|
||||
List.range 1 totalPages
|
||||
|> List.map viewPageLink
|
||||
|> ul [ class "pagination" ]
|
||||
|
||||
else
|
||||
Html.text ""
|
||||
|
||||
|
||||
pageLink : Int -> Bool -> Html Msg
|
||||
pageLink targetPage isActive =
|
||||
li [ classList [ ( "page-item", True ), ( "active", isActive ) ] ]
|
||||
[ a
|
||||
[ class "page-link"
|
||||
, onClick (ClickedFeedPage targetPage)
|
||||
|
||||
-- The RealWorld CSS requires an href to work properly.
|
||||
, href ""
|
||||
]
|
||||
[ text (String.fromInt targetPage) ]
|
||||
]
|
||||
|
||||
|
||||
viewPreview : Maybe Cred -> Time.Zone -> Article Preview -> Html Msg
|
||||
viewPreview maybeCred timeZone article =
|
||||
let
|
||||
|
||||
@@ -84,19 +84,8 @@ view model =
|
||||
[ viewBanner
|
||||
, div [ class "container page" ]
|
||||
[ div [ class "row" ]
|
||||
[ div [ class "col-md-9" ] <|
|
||||
case model.feed of
|
||||
Loaded feed ->
|
||||
viewFeed model.timeZone feed
|
||||
|
||||
Loading ->
|
||||
[]
|
||||
|
||||
LoadingSlowly ->
|
||||
[ Loading.icon ]
|
||||
|
||||
Failed ->
|
||||
[ Loading.error "feed" ]
|
||||
[ div [ class "col-md-9" ]
|
||||
(viewFeed model)
|
||||
, div [ class "col-md-3" ] <|
|
||||
case model.tags of
|
||||
Loaded tags ->
|
||||
@@ -130,11 +119,25 @@ viewBanner =
|
||||
]
|
||||
|
||||
|
||||
viewFeed : Time.Zone -> Feed.Model -> List (Html Msg)
|
||||
viewFeed timeZone feed =
|
||||
div [ class "feed-toggle" ]
|
||||
[ Feed.viewFeedSources feed |> Html.map GotFeedMsg ]
|
||||
:: (Feed.viewArticles timeZone feed |> List.map (Html.map GotFeedMsg))
|
||||
{-| 👉 TODO refactor this to accept narrower types than the entire Model.
|
||||
💡 HINT: It may end up with multiple arguments!
|
||||
-}
|
||||
viewFeed : Model -> List (Html Msg)
|
||||
viewFeed model =
|
||||
case model.feed of
|
||||
Loaded feed ->
|
||||
div [ class "feed-toggle" ]
|
||||
[ Feed.viewFeedSources feed |> Html.map GotFeedMsg ]
|
||||
:: (Feed.viewArticles model.timeZone feed |> List.map (Html.map GotFeedMsg))
|
||||
|
||||
Loading ->
|
||||
[]
|
||||
|
||||
LoadingSlowly ->
|
||||
[ Loading.icon ]
|
||||
|
||||
Failed ->
|
||||
[ Loading.error "feed" ]
|
||||
|
||||
|
||||
viewTags : List Tag -> Html Msg
|
||||
|
||||
@@ -98,6 +98,10 @@ type ValidForm
|
||||
|
||||
view : Model -> { title : String, content : Html Msg }
|
||||
view model =
|
||||
let
|
||||
form =
|
||||
viewForm model
|
||||
in
|
||||
{ title = "Settings"
|
||||
, content =
|
||||
case Session.cred model.session of
|
||||
@@ -109,7 +113,7 @@ view model =
|
||||
[ h1 [ class "text-xs-center" ] [ text "Your Settings" ]
|
||||
, ul [ class "error-messages" ]
|
||||
(List.map viewProblem model.problems)
|
||||
, viewForm cred model.form
|
||||
, form
|
||||
]
|
||||
]
|
||||
]
|
||||
@@ -120,62 +124,74 @@ view model =
|
||||
}
|
||||
|
||||
|
||||
viewForm : Cred -> Form -> Html Msg
|
||||
viewForm cred form =
|
||||
Html.form [ onSubmit (SubmittedForm cred) ]
|
||||
[ fieldset []
|
||||
[ fieldset [ class "form-group" ]
|
||||
[ input
|
||||
[ class "form-control"
|
||||
, placeholder "URL of profile picture"
|
||||
, value form.avatar
|
||||
, onInput EnteredAvatar
|
||||
{-| 👉 TODO refactor this to accept narrower types than the entire Model.
|
||||
💡 HINT: It may end up with multiple arguments!
|
||||
-}
|
||||
viewForm : Model -> Html Msg
|
||||
viewForm model =
|
||||
let
|
||||
form =
|
||||
model.form
|
||||
in
|
||||
case Session.cred model.session of
|
||||
Nothing ->
|
||||
text ""
|
||||
|
||||
Just cred ->
|
||||
Html.form [ onSubmit (SubmittedForm cred) ]
|
||||
[ fieldset []
|
||||
[ fieldset [ class "form-group" ]
|
||||
[ input
|
||||
[ class "form-control"
|
||||
, placeholder "URL of profile picture"
|
||||
, value form.avatar
|
||||
, onInput EnteredAvatar
|
||||
]
|
||||
[]
|
||||
]
|
||||
, fieldset [ class "form-group" ]
|
||||
[ input
|
||||
[ class "form-control form-control-lg"
|
||||
, placeholder "Username"
|
||||
, value form.username
|
||||
, onInput EnteredUsername
|
||||
]
|
||||
[]
|
||||
]
|
||||
, fieldset [ class "form-group" ]
|
||||
[ textarea
|
||||
[ class "form-control form-control-lg"
|
||||
, placeholder "Short bio about you"
|
||||
, attribute "rows" "8"
|
||||
, value form.bio
|
||||
, onInput EnteredBio
|
||||
]
|
||||
[]
|
||||
]
|
||||
, fieldset [ class "form-group" ]
|
||||
[ input
|
||||
[ class "form-control form-control-lg"
|
||||
, placeholder "Email"
|
||||
, value form.email
|
||||
, onInput EnteredEmail
|
||||
]
|
||||
[]
|
||||
]
|
||||
, fieldset [ class "form-group" ]
|
||||
[ input
|
||||
[ class "form-control form-control-lg"
|
||||
, type_ "password"
|
||||
, placeholder "Password"
|
||||
, value form.password
|
||||
, onInput EnteredPassword
|
||||
]
|
||||
[]
|
||||
]
|
||||
, button
|
||||
[ class "btn btn-lg btn-primary pull-xs-right" ]
|
||||
[ text "Update Settings" ]
|
||||
]
|
||||
[]
|
||||
]
|
||||
, fieldset [ class "form-group" ]
|
||||
[ input
|
||||
[ class "form-control form-control-lg"
|
||||
, placeholder "Username"
|
||||
, value form.username
|
||||
, onInput EnteredUsername
|
||||
]
|
||||
[]
|
||||
]
|
||||
, fieldset [ class "form-group" ]
|
||||
[ textarea
|
||||
[ class "form-control form-control-lg"
|
||||
, placeholder "Short bio about you"
|
||||
, attribute "rows" "8"
|
||||
, value form.bio
|
||||
, onInput EnteredBio
|
||||
]
|
||||
[]
|
||||
]
|
||||
, fieldset [ class "form-group" ]
|
||||
[ input
|
||||
[ class "form-control form-control-lg"
|
||||
, placeholder "Email"
|
||||
, value form.email
|
||||
, onInput EnteredEmail
|
||||
]
|
||||
[]
|
||||
]
|
||||
, fieldset [ class "form-group" ]
|
||||
[ input
|
||||
[ class "form-control form-control-lg"
|
||||
, type_ "password"
|
||||
, placeholder "Password"
|
||||
, value form.password
|
||||
, onInput EnteredPassword
|
||||
]
|
||||
[]
|
||||
]
|
||||
, button
|
||||
[ class "btn btn-lg btn-primary pull-xs-right" ]
|
||||
[ text "Update Settings" ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewProblem : Problem -> Html msg
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module PaginatedList exposing (PaginatedList, fromList, map, mapPage, page, total, values)
|
||||
module PaginatedList exposing (PaginatedList, fromList, map, mapPage, page, total, values, view)
|
||||
|
||||
import Html exposing (Html, a, li, text, ul)
|
||||
import Html.Attributes exposing (class, classList, href)
|
||||
@@ -61,3 +61,38 @@ mapPage transform (PaginatedList info) =
|
||||
|
||||
|
||||
-- VIEW
|
||||
|
||||
|
||||
view : (Int -> msg) -> PaginatedList a -> Int -> Html msg
|
||||
view toMsg list resultsPerPage =
|
||||
let
|
||||
totalPages =
|
||||
ceiling (toFloat (total list) / toFloat resultsPerPage)
|
||||
|
||||
activePage =
|
||||
page list
|
||||
|
||||
viewPageLink currentPage =
|
||||
pageLink toMsg currentPage (currentPage == activePage)
|
||||
in
|
||||
if totalPages > 1 then
|
||||
List.range 1 totalPages
|
||||
|> List.map viewPageLink
|
||||
|> ul [ class "pagination" ]
|
||||
|
||||
else
|
||||
Html.text ""
|
||||
|
||||
|
||||
pageLink : (Int -> msg) -> Int -> Bool -> Html msg
|
||||
pageLink toMsg targetPage isActive =
|
||||
li [ classList [ ( "page-item", True ), ( "active", isActive ) ] ]
|
||||
[ a
|
||||
[ class "page-link"
|
||||
, onClick (toMsg targetPage)
|
||||
|
||||
-- The RealWorld CSS requires an href to work properly.
|
||||
, href ""
|
||||
]
|
||||
[ text (String.fromInt targetPage) ]
|
||||
]
|
||||
|
||||