Bump part3 and part4
@@ -1,16 +0,0 @@
|
||||
# Part 3
|
||||
|
||||
To build everything, `cd` into this `part3/` directory and run:
|
||||
|
||||
```shell
|
||||
elm make src/Main.elm --output=../server/public/elm.js
|
||||
```
|
||||
|
||||
Then open [http://localhost:3000](http://localhost:3000) in your browser.
|
||||
|
||||
## Exercise
|
||||
|
||||
Resolve the TODOs in these files:
|
||||
* `src/Page/Home.elm`
|
||||
* `src/Page/Settings.elm`
|
||||
* `src/Page/Article.elm`
|
||||
@@ -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`
|
||||
|
||||
@@ -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) ]
|
||||
]
|
||||
|
||||
13
advanced/part5/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Part 5
|
||||
|
||||
To build everything, `cd` into this `part5/` directory and run:
|
||||
|
||||
```shell
|
||||
elm make src/Main.elm --output=../server/public/elm.js
|
||||
```
|
||||
|
||||
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.
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 736 B After Width: | Height: | Size: 736 B |
|
Before Width: | Height: | Size: 985 B After Width: | Height: | Size: 985 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
@@ -110,11 +110,50 @@ viewArticles timeZone (Model { articles, sources, session }) =
|
||||
FeedSources.selected sources
|
||||
|
||||
pagination =
|
||||
PaginatedList.view ClickedFeedPage articles (limit feedSource)
|
||||
viewPaginatedList 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,8 +84,19 @@ view model =
|
||||
[ viewBanner
|
||||
, div [ class "container page" ]
|
||||
[ div [ class "row" ]
|
||||
[ div [ class "col-md-9" ]
|
||||
(viewFeed model)
|
||||
[ 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-3" ] <|
|
||||
case model.tags of
|
||||
Loaded tags ->
|
||||
@@ -119,25 +130,11 @@ viewBanner =
|
||||
]
|
||||
|
||||
|
||||
{-| 👉 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" ]
|
||||
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))
|
||||
|
||||
|
||||
viewTags : List Tag -> Html Msg
|
||||
@@ -98,10 +98,6 @@ 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
|
||||
@@ -113,7 +109,7 @@ view model =
|
||||
[ h1 [ class "text-xs-center" ] [ text "Your Settings" ]
|
||||
, ul [ class "error-messages" ]
|
||||
(List.map viewProblem model.problems)
|
||||
, form
|
||||
, viewForm cred model.form
|
||||
]
|
||||
]
|
||||
]
|
||||
@@ -124,74 +120,62 @@ view model =
|
||||
}
|
||||
|
||||
|
||||
{-| 👉 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" ]
|
||||
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
|
||||
]
|
||||
[]
|
||||
]
|
||||
, 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, view)
|
||||
module PaginatedList exposing (PaginatedList, fromList, map, mapPage, page, total, values)
|
||||
|
||||
import Html exposing (Html, a, li, text, ul)
|
||||
import Html.Attributes exposing (class, classList, href)
|
||||
@@ -61,38 +61,3 @@ 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) ]
|
||||
]
|
||||