diff --git a/part2/elm-package.json b/part2/elm-package.json index 2a97361..7c979f1 100644 --- a/part2/elm-package.json +++ b/part2/elm-package.json @@ -19,7 +19,7 @@ "evancz/url-parser": "2.0.1 <= v < 3.0.0", "lukewestby/elm-http-builder": "5.1.0 <= v < 6.0.0", "mgold/elm-date-format": "1.3.0 <= v < 2.0.0", - "rtfeldman/elm-validate": "2.0.0 <= v < 3.0.0", + "rtfeldman/elm-validate": "3.0.0 <= v < 4.0.0", "rtfeldman/selectlist": "1.0.0 <= v < 2.0.0" }, "elm-version": "0.18.0 <= v < 0.19.0" diff --git a/part2/elm-stuff/exact-dependencies.json b/part2/elm-stuff/exact-dependencies.json index 0298d1a..77b48b4 100644 --- a/part2/elm-stuff/exact-dependencies.json +++ b/part2/elm-stuff/exact-dependencies.json @@ -1,5 +1,5 @@ { - "rtfeldman/elm-validate": "2.0.0", + "rtfeldman/elm-validate": "3.0.0", "rtfeldman/selectlist": "1.0.0", "elm-lang/navigation": "2.1.0", "elm-lang/virtual-dom": "2.0.4", diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/.gitignore b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/.gitignore similarity index 100% rename from part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/.gitignore rename to part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/.gitignore diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/CHANGELOG.md b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/CHANGELOG.md similarity index 100% rename from part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/CHANGELOG.md rename to part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/CHANGELOG.md diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/LICENSE b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/LICENSE similarity index 100% rename from part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/LICENSE rename to part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/LICENSE diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/README.md b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/README.md similarity index 86% rename from part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/README.md rename to part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/README.md index be1baf7..423e1b7 100644 --- a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/README.md +++ b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/README.md @@ -16,7 +16,7 @@ type alias Model = { name : String, email : String, age : String, selections : List String } -modelValidator : Validator Model String +modelValidator : Validator String Model modelValidator = Validate.all [ ifBlank .name "Please enter a name." @@ -28,8 +28,7 @@ modelValidator = validate modelValidator { name = "Sam", email = "", age = "abc", selections = [ "cats" ] } - == [ "Please enter an email address.", "Age must be a whole number." ] - --> True + --> [ "Please enter an email address.", "Age must be a whole number." ] ``` You can represent your errors however you like. One nice approach is to use @@ -56,8 +55,7 @@ type alias Model = validate modelValidator { name = "Sam", email = "", age = "abc", selections = [ "cats" ] } - == [ ( Email, "Please enter an email address." ) - , ( Age, "Age must be a whole number." ) - ] - --> True + --> [ ( Email, "Please enter an email address." ) + --> , ( Age, "Age must be a whole number." ) + --> ] ``` diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/elm-package.json b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/elm-package.json similarity index 94% rename from part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/elm-package.json rename to part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/elm-package.json index 395c724..3afc2ea 100644 --- a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/elm-package.json +++ b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/elm-package.json @@ -1,5 +1,5 @@ { - "version": "2.0.0", + "version": "3.0.0", "summary": "Convenience functions for validating data.", "repository": "https://github.com/rtfeldman/elm-validate.git", "license": "BSD-3-Clause", diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/examples/SignupForm.elm b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/SignupForm.elm similarity index 100% rename from part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/examples/SignupForm.elm rename to part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/SignupForm.elm diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/examples/elm-package.json b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/elm-package.json similarity index 100% rename from part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/examples/elm-package.json rename to part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/elm-package.json diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/src/Validate.elm b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/src/Validate.elm similarity index 75% rename from part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/src/Validate.elm rename to part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/src/Validate.elm index 211abfa..4d1b4a1 100644 --- a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/src/Validate.elm +++ b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/src/Validate.elm @@ -4,6 +4,7 @@ module Validate , all , any , firstError + , fromErrors , ifBlank , ifEmptyDict , ifEmptyList @@ -21,7 +22,7 @@ module Validate {-| Convenience functions for validating data. - import Validate exposing (ifBlank, ifNotInt, validate) + import Validate exposing (Validator, ifBlank, ifNotInt, validate) type Field = Name | Email | Age @@ -49,7 +50,7 @@ module Validate # Creating validators -@docs ifBlank, ifNotInt, ifEmptyList, ifEmptyDict, ifEmptySet, ifNothing, ifInvalidEmail, ifTrue, ifFalse +@docs ifBlank, ifNotInt, ifEmptyList, ifEmptyDict, ifEmptySet, ifNothing, ifInvalidEmail, ifTrue, ifFalse, fromErrors # Combining validators @@ -86,7 +87,7 @@ type Validator error subject {-| Return an error if the given predicate returns `True` for the given subject. - import Validate exposing (ifBlank, ifNotInt, validate) + import Validate exposing (Validator, ifBlank, ifNotInt, validate) type Field = Name | Email | Age @@ -116,7 +117,7 @@ validate (Validator getErrors) subject = {-| Return an error if the given `String` is empty, or if it contains only whitespace characters. - import Validate exposing (ifBlank, ifNotInt) + import Validate exposing (Validator, ifBlank) modelValidator : Validator Model String modelValidator = @@ -132,10 +133,31 @@ ifBlank subjectToString error = {-| Return an error if the given `String` cannot be parsed as an `Int`. + + import Validate exposing (Validator, ifNotInt) + + modelValidator : Validator Model String + modelValidator = + Validate.all + [ ifNotInt .followers (\_ -> "Please enter a whole number for followers.") + , ifNotInt .stars (\stars -> "Stars was \"" ++ stars ++ "\", but it needs to be a whole number.")" + ] + -} -ifNotInt : (subject -> String) -> error -> Validator error subject -ifNotInt subjectToString error = - ifFalse (\subject -> isInt (subjectToString subject)) error +ifNotInt : (subject -> String) -> (String -> error) -> Validator error subject +ifNotInt subjectToString errorFromString = + let + getErrors subject = + let + str = + subjectToString subject + in + if isInt str then + [] + else + [ errorFromString str ] + in + Validator getErrors {-| Return an error if a `List` is empty. @@ -167,16 +189,65 @@ ifNothing subjectToMaybe error = {-| Return an error if an email address is malformed. + + import Validate exposing (Validator, ifBlank, ifNotInt) + + modelValidator : Validator Model String + modelValidator = + Validate.all + [ ifInvalidEmail .primaryEmail (\_ -> "Please enter a valid primary email address.") + , ifInvalidEmail .superSecretEmail (\email -> "Unfortunately, \"" ++ email ++ "\" is not a valid Super Secret Email Address.") + ] + -} -ifInvalidEmail : (subject -> String) -> error -> Validator error subject -ifInvalidEmail subjectToEmail error = - ifFalse (\subject -> isValidEmail (subjectToEmail subject)) error +ifInvalidEmail : (subject -> String) -> (String -> error) -> Validator error subject +ifInvalidEmail subjectToEmail errorFromEmail = + let + getErrors subject = + let + email = + subjectToEmail subject + in + if isValidEmail email then + [] + else + [ errorFromEmail email ] + in + Validator getErrors + + +{-| Create a custom validator, by providing a function that returns a list of +errors given a subject. + + import Validate exposing (Validator, fromErrors) + + modelValidator : Validator Model String + modelValidator = + fromErrors modelToErrors + + modelToErrors : Model -> List String + modelToErrors model = + let + usernameLength = + String.length model.username + in + if usernameLength < minUsernameChars then + [ "Username not long enough" ] + else if usernameLength > maxUsernameChars then + [ "Username too long" ] + else + [] + +-} +fromErrors : (subject -> List error) -> Validator error subject +fromErrors toErrors = + Validator toErrors {-| Return an error if a predicate returns `True` for the given subject. - import Validate exposing (ifTrue) + import Validate exposing (Validator, ifTrue) modelValidator : Validator Model String modelValidator = @@ -199,7 +270,7 @@ ifTrue test error = {-| Return an error if a predicate returns `False` for the given subject. - import Validate exposing (ifFalse) + import Validate exposing (Validator, ifFalse) modelValidator : Validator Model String modelValidator = @@ -226,7 +297,7 @@ ifFalse test error = {-| Run each of the given validators, in order, and return their concatenated error lists. - import Validate exposing (ifBlank, ifNotInt) + import Validate exposing (Validator, ifBlank, ifNotInt) modelValidator : Validator Model String modelValidator = @@ -253,7 +324,7 @@ all validators = {-| Run each of the given validators, in order, stopping after the first error and returning it. If no errors are encountered, return `Nothing`. - import Validate exposing (ifBlank, ifInvalidEmail, ifNotInt) + import Validate exposing (Validator, ifBlank, ifInvalidEmail, ifNotInt) type alias Model = @@ -337,7 +408,7 @@ isBlank str = Regex.contains lacksNonWhitespaceChars str -{-| Returns `True` if the email is malformed. +{-| Returns `True` if the email is valid. [`ifInvalidEmail`](#ifInvalidEmail) uses this under the hood. diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/tests/ValidateTests.elm b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/ValidateTests.elm similarity index 100% rename from part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/tests/ValidateTests.elm rename to part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/ValidateTests.elm diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/tests/elm-package.json b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-package.json similarity index 100% rename from part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/tests/elm-package.json rename to part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-package.json diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/tests/elm-verify-examples.json b/part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-verify-examples.json similarity index 100% rename from part3/elm-stuff/packages/rtfeldman/elm-validate/2.0.0/tests/elm-verify-examples.json rename to part2/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-verify-examples.json diff --git a/part2/src/Data/Article/Author.elm b/part2/src/Data/Article/Author.elm index ab8f108..a632508 100644 --- a/part2/src/Data/Article/Author.elm +++ b/part2/src/Data/Article/Author.elm @@ -3,7 +3,7 @@ module Data.Article.Author exposing (Author, decoder) import Data.User as User exposing (Username) import Data.UserPhoto as UserPhoto exposing (UserPhoto) import Json.Decode as Decode exposing (Decoder) -import Json.Decode.Pipeline exposing (custom, decode, required) +import Json.Decode.Pipeline exposing (custom, decode, optional, required) decoder : Decoder Author @@ -12,7 +12,7 @@ decoder = |> required "username" User.usernameDecoder |> required "bio" (Decode.nullable Decode.string) |> required "image" UserPhoto.decoder - |> required "following" Decode.bool + |> optional "following" Decode.bool False type alias Author = diff --git a/part2/src/Data/User.elm b/part2/src/Data/User.elm index 40f6338..aa60a0c 100644 --- a/part2/src/Data/User.elm +++ b/part2/src/Data/User.elm @@ -4,7 +4,7 @@ import Data.AuthToken as AuthToken exposing (AuthToken) import Data.UserPhoto as UserPhoto exposing (UserPhoto) import Html exposing (Html) import Json.Decode as Decode exposing (Decoder) -import Json.Decode.Pipeline exposing (decode, required) +import Json.Decode.Pipeline exposing (decode, optional, required) import Json.Encode as Encode exposing (Value) import Json.Encode.Extra as EncodeExtra import UrlParser @@ -16,8 +16,6 @@ type alias User = , username : Username , bio : Maybe String , image : UserPhoto - , createdAt : String - , updatedAt : String } @@ -33,8 +31,6 @@ decoder = |> required "username" usernameDecoder |> required "bio" (Decode.nullable Decode.string) |> required "image" UserPhoto.decoder - |> required "createdAt" Decode.string - |> required "updatedAt" Decode.string encode : User -> Value @@ -45,8 +41,6 @@ encode user = , ( "username", encodeUsername user.username ) , ( "bio", EncodeExtra.maybe Encode.string user.bio ) , ( "image", UserPhoto.encode user.image ) - , ( "createdAt", Encode.string user.createdAt ) - , ( "updatedAt", Encode.string user.updatedAt ) ] diff --git a/part2/src/Data/UserPhoto.elm b/part2/src/Data/UserPhoto.elm index a97715c..bf7f768 100644 --- a/part2/src/Data/UserPhoto.elm +++ b/part2/src/Data/UserPhoto.elm @@ -39,7 +39,15 @@ photoToUrl : UserPhoto -> String photoToUrl (UserPhoto maybeUrl) = case maybeUrl of Nothing -> - "https://static.productionready.io/images/smiley-cyrus.jpg" + defaultPhotoUrl + + Just "" -> + defaultPhotoUrl Just url -> url + + +defaultPhotoUrl : String +defaultPhotoUrl = + "/assets/images/smiley-cyrus.jpg" diff --git a/part2/src/Page/Article/Editor.elm b/part2/src/Page/Article/Editor.elm index bb1c579..8ba8e27 100644 --- a/part2/src/Page/Article/Editor.elm +++ b/part2/src/Page/Article/Editor.elm @@ -4,7 +4,7 @@ import Data.Article as Article exposing (Article, Body) import Data.Session exposing (Session) import Data.User exposing (User) import Html exposing (..) -import Html.Attributes exposing (attribute, class, disabled, href, id, placeholder, type_, value) +import Html.Attributes exposing (attribute, class, defaultValue, disabled, href, id, placeholder, type_) import Html.Events exposing (onInput, onSubmit) import Http import Page.Errored exposing (PageLoadError, pageLoadError) @@ -102,26 +102,26 @@ viewForm model = [ class "form-control-lg" , placeholder "Article Title" , onInput SetTitle - , value model.title + , defaultValue model.title ] [] , Form.input [ placeholder "What's this article about?" , onInput SetDescription - , value model.description + , defaultValue model.description ] [] , Form.textarea [ placeholder "Write your article (in markdown)" , attribute "rows" "8" , onInput SetBody - , value model.body + , defaultValue model.body ] [] , Form.input [ placeholder "Enter tags" , onInput SetTags - , value (String.join " " model.tags) + , defaultValue (String.join " " model.tags) ] [] , button [ class "btn btn-lg pull-xs-right btn-primary", disabled model.isSaving ] diff --git a/part2/src/Page/Register.elm b/part2/src/Page/Register.elm index 3349060..e1e09a2 100644 --- a/part2/src/Page/Register.elm +++ b/part2/src/Page/Register.elm @@ -186,10 +186,23 @@ modelValidator = Validate.all [ ifBlank .username ( Username, "username can't be blank." ) , ifBlank .email ( Email, "email can't be blank." ) - , ifBlank .password ( Password, "password can't be blank." ) + , Validate.fromErrors passwordLength ] +minPasswordChars : Int +minPasswordChars = + 6 + + +passwordLength : Model -> List Error +passwordLength { password } = + if String.length password < minPasswordChars then + [ ( Password, "password must be at least " ++ toString minPasswordChars ++ " characters long." ) ] + else + [] + + errorsDecoder : Decoder (List String) errorsDecoder = decode (\email username password -> List.concat [ email, username, password ]) diff --git a/part2/src/Views/Article.elm b/part2/src/Views/Article.elm index 180b1ec..c1f229b 100644 --- a/part2/src/Views/Article.elm +++ b/part2/src/Views/Article.elm @@ -7,12 +7,22 @@ import Data.Article exposing (Article) import Data.UserPhoto as UserPhoto exposing (UserPhoto) import Date.Format import Html exposing (..) -import Html.Attributes exposing (attribute, class, classList, href, id, src) +import Html.Attributes exposing (attribute, class, classList, href, id, placeholder, src) import Route exposing (Route) import Views.Article.Favorite as Favorite import Views.Author +-- VIEWS -- + + +{-| Some pages want to view just the timestamp, not the whole article. +-} +viewTimestamp : Article a -> Html msg +viewTimestamp article = + span [ class "date" ] [ text (formattedTimestamp article) ] + + view : (Article a -> msg) -> Article a -> Html msg view toggleFavorite article = let @@ -25,7 +35,7 @@ view toggleFavorite article = [ img [ UserPhoto.src author.image ] [] ] , div [ class "info" ] [ Views.Author.view author.username - , viewTimestamp article + , span [ class "date" ] [ text (formattedTimestamp article) ] ] , Favorite.button toggleFavorite @@ -41,9 +51,8 @@ view toggleFavorite article = ] -viewTimestamp : Article a -> Html msg -viewTimestamp article = - span [ class "date" ] [ text (formattedTimestamp article) ] + +-- INTERNAL -- formattedTimestamp : Article a -> String diff --git a/part3/elm-package.json b/part3/elm-package.json index 2a97361..7c979f1 100644 --- a/part3/elm-package.json +++ b/part3/elm-package.json @@ -19,7 +19,7 @@ "evancz/url-parser": "2.0.1 <= v < 3.0.0", "lukewestby/elm-http-builder": "5.1.0 <= v < 6.0.0", "mgold/elm-date-format": "1.3.0 <= v < 2.0.0", - "rtfeldman/elm-validate": "2.0.0 <= v < 3.0.0", + "rtfeldman/elm-validate": "3.0.0 <= v < 4.0.0", "rtfeldman/selectlist": "1.0.0 <= v < 2.0.0" }, "elm-version": "0.18.0 <= v < 0.19.0" diff --git a/part3/elm-stuff/exact-dependencies.json b/part3/elm-stuff/exact-dependencies.json index 0298d1a..77b48b4 100644 --- a/part3/elm-stuff/exact-dependencies.json +++ b/part3/elm-stuff/exact-dependencies.json @@ -1,5 +1,5 @@ { - "rtfeldman/elm-validate": "2.0.0", + "rtfeldman/elm-validate": "3.0.0", "rtfeldman/selectlist": "1.0.0", "elm-lang/navigation": "2.1.0", "elm-lang/virtual-dom": "2.0.4", diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/.gitignore b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/.gitignore new file mode 100644 index 0000000..572b5fa --- /dev/null +++ b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +# Elm packages and build artifacts +elm-stuff diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/CHANGELOG.md b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/CHANGELOG.md new file mode 100644 index 0000000..9030e8f --- /dev/null +++ b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/CHANGELOG.md @@ -0,0 +1,5 @@ +## Releases +| Version | Notes | +| ------- | ----- | +| [**2.0.0**](https://github.com/rtfeldman/elm-validate/tree/1.0.0) | Change Validator to be a union type, replace `ifInvalid` with `ifTrue` and `ifFalse`, rearrange arguments of `ifBlank` and similar functions, replace `eager` with `firstError`, and expose `isBlank`, `isInt`, and `isValidEmail`. +| [**1.0.0**](https://github.com/rtfeldman/elm-validate/tree/1.0.0) | Initial Release diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/LICENSE b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/LICENSE new file mode 100644 index 0000000..af711e0 --- /dev/null +++ b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2015, Richard Feldman +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of elm-validate nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/README.md b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/README.md new file mode 100644 index 0000000..423e1b7 --- /dev/null +++ b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/README.md @@ -0,0 +1,61 @@ +# elm-validate + +`elm-validate` provides convenience functions for validating data. + +It is based around the idea of a `Validator`, which runs checks on a +subject and returns a list of errors representing anything invalid about +that subject. If the list is empty, the subject is valid. + +For example: + +```elm +import Validate exposing (ifBlank, ifNotInt, validate) + + +type alias Model = + { name : String, email : String, age : String, selections : List String } + + +modelValidator : Validator String Model +modelValidator = + Validate.all + [ ifBlank .name "Please enter a name." + , ifBlank .email "Please enter an email address." + , ifNotInt .age "Age must be a whole number." + , ifEmptyList .selections "Please select at least one." + ] + + +validate modelValidator + { name = "Sam", email = "", age = "abc", selections = [ "cats" ] } + --> [ "Please enter an email address.", "Age must be a whole number." ] +``` + +You can represent your errors however you like. One nice approach is to use +tuple of the error message and the field responsible for the error: + +```elm +type Field = + Name | Email | Age | Selections + + +modelValidator : Validator ( Field, String ) Model +modelValidator = + Validate.all + [ ifBlank .name ( Name, "Please enter a name." ) + , ifBlank .email ( Email, "Please enter an email address." ) + , ifNotInt .age ( Age, "Age must be a whole number." ) + , ifEmptyList .selections ( Selections, "Please select at least one." ) + ] + + +type alias Model = + { name : String, email : String, age : String } + + +validate modelValidator + { name = "Sam", email = "", age = "abc", selections = [ "cats" ] } + --> [ ( Email, "Please enter an email address." ) + --> , ( Age, "Age must be a whole number." ) + --> ] +``` diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/elm-package.json b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/elm-package.json new file mode 100644 index 0000000..3afc2ea --- /dev/null +++ b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/elm-package.json @@ -0,0 +1,16 @@ +{ + "version": "3.0.0", + "summary": "Convenience functions for validating data.", + "repository": "https://github.com/rtfeldman/elm-validate.git", + "license": "BSD-3-Clause", + "source-directories": [ + "src" + ], + "exposed-modules": [ + "Validate" + ], + "dependencies": { + "elm-lang/core": "5.0.0 <= v < 6.0.0" + }, + "elm-version": "0.18.0 <= v < 0.19.0" +} diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/SignupForm.elm b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/SignupForm.elm new file mode 100644 index 0000000..6514a54 --- /dev/null +++ b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/SignupForm.elm @@ -0,0 +1,36 @@ +module SignupForm exposing (..) + +import Validate exposing (Validator, ifBlank, ifEmptyList, ifInvalidEmail, ifNotInt, validate) + + +type alias Model = + { name : String, email : String, age : String, selections : List Float } + + +type Field + = Name + | Email + | Age + | Selections + + +modelValidator : Validator ( Field, String ) Model +modelValidator = + Validate.all + [ ifBlank .name ( Name, "Please enter a name." ) + , Validate.firstError + [ ifBlank .email ( Email, "Please enter an email address." ) + , ifInvalidEmail .email ( Email, "This is not a valid email address." ) + ] + , ifNotInt .age ( Age, "Age must be a whole number." ) + , ifEmptyList .selections ( Selections, "Please select at least one." ) + ] + + +result : Bool +result = + validate modelValidator + { name = "Sam", email = "", age = "abc", selections = [ 1.2 ] } + == [ ( Email, "Please enter an email address." ) + , ( Age, "Age must be a whole number." ) + ] diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/elm-package.json b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/elm-package.json new file mode 100644 index 0000000..1d4a10a --- /dev/null +++ b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/elm-package.json @@ -0,0 +1,15 @@ +{ + "version": "1.0.0", + "summary": "Examples for elm-validate", + "repository": "https://github.com/rtfeldman/elm-validate.git", + "license": "BSD-3-Clause", + "source-directories": [ + "../src", + "." + ], + "exposed-modules": [], + "dependencies": { + "elm-lang/core": "5.0.0 <= v < 6.0.0" + }, + "elm-version": "0.18.0 <= v < 0.19.0" +} diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/src/Validate.elm b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/src/Validate.elm new file mode 100644 index 0000000..4d1b4a1 --- /dev/null +++ b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/src/Validate.elm @@ -0,0 +1,448 @@ +module Validate + exposing + ( Validator + , all + , any + , firstError + , fromErrors + , ifBlank + , ifEmptyDict + , ifEmptyList + , ifEmptySet + , ifFalse + , ifInvalidEmail + , ifNotInt + , ifNothing + , ifTrue + , isBlank + , isInt + , isValidEmail + , validate + ) + +{-| Convenience functions for validating data. + + import Validate exposing (Validator, ifBlank, ifNotInt, validate) + + type Field = Name | Email | Age + + type alias Model = { name : String, email : String, age : String } + + modelValidator : Validator String Model + modelValidator = + Validate.all + [ ifBlank .name "Please enter a name." + , Validate.firstError + [ ifBlank .email "Please enter an email address." + , ifInvalidEmail .email "This is not a valid email address." + ] + , ifNotInt .age "Age must be a whole number." + ] + + validate modelValidator { name = "Sam", email = "blah", age = "abc" } + --> [ "This is not a valid email address.", "Age must be a whole number." ] + + +# Validating a subject + +@docs Validator, validate + + +# Creating validators + +@docs ifBlank, ifNotInt, ifEmptyList, ifEmptyDict, ifEmptySet, ifNothing, ifInvalidEmail, ifTrue, ifFalse, fromErrors + + +# Combining validators + +@docs all, any, firstError + + +# Checking values directly + +@docs isBlank, isInt, isValidEmail + +-} + +import Dict exposing (Dict) +import Regex exposing (Regex) +import Set exposing (Set) +import String + + +-- VALIDATING A SUBJECT -- + + +{-| A `Validator` contains a function which takes a subject and returns a list +of errors describing anything invalid about that subject. + +Pass it to [`validate`](#validate) to get the list of errors. +An empty error list means the subject was valid. + +-} +type Validator error subject + = Validator (subject -> List error) + + +{-| Return an error if the given predicate returns `True` for the given +subject. + + import Validate exposing (Validator, ifBlank, ifNotInt, validate) + + type Field = Name | Email | Age + + type alias Model = { name : String, email : String, age : String } + + modelValidator : Validator ( Field, String ) Model + modelValidator = + Validate.all + [ ifBlank .name ( Name, "Please enter a name." ) + , ifBlank .email ( Email, "Please enter an email address." ) + , ifNotInt .age ( Age, "Age must be a whole number." ) + ] + + validate modelValidator { name = "Sam", email = "", age = "abc" } + --> [ ( Email, "Please enter an email address." ), ( Age, "Age must be a whole number." ) ] + +-} +validate : Validator error subject -> subject -> List error +validate (Validator getErrors) subject = + getErrors subject + + + +-- CONSTRUCTING VALIDATORS -- + + +{-| Return an error if the given `String` is empty, or if it contains only +whitespace characters. + + import Validate exposing (Validator, ifBlank) + + modelValidator : Validator Model String + modelValidator = + Validate.all + [ ifBlank .name "Please enter a name." + , ifBlank .email "Please enter an email address." + ] + +-} +ifBlank : (subject -> String) -> error -> Validator error subject +ifBlank subjectToString error = + ifTrue (\subject -> isBlank (subjectToString subject)) error + + +{-| Return an error if the given `String` cannot be parsed as an `Int`. + + import Validate exposing (Validator, ifNotInt) + + modelValidator : Validator Model String + modelValidator = + Validate.all + [ ifNotInt .followers (\_ -> "Please enter a whole number for followers.") + , ifNotInt .stars (\stars -> "Stars was \"" ++ stars ++ "\", but it needs to be a whole number.")" + ] + +-} +ifNotInt : (subject -> String) -> (String -> error) -> Validator error subject +ifNotInt subjectToString errorFromString = + let + getErrors subject = + let + str = + subjectToString subject + in + if isInt str then + [] + else + [ errorFromString str ] + in + Validator getErrors + + +{-| Return an error if a `List` is empty. +-} +ifEmptyList : (subject -> List a) -> error -> Validator error subject +ifEmptyList subjectToList error = + ifTrue (\subject -> List.isEmpty (subjectToList subject)) error + + +{-| Return an error if a `Dict` is empty. +-} +ifEmptyDict : (subject -> Dict comparable v) -> error -> Validator error subject +ifEmptyDict subjectToDict error = + ifTrue (\subject -> Dict.isEmpty (subjectToDict subject)) error + + +{-| Return an error if a `Set` is empty. +-} +ifEmptySet : (subject -> Set comparable) -> error -> Validator error subject +ifEmptySet subjectToSet error = + ifTrue (\subject -> Set.isEmpty (subjectToSet subject)) error + + +{-| Return an error if a `Maybe` is `Nothing`. +-} +ifNothing : (subject -> Maybe a) -> error -> Validator error subject +ifNothing subjectToMaybe error = + ifTrue (\subject -> subjectToMaybe subject == Nothing) error + + +{-| Return an error if an email address is malformed. + + import Validate exposing (Validator, ifBlank, ifNotInt) + + modelValidator : Validator Model String + modelValidator = + Validate.all + [ ifInvalidEmail .primaryEmail (\_ -> "Please enter a valid primary email address.") + , ifInvalidEmail .superSecretEmail (\email -> "Unfortunately, \"" ++ email ++ "\" is not a valid Super Secret Email Address.") + ] + +-} +ifInvalidEmail : (subject -> String) -> (String -> error) -> Validator error subject +ifInvalidEmail subjectToEmail errorFromEmail = + let + getErrors subject = + let + email = + subjectToEmail subject + in + if isValidEmail email then + [] + else + [ errorFromEmail email ] + in + Validator getErrors + + +{-| Create a custom validator, by providing a function that returns a list of +errors given a subject. + + import Validate exposing (Validator, fromErrors) + + modelValidator : Validator Model String + modelValidator = + fromErrors modelToErrors + + modelToErrors : Model -> List String + modelToErrors model = + let + usernameLength = + String.length model.username + in + if usernameLength < minUsernameChars then + [ "Username not long enough" ] + else if usernameLength > maxUsernameChars then + [ "Username too long" ] + else + [] + +-} +fromErrors : (subject -> List error) -> Validator error subject +fromErrors toErrors = + Validator toErrors + + +{-| Return an error if a predicate returns `True` for the given +subject. + + import Validate exposing (Validator, ifTrue) + + modelValidator : Validator Model String + modelValidator = + ifTrue (\model -> countSelected model < 2) + "Please select at least two." + +-} +ifTrue : (subject -> Bool) -> error -> Validator error subject +ifTrue test error = + let + getErrors subject = + if test subject then + [ error ] + else + [] + in + Validator getErrors + + +{-| Return an error if a predicate returns `False` for the given +subject. + + import Validate exposing (Validator, ifFalse) + + modelValidator : Validator Model String + modelValidator = + ifFalse (\model -> countSelected model >= 2) + "Please select at least two." + +-} +ifFalse : (subject -> Bool) -> error -> Validator error subject +ifFalse test error = + let + getErrors subject = + if test subject then + [] + else + [ error ] + in + Validator getErrors + + + +-- COMBINING VALIDATORS -- + + +{-| Run each of the given validators, in order, and return their concatenated +error lists. + + import Validate exposing (Validator, ifBlank, ifNotInt) + + modelValidator : Validator Model String + modelValidator = + Validate.all + [ ifBlank .name "Please enter a name." + , ifBlank .email "Please enter an email address." + , ifNotInt .age "Age must be a whole number." + ] + +-} +all : List (Validator error subject) -> Validator error subject +all validators = + let + newGetErrors subject = + let + accumulateErrors (Validator getErrors) totalErrors = + totalErrors ++ getErrors subject + in + List.foldl accumulateErrors [] validators + in + Validator newGetErrors + + +{-| Run each of the given validators, in order, stopping after the first error +and returning it. If no errors are encountered, return `Nothing`. + + import Validate exposing (Validator, ifBlank, ifInvalidEmail, ifNotInt) + + + type alias Model = + { email : String, age : String } + + + modelValidator : Validator String Model + modelValidator = + Validate.all + [ Validate.firstError + [ ifBlank .email "Please enter an email address." + , ifInvalidEmail .email "This is not a valid email address." + ] + , ifNotInt .age "Age must be a whole number." + ] + + + validate modelValidator { email = " ", age = "5" } + --> [ "Please enter an email address." ] + + validate modelValidator { email = "blah", age = "5" } + --> [ "This is not a valid email address." ] + + validate modelValidator { email = "foo@bar.com", age = "5" } + --> [] + +-} +firstError : List (Validator error subject) -> Validator error subject +firstError validators = + let + getErrors subject = + firstErrorHelp validators subject + in + Validator getErrors + + +firstErrorHelp : List (Validator error subject) -> subject -> List error +firstErrorHelp validators subject = + case validators of + [] -> + [] + + (Validator getErrors) :: rest -> + case getErrors subject of + [] -> + firstErrorHelp rest subject + + errors -> + errors + + +{-| Return `True` if none of the given validators returns any errors for the given +subject, and `False` if any validator returns one or more errors. +-} +any : List (Validator error subject) -> subject -> Bool +any validators subject = + case validators of + [] -> + True + + (Validator getErrors) :: others -> + case getErrors subject of + [] -> + any others subject + + error :: _ -> + False + + + +-- CHECKING VALUES DIRECTLY -- + + +{-| Returns `True` if the given string is nothing but whitespace. + +[`ifBlank`](#ifBlank) uses this under the hood. + +-} +isBlank : String -> Bool +isBlank str = + Regex.contains lacksNonWhitespaceChars str + + +{-| Returns `True` if the email is valid. + +[`ifInvalidEmail`](#ifInvalidEmail) uses this under the hood. + +-} +isValidEmail : String -> Bool +isValidEmail email = + Regex.contains validEmail email + + +{-| Returns `True` if `String.toInt` on the given string returns an `Ok`. + +[`ifNotInt`](#ifNotInt) uses this under the hood. + +-} +isInt : String -> Bool +isInt str = + case String.toInt str of + Ok _ -> + True + + Err _ -> + False + + + +-- INTERNAL HELPERS -- + + +lacksNonWhitespaceChars : Regex +lacksNonWhitespaceChars = + Regex.regex "^\\s*$" + + +validEmail : Regex +validEmail = + Regex.regex "^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" + |> Regex.caseInsensitive diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/ValidateTests.elm b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/ValidateTests.elm new file mode 100644 index 0000000..f44ae7e --- /dev/null +++ b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/ValidateTests.elm @@ -0,0 +1,52 @@ +module ValidateTests exposing (..) + +import Expect exposing (Expectation) +import Fuzz exposing (Fuzzer, int, list, string) +import Test exposing (..) +import Validate + + +blankness : Test +blankness = + describe "blankness" + [ test "empty string is blank" <| + \() -> + "" + |> Validate.isBlank + |> Expect.true "Validate.isBlank should have considered empty string blank" + , fuzz whitespace "whitespace characters are blank" <| + \str -> + str + |> Validate.isBlank + |> Expect.true "Validate.isBlank should consider whitespace blank" + , fuzz2 whitespace whitespace "non-whitespace characters mean it's not blank" <| + \prefix suffix -> + (prefix ++ "_" ++ suffix) + |> Validate.isBlank + |> Expect.false "Validate.isBlank shouldn't consider strings containing non-whitespace characters blank" + ] + + +email : Test +email = + describe "email" + [ test "empty string is not a valid email" <| + \() -> + "" + |> Validate.isValidEmail + |> Expect.false "Validate.isValidEmail should have considered empty string blank" + , test "valid email is valid" <| + \() -> + "foo@bar.com" + |> Validate.isValidEmail + |> Expect.true "Validate.isValidEmail should have considered foo@bar.com a valid email address" + ] + + +whitespace : Fuzzer String +whitespace = + [ ' ', ' ', '\t', '\n' ] + |> List.map Fuzz.constant + |> Fuzz.oneOf + |> Fuzz.list + |> Fuzz.map String.fromList diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-package.json b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-package.json new file mode 100644 index 0000000..0d2d828 --- /dev/null +++ b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-package.json @@ -0,0 +1,16 @@ +{ + "version": "1.0.0", + "summary": "Test Suites", + "repository": "https://github.com/rtfeldman/elm-validate.git", + "license": "BSD-3-Clause", + "source-directories": [ + "../src", + "." + ], + "exposed-modules": [], + "dependencies": { + "elm-lang/core": "5.0.0 <= v < 6.0.0", + "elm-community/elm-test": "4.0.0 <= v < 5.0.0" + }, + "elm-version": "0.18.0 <= v < 0.19.0" +} \ No newline at end of file diff --git a/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-verify-examples.json b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-verify-examples.json new file mode 100644 index 0000000..1feeb5c --- /dev/null +++ b/part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-verify-examples.json @@ -0,0 +1,4 @@ +{ + "root": "../src", + "tests": [ "Validate" ] +} diff --git a/part3/src/Data/Article/Author.elm b/part3/src/Data/Article/Author.elm index ab8f108..a632508 100644 --- a/part3/src/Data/Article/Author.elm +++ b/part3/src/Data/Article/Author.elm @@ -3,7 +3,7 @@ module Data.Article.Author exposing (Author, decoder) import Data.User as User exposing (Username) import Data.UserPhoto as UserPhoto exposing (UserPhoto) import Json.Decode as Decode exposing (Decoder) -import Json.Decode.Pipeline exposing (custom, decode, required) +import Json.Decode.Pipeline exposing (custom, decode, optional, required) decoder : Decoder Author @@ -12,7 +12,7 @@ decoder = |> required "username" User.usernameDecoder |> required "bio" (Decode.nullable Decode.string) |> required "image" UserPhoto.decoder - |> required "following" Decode.bool + |> optional "following" Decode.bool False type alias Author = diff --git a/part3/src/Data/User.elm b/part3/src/Data/User.elm index 40f6338..aa60a0c 100644 --- a/part3/src/Data/User.elm +++ b/part3/src/Data/User.elm @@ -4,7 +4,7 @@ import Data.AuthToken as AuthToken exposing (AuthToken) import Data.UserPhoto as UserPhoto exposing (UserPhoto) import Html exposing (Html) import Json.Decode as Decode exposing (Decoder) -import Json.Decode.Pipeline exposing (decode, required) +import Json.Decode.Pipeline exposing (decode, optional, required) import Json.Encode as Encode exposing (Value) import Json.Encode.Extra as EncodeExtra import UrlParser @@ -16,8 +16,6 @@ type alias User = , username : Username , bio : Maybe String , image : UserPhoto - , createdAt : String - , updatedAt : String } @@ -33,8 +31,6 @@ decoder = |> required "username" usernameDecoder |> required "bio" (Decode.nullable Decode.string) |> required "image" UserPhoto.decoder - |> required "createdAt" Decode.string - |> required "updatedAt" Decode.string encode : User -> Value @@ -45,8 +41,6 @@ encode user = , ( "username", encodeUsername user.username ) , ( "bio", EncodeExtra.maybe Encode.string user.bio ) , ( "image", UserPhoto.encode user.image ) - , ( "createdAt", Encode.string user.createdAt ) - , ( "updatedAt", Encode.string user.updatedAt ) ] diff --git a/part3/src/Data/UserPhoto.elm b/part3/src/Data/UserPhoto.elm index a97715c..bf7f768 100644 --- a/part3/src/Data/UserPhoto.elm +++ b/part3/src/Data/UserPhoto.elm @@ -39,7 +39,15 @@ photoToUrl : UserPhoto -> String photoToUrl (UserPhoto maybeUrl) = case maybeUrl of Nothing -> - "https://static.productionready.io/images/smiley-cyrus.jpg" + defaultPhotoUrl + + Just "" -> + defaultPhotoUrl Just url -> url + + +defaultPhotoUrl : String +defaultPhotoUrl = + "/assets/images/smiley-cyrus.jpg" diff --git a/part3/src/Page/Article/Editor.elm b/part3/src/Page/Article/Editor.elm index bb1c579..8ba8e27 100644 --- a/part3/src/Page/Article/Editor.elm +++ b/part3/src/Page/Article/Editor.elm @@ -4,7 +4,7 @@ import Data.Article as Article exposing (Article, Body) import Data.Session exposing (Session) import Data.User exposing (User) import Html exposing (..) -import Html.Attributes exposing (attribute, class, disabled, href, id, placeholder, type_, value) +import Html.Attributes exposing (attribute, class, defaultValue, disabled, href, id, placeholder, type_) import Html.Events exposing (onInput, onSubmit) import Http import Page.Errored exposing (PageLoadError, pageLoadError) @@ -102,26 +102,26 @@ viewForm model = [ class "form-control-lg" , placeholder "Article Title" , onInput SetTitle - , value model.title + , defaultValue model.title ] [] , Form.input [ placeholder "What's this article about?" , onInput SetDescription - , value model.description + , defaultValue model.description ] [] , Form.textarea [ placeholder "Write your article (in markdown)" , attribute "rows" "8" , onInput SetBody - , value model.body + , defaultValue model.body ] [] , Form.input [ placeholder "Enter tags" , onInput SetTags - , value (String.join " " model.tags) + , defaultValue (String.join " " model.tags) ] [] , button [ class "btn btn-lg pull-xs-right btn-primary", disabled model.isSaving ] diff --git a/part3/src/Page/Register.elm b/part3/src/Page/Register.elm index 3349060..e1e09a2 100644 --- a/part3/src/Page/Register.elm +++ b/part3/src/Page/Register.elm @@ -186,10 +186,23 @@ modelValidator = Validate.all [ ifBlank .username ( Username, "username can't be blank." ) , ifBlank .email ( Email, "email can't be blank." ) - , ifBlank .password ( Password, "password can't be blank." ) + , Validate.fromErrors passwordLength ] +minPasswordChars : Int +minPasswordChars = + 6 + + +passwordLength : Model -> List Error +passwordLength { password } = + if String.length password < minPasswordChars then + [ ( Password, "password must be at least " ++ toString minPasswordChars ++ " characters long." ) ] + else + [] + + errorsDecoder : Decoder (List String) errorsDecoder = decode (\email username password -> List.concat [ email, username, password ]) diff --git a/part3/src/Views/Article.elm b/part3/src/Views/Article.elm index 180b1ec..c1f229b 100644 --- a/part3/src/Views/Article.elm +++ b/part3/src/Views/Article.elm @@ -7,12 +7,22 @@ import Data.Article exposing (Article) import Data.UserPhoto as UserPhoto exposing (UserPhoto) import Date.Format import Html exposing (..) -import Html.Attributes exposing (attribute, class, classList, href, id, src) +import Html.Attributes exposing (attribute, class, classList, href, id, placeholder, src) import Route exposing (Route) import Views.Article.Favorite as Favorite import Views.Author +-- VIEWS -- + + +{-| Some pages want to view just the timestamp, not the whole article. +-} +viewTimestamp : Article a -> Html msg +viewTimestamp article = + span [ class "date" ] [ text (formattedTimestamp article) ] + + view : (Article a -> msg) -> Article a -> Html msg view toggleFavorite article = let @@ -25,7 +35,7 @@ view toggleFavorite article = [ img [ UserPhoto.src author.image ] [] ] , div [ class "info" ] [ Views.Author.view author.username - , viewTimestamp article + , span [ class "date" ] [ text (formattedTimestamp article) ] ] , Favorite.button toggleFavorite @@ -41,9 +51,8 @@ view toggleFavorite article = ] -viewTimestamp : Article a -> Html msg -viewTimestamp article = - span [ class "date" ] [ text (formattedTimestamp article) ] + +-- INTERNAL -- formattedTimestamp : Article a -> String