Update part2 and part3
This commit is contained in:
@@ -19,7 +19,7 @@
|
|||||||
"evancz/url-parser": "2.0.1 <= v < 3.0.0",
|
"evancz/url-parser": "2.0.1 <= v < 3.0.0",
|
||||||
"lukewestby/elm-http-builder": "5.1.0 <= v < 6.0.0",
|
"lukewestby/elm-http-builder": "5.1.0 <= v < 6.0.0",
|
||||||
"mgold/elm-date-format": "1.3.0 <= v < 2.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"
|
"rtfeldman/selectlist": "1.0.0 <= v < 2.0.0"
|
||||||
},
|
},
|
||||||
"elm-version": "0.18.0 <= v < 0.19.0"
|
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"rtfeldman/elm-validate": "2.0.0",
|
"rtfeldman/elm-validate": "3.0.0",
|
||||||
"rtfeldman/selectlist": "1.0.0",
|
"rtfeldman/selectlist": "1.0.0",
|
||||||
"elm-lang/navigation": "2.1.0",
|
"elm-lang/navigation": "2.1.0",
|
||||||
"elm-lang/virtual-dom": "2.0.4",
|
"elm-lang/virtual-dom": "2.0.4",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type alias Model =
|
|||||||
{ name : String, email : String, age : String, selections : List String }
|
{ name : String, email : String, age : String, selections : List String }
|
||||||
|
|
||||||
|
|
||||||
modelValidator : Validator Model String
|
modelValidator : Validator String Model
|
||||||
modelValidator =
|
modelValidator =
|
||||||
Validate.all
|
Validate.all
|
||||||
[ ifBlank .name "Please enter a name."
|
[ ifBlank .name "Please enter a name."
|
||||||
@@ -28,8 +28,7 @@ modelValidator =
|
|||||||
|
|
||||||
validate modelValidator
|
validate modelValidator
|
||||||
{ name = "Sam", email = "", age = "abc", selections = [ "cats" ] }
|
{ name = "Sam", email = "", age = "abc", selections = [ "cats" ] }
|
||||||
== [ "Please enter an email address.", "Age must be a whole number." ]
|
--> [ "Please enter an email address.", "Age must be a whole number." ]
|
||||||
--> True
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can represent your errors however you like. One nice approach is to use
|
You can represent your errors however you like. One nice approach is to use
|
||||||
@@ -56,8 +55,7 @@ type alias Model =
|
|||||||
|
|
||||||
validate modelValidator
|
validate modelValidator
|
||||||
{ name = "Sam", email = "", age = "abc", selections = [ "cats" ] }
|
{ name = "Sam", email = "", age = "abc", selections = [ "cats" ] }
|
||||||
== [ ( Email, "Please enter an email address." )
|
--> [ ( Email, "Please enter an email address." )
|
||||||
, ( Age, "Age must be a whole number." )
|
--> , ( Age, "Age must be a whole number." )
|
||||||
]
|
--> ]
|
||||||
--> True
|
|
||||||
```
|
```
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"summary": "Convenience functions for validating data.",
|
"summary": "Convenience functions for validating data.",
|
||||||
"repository": "https://github.com/rtfeldman/elm-validate.git",
|
"repository": "https://github.com/rtfeldman/elm-validate.git",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
@@ -4,6 +4,7 @@ module Validate
|
|||||||
, all
|
, all
|
||||||
, any
|
, any
|
||||||
, firstError
|
, firstError
|
||||||
|
, fromErrors
|
||||||
, ifBlank
|
, ifBlank
|
||||||
, ifEmptyDict
|
, ifEmptyDict
|
||||||
, ifEmptyList
|
, ifEmptyList
|
||||||
@@ -21,7 +22,7 @@ module Validate
|
|||||||
|
|
||||||
{-| Convenience functions for validating data.
|
{-| Convenience functions for validating data.
|
||||||
|
|
||||||
import Validate exposing (ifBlank, ifNotInt, validate)
|
import Validate exposing (Validator, ifBlank, ifNotInt, validate)
|
||||||
|
|
||||||
type Field = Name | Email | Age
|
type Field = Name | Email | Age
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ module Validate
|
|||||||
|
|
||||||
# Creating validators
|
# 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
|
# Combining validators
|
||||||
@@ -86,7 +87,7 @@ type Validator error subject
|
|||||||
{-| Return an error if the given predicate returns `True` for the given
|
{-| Return an error if the given predicate returns `True` for the given
|
||||||
subject.
|
subject.
|
||||||
|
|
||||||
import Validate exposing (ifBlank, ifNotInt, validate)
|
import Validate exposing (Validator, ifBlank, ifNotInt, validate)
|
||||||
|
|
||||||
type Field = Name | Email | Age
|
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
|
{-| Return an error if the given `String` is empty, or if it contains only
|
||||||
whitespace characters.
|
whitespace characters.
|
||||||
|
|
||||||
import Validate exposing (ifBlank, ifNotInt)
|
import Validate exposing (Validator, ifBlank)
|
||||||
|
|
||||||
modelValidator : Validator Model String
|
modelValidator : Validator Model String
|
||||||
modelValidator =
|
modelValidator =
|
||||||
@@ -132,10 +133,31 @@ ifBlank subjectToString error =
|
|||||||
|
|
||||||
|
|
||||||
{-| Return an error if the given `String` cannot be parsed as an `Int`.
|
{-| 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 : (subject -> String) -> (String -> error) -> Validator error subject
|
||||||
ifNotInt subjectToString error =
|
ifNotInt subjectToString errorFromString =
|
||||||
ifFalse (\subject -> isInt (subjectToString subject)) error
|
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.
|
{-| Return an error if a `List` is empty.
|
||||||
@@ -167,16 +189,65 @@ ifNothing subjectToMaybe error =
|
|||||||
|
|
||||||
|
|
||||||
{-| Return an error if an email address is malformed.
|
{-| 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 : (subject -> String) -> (String -> error) -> Validator error subject
|
||||||
ifInvalidEmail subjectToEmail error =
|
ifInvalidEmail subjectToEmail errorFromEmail =
|
||||||
ifFalse (\subject -> isValidEmail (subjectToEmail subject)) error
|
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
|
{-| Return an error if a predicate returns `True` for the given
|
||||||
subject.
|
subject.
|
||||||
|
|
||||||
import Validate exposing (ifTrue)
|
import Validate exposing (Validator, ifTrue)
|
||||||
|
|
||||||
modelValidator : Validator Model String
|
modelValidator : Validator Model String
|
||||||
modelValidator =
|
modelValidator =
|
||||||
@@ -199,7 +270,7 @@ ifTrue test error =
|
|||||||
{-| Return an error if a predicate returns `False` for the given
|
{-| Return an error if a predicate returns `False` for the given
|
||||||
subject.
|
subject.
|
||||||
|
|
||||||
import Validate exposing (ifFalse)
|
import Validate exposing (Validator, ifFalse)
|
||||||
|
|
||||||
modelValidator : Validator Model String
|
modelValidator : Validator Model String
|
||||||
modelValidator =
|
modelValidator =
|
||||||
@@ -226,7 +297,7 @@ ifFalse test error =
|
|||||||
{-| Run each of the given validators, in order, and return their concatenated
|
{-| Run each of the given validators, in order, and return their concatenated
|
||||||
error lists.
|
error lists.
|
||||||
|
|
||||||
import Validate exposing (ifBlank, ifNotInt)
|
import Validate exposing (Validator, ifBlank, ifNotInt)
|
||||||
|
|
||||||
modelValidator : Validator Model String
|
modelValidator : Validator Model String
|
||||||
modelValidator =
|
modelValidator =
|
||||||
@@ -253,7 +324,7 @@ all validators =
|
|||||||
{-| Run each of the given validators, in order, stopping after the first error
|
{-| Run each of the given validators, in order, stopping after the first error
|
||||||
and returning it. If no errors are encountered, return `Nothing`.
|
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 =
|
type alias Model =
|
||||||
@@ -337,7 +408,7 @@ isBlank str =
|
|||||||
Regex.contains lacksNonWhitespaceChars 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.
|
[`ifInvalidEmail`](#ifInvalidEmail) uses this under the hood.
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@ module Data.Article.Author exposing (Author, decoder)
|
|||||||
import Data.User as User exposing (Username)
|
import Data.User as User exposing (Username)
|
||||||
import Data.UserPhoto as UserPhoto exposing (UserPhoto)
|
import Data.UserPhoto as UserPhoto exposing (UserPhoto)
|
||||||
import Json.Decode as Decode exposing (Decoder)
|
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
|
decoder : Decoder Author
|
||||||
@@ -12,7 +12,7 @@ decoder =
|
|||||||
|> required "username" User.usernameDecoder
|
|> required "username" User.usernameDecoder
|
||||||
|> required "bio" (Decode.nullable Decode.string)
|
|> required "bio" (Decode.nullable Decode.string)
|
||||||
|> required "image" UserPhoto.decoder
|
|> required "image" UserPhoto.decoder
|
||||||
|> required "following" Decode.bool
|
|> optional "following" Decode.bool False
|
||||||
|
|
||||||
|
|
||||||
type alias Author =
|
type alias Author =
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Data.AuthToken as AuthToken exposing (AuthToken)
|
|||||||
import Data.UserPhoto as UserPhoto exposing (UserPhoto)
|
import Data.UserPhoto as UserPhoto exposing (UserPhoto)
|
||||||
import Html exposing (Html)
|
import Html exposing (Html)
|
||||||
import Json.Decode as Decode exposing (Decoder)
|
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 as Encode exposing (Value)
|
||||||
import Json.Encode.Extra as EncodeExtra
|
import Json.Encode.Extra as EncodeExtra
|
||||||
import UrlParser
|
import UrlParser
|
||||||
@@ -16,8 +16,6 @@ type alias User =
|
|||||||
, username : Username
|
, username : Username
|
||||||
, bio : Maybe String
|
, bio : Maybe String
|
||||||
, image : UserPhoto
|
, image : UserPhoto
|
||||||
, createdAt : String
|
|
||||||
, updatedAt : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -33,8 +31,6 @@ decoder =
|
|||||||
|> required "username" usernameDecoder
|
|> required "username" usernameDecoder
|
||||||
|> required "bio" (Decode.nullable Decode.string)
|
|> required "bio" (Decode.nullable Decode.string)
|
||||||
|> required "image" UserPhoto.decoder
|
|> required "image" UserPhoto.decoder
|
||||||
|> required "createdAt" Decode.string
|
|
||||||
|> required "updatedAt" Decode.string
|
|
||||||
|
|
||||||
|
|
||||||
encode : User -> Value
|
encode : User -> Value
|
||||||
@@ -45,8 +41,6 @@ encode user =
|
|||||||
, ( "username", encodeUsername user.username )
|
, ( "username", encodeUsername user.username )
|
||||||
, ( "bio", EncodeExtra.maybe Encode.string user.bio )
|
, ( "bio", EncodeExtra.maybe Encode.string user.bio )
|
||||||
, ( "image", UserPhoto.encode user.image )
|
, ( "image", UserPhoto.encode user.image )
|
||||||
, ( "createdAt", Encode.string user.createdAt )
|
|
||||||
, ( "updatedAt", Encode.string user.updatedAt )
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,15 @@ photoToUrl : UserPhoto -> String
|
|||||||
photoToUrl (UserPhoto maybeUrl) =
|
photoToUrl (UserPhoto maybeUrl) =
|
||||||
case maybeUrl of
|
case maybeUrl of
|
||||||
Nothing ->
|
Nothing ->
|
||||||
"https://static.productionready.io/images/smiley-cyrus.jpg"
|
defaultPhotoUrl
|
||||||
|
|
||||||
|
Just "" ->
|
||||||
|
defaultPhotoUrl
|
||||||
|
|
||||||
Just url ->
|
Just url ->
|
||||||
url
|
url
|
||||||
|
|
||||||
|
|
||||||
|
defaultPhotoUrl : String
|
||||||
|
defaultPhotoUrl =
|
||||||
|
"/assets/images/smiley-cyrus.jpg"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Data.Article as Article exposing (Article, Body)
|
|||||||
import Data.Session exposing (Session)
|
import Data.Session exposing (Session)
|
||||||
import Data.User exposing (User)
|
import Data.User exposing (User)
|
||||||
import Html exposing (..)
|
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 Html.Events exposing (onInput, onSubmit)
|
||||||
import Http
|
import Http
|
||||||
import Page.Errored exposing (PageLoadError, pageLoadError)
|
import Page.Errored exposing (PageLoadError, pageLoadError)
|
||||||
@@ -102,26 +102,26 @@ viewForm model =
|
|||||||
[ class "form-control-lg"
|
[ class "form-control-lg"
|
||||||
, placeholder "Article Title"
|
, placeholder "Article Title"
|
||||||
, onInput SetTitle
|
, onInput SetTitle
|
||||||
, value model.title
|
, defaultValue model.title
|
||||||
]
|
]
|
||||||
[]
|
[]
|
||||||
, Form.input
|
, Form.input
|
||||||
[ placeholder "What's this article about?"
|
[ placeholder "What's this article about?"
|
||||||
, onInput SetDescription
|
, onInput SetDescription
|
||||||
, value model.description
|
, defaultValue model.description
|
||||||
]
|
]
|
||||||
[]
|
[]
|
||||||
, Form.textarea
|
, Form.textarea
|
||||||
[ placeholder "Write your article (in markdown)"
|
[ placeholder "Write your article (in markdown)"
|
||||||
, attribute "rows" "8"
|
, attribute "rows" "8"
|
||||||
, onInput SetBody
|
, onInput SetBody
|
||||||
, value model.body
|
, defaultValue model.body
|
||||||
]
|
]
|
||||||
[]
|
[]
|
||||||
, Form.input
|
, Form.input
|
||||||
[ placeholder "Enter tags"
|
[ placeholder "Enter tags"
|
||||||
, onInput SetTags
|
, 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 ]
|
, button [ class "btn btn-lg pull-xs-right btn-primary", disabled model.isSaving ]
|
||||||
|
|||||||
@@ -186,10 +186,23 @@ modelValidator =
|
|||||||
Validate.all
|
Validate.all
|
||||||
[ ifBlank .username ( Username, "username can't be blank." )
|
[ ifBlank .username ( Username, "username can't be blank." )
|
||||||
, ifBlank .email ( Email, "email 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 : Decoder (List String)
|
||||||
errorsDecoder =
|
errorsDecoder =
|
||||||
decode (\email username password -> List.concat [ email, username, password ])
|
decode (\email username password -> List.concat [ email, username, password ])
|
||||||
|
|||||||
@@ -7,12 +7,22 @@ import Data.Article exposing (Article)
|
|||||||
import Data.UserPhoto as UserPhoto exposing (UserPhoto)
|
import Data.UserPhoto as UserPhoto exposing (UserPhoto)
|
||||||
import Date.Format
|
import Date.Format
|
||||||
import Html exposing (..)
|
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 Route exposing (Route)
|
||||||
import Views.Article.Favorite as Favorite
|
import Views.Article.Favorite as Favorite
|
||||||
import Views.Author
|
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 : (Article a -> msg) -> Article a -> Html msg
|
||||||
view toggleFavorite article =
|
view toggleFavorite article =
|
||||||
let
|
let
|
||||||
@@ -25,7 +35,7 @@ view toggleFavorite article =
|
|||||||
[ img [ UserPhoto.src author.image ] [] ]
|
[ img [ UserPhoto.src author.image ] [] ]
|
||||||
, div [ class "info" ]
|
, div [ class "info" ]
|
||||||
[ Views.Author.view author.username
|
[ Views.Author.view author.username
|
||||||
, viewTimestamp article
|
, span [ class "date" ] [ text (formattedTimestamp article) ]
|
||||||
]
|
]
|
||||||
, Favorite.button
|
, Favorite.button
|
||||||
toggleFavorite
|
toggleFavorite
|
||||||
@@ -41,9 +51,8 @@ view toggleFavorite article =
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
viewTimestamp : Article a -> Html msg
|
|
||||||
viewTimestamp article =
|
-- INTERNAL --
|
||||||
span [ class "date" ] [ text (formattedTimestamp article) ]
|
|
||||||
|
|
||||||
|
|
||||||
formattedTimestamp : Article a -> String
|
formattedTimestamp : Article a -> String
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"evancz/url-parser": "2.0.1 <= v < 3.0.0",
|
"evancz/url-parser": "2.0.1 <= v < 3.0.0",
|
||||||
"lukewestby/elm-http-builder": "5.1.0 <= v < 6.0.0",
|
"lukewestby/elm-http-builder": "5.1.0 <= v < 6.0.0",
|
||||||
"mgold/elm-date-format": "1.3.0 <= v < 2.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"
|
"rtfeldman/selectlist": "1.0.0 <= v < 2.0.0"
|
||||||
},
|
},
|
||||||
"elm-version": "0.18.0 <= v < 0.19.0"
|
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"rtfeldman/elm-validate": "2.0.0",
|
"rtfeldman/elm-validate": "3.0.0",
|
||||||
"rtfeldman/selectlist": "1.0.0",
|
"rtfeldman/selectlist": "1.0.0",
|
||||||
"elm-lang/navigation": "2.1.0",
|
"elm-lang/navigation": "2.1.0",
|
||||||
"elm-lang/virtual-dom": "2.0.4",
|
"elm-lang/virtual-dom": "2.0.4",
|
||||||
|
|||||||
30
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/.gitignore
vendored
Normal file
30
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/.gitignore
vendored
Normal file
@@ -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
|
||||||
5
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/CHANGELOG.md
vendored
Normal file
5
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/CHANGELOG.md
vendored
Normal file
@@ -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
|
||||||
28
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/LICENSE
vendored
Normal file
28
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/LICENSE
vendored
Normal file
@@ -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.
|
||||||
|
|
||||||
61
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/README.md
vendored
Normal file
61
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/README.md
vendored
Normal file
@@ -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." )
|
||||||
|
--> ]
|
||||||
|
```
|
||||||
16
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/elm-package.json
vendored
Normal file
16
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/elm-package.json
vendored
Normal file
@@ -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"
|
||||||
|
}
|
||||||
36
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/SignupForm.elm
vendored
Normal file
36
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/SignupForm.elm
vendored
Normal file
@@ -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." )
|
||||||
|
]
|
||||||
15
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/elm-package.json
vendored
Normal file
15
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/examples/elm-package.json
vendored
Normal file
@@ -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"
|
||||||
|
}
|
||||||
448
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/src/Validate.elm
vendored
Normal file
448
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/src/Validate.elm
vendored
Normal file
@@ -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
|
||||||
52
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/ValidateTests.elm
vendored
Normal file
52
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/ValidateTests.elm
vendored
Normal file
@@ -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
|
||||||
16
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-package.json
vendored
Normal file
16
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-package.json
vendored
Normal file
@@ -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"
|
||||||
|
}
|
||||||
4
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-verify-examples.json
vendored
Normal file
4
part3/elm-stuff/packages/rtfeldman/elm-validate/3.0.0/tests/elm-verify-examples.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"root": "../src",
|
||||||
|
"tests": [ "Validate" ]
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ module Data.Article.Author exposing (Author, decoder)
|
|||||||
import Data.User as User exposing (Username)
|
import Data.User as User exposing (Username)
|
||||||
import Data.UserPhoto as UserPhoto exposing (UserPhoto)
|
import Data.UserPhoto as UserPhoto exposing (UserPhoto)
|
||||||
import Json.Decode as Decode exposing (Decoder)
|
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
|
decoder : Decoder Author
|
||||||
@@ -12,7 +12,7 @@ decoder =
|
|||||||
|> required "username" User.usernameDecoder
|
|> required "username" User.usernameDecoder
|
||||||
|> required "bio" (Decode.nullable Decode.string)
|
|> required "bio" (Decode.nullable Decode.string)
|
||||||
|> required "image" UserPhoto.decoder
|
|> required "image" UserPhoto.decoder
|
||||||
|> required "following" Decode.bool
|
|> optional "following" Decode.bool False
|
||||||
|
|
||||||
|
|
||||||
type alias Author =
|
type alias Author =
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Data.AuthToken as AuthToken exposing (AuthToken)
|
|||||||
import Data.UserPhoto as UserPhoto exposing (UserPhoto)
|
import Data.UserPhoto as UserPhoto exposing (UserPhoto)
|
||||||
import Html exposing (Html)
|
import Html exposing (Html)
|
||||||
import Json.Decode as Decode exposing (Decoder)
|
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 as Encode exposing (Value)
|
||||||
import Json.Encode.Extra as EncodeExtra
|
import Json.Encode.Extra as EncodeExtra
|
||||||
import UrlParser
|
import UrlParser
|
||||||
@@ -16,8 +16,6 @@ type alias User =
|
|||||||
, username : Username
|
, username : Username
|
||||||
, bio : Maybe String
|
, bio : Maybe String
|
||||||
, image : UserPhoto
|
, image : UserPhoto
|
||||||
, createdAt : String
|
|
||||||
, updatedAt : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -33,8 +31,6 @@ decoder =
|
|||||||
|> required "username" usernameDecoder
|
|> required "username" usernameDecoder
|
||||||
|> required "bio" (Decode.nullable Decode.string)
|
|> required "bio" (Decode.nullable Decode.string)
|
||||||
|> required "image" UserPhoto.decoder
|
|> required "image" UserPhoto.decoder
|
||||||
|> required "createdAt" Decode.string
|
|
||||||
|> required "updatedAt" Decode.string
|
|
||||||
|
|
||||||
|
|
||||||
encode : User -> Value
|
encode : User -> Value
|
||||||
@@ -45,8 +41,6 @@ encode user =
|
|||||||
, ( "username", encodeUsername user.username )
|
, ( "username", encodeUsername user.username )
|
||||||
, ( "bio", EncodeExtra.maybe Encode.string user.bio )
|
, ( "bio", EncodeExtra.maybe Encode.string user.bio )
|
||||||
, ( "image", UserPhoto.encode user.image )
|
, ( "image", UserPhoto.encode user.image )
|
||||||
, ( "createdAt", Encode.string user.createdAt )
|
|
||||||
, ( "updatedAt", Encode.string user.updatedAt )
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,15 @@ photoToUrl : UserPhoto -> String
|
|||||||
photoToUrl (UserPhoto maybeUrl) =
|
photoToUrl (UserPhoto maybeUrl) =
|
||||||
case maybeUrl of
|
case maybeUrl of
|
||||||
Nothing ->
|
Nothing ->
|
||||||
"https://static.productionready.io/images/smiley-cyrus.jpg"
|
defaultPhotoUrl
|
||||||
|
|
||||||
|
Just "" ->
|
||||||
|
defaultPhotoUrl
|
||||||
|
|
||||||
Just url ->
|
Just url ->
|
||||||
url
|
url
|
||||||
|
|
||||||
|
|
||||||
|
defaultPhotoUrl : String
|
||||||
|
defaultPhotoUrl =
|
||||||
|
"/assets/images/smiley-cyrus.jpg"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Data.Article as Article exposing (Article, Body)
|
|||||||
import Data.Session exposing (Session)
|
import Data.Session exposing (Session)
|
||||||
import Data.User exposing (User)
|
import Data.User exposing (User)
|
||||||
import Html exposing (..)
|
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 Html.Events exposing (onInput, onSubmit)
|
||||||
import Http
|
import Http
|
||||||
import Page.Errored exposing (PageLoadError, pageLoadError)
|
import Page.Errored exposing (PageLoadError, pageLoadError)
|
||||||
@@ -102,26 +102,26 @@ viewForm model =
|
|||||||
[ class "form-control-lg"
|
[ class "form-control-lg"
|
||||||
, placeholder "Article Title"
|
, placeholder "Article Title"
|
||||||
, onInput SetTitle
|
, onInput SetTitle
|
||||||
, value model.title
|
, defaultValue model.title
|
||||||
]
|
]
|
||||||
[]
|
[]
|
||||||
, Form.input
|
, Form.input
|
||||||
[ placeholder "What's this article about?"
|
[ placeholder "What's this article about?"
|
||||||
, onInput SetDescription
|
, onInput SetDescription
|
||||||
, value model.description
|
, defaultValue model.description
|
||||||
]
|
]
|
||||||
[]
|
[]
|
||||||
, Form.textarea
|
, Form.textarea
|
||||||
[ placeholder "Write your article (in markdown)"
|
[ placeholder "Write your article (in markdown)"
|
||||||
, attribute "rows" "8"
|
, attribute "rows" "8"
|
||||||
, onInput SetBody
|
, onInput SetBody
|
||||||
, value model.body
|
, defaultValue model.body
|
||||||
]
|
]
|
||||||
[]
|
[]
|
||||||
, Form.input
|
, Form.input
|
||||||
[ placeholder "Enter tags"
|
[ placeholder "Enter tags"
|
||||||
, onInput SetTags
|
, 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 ]
|
, button [ class "btn btn-lg pull-xs-right btn-primary", disabled model.isSaving ]
|
||||||
|
|||||||
@@ -186,10 +186,23 @@ modelValidator =
|
|||||||
Validate.all
|
Validate.all
|
||||||
[ ifBlank .username ( Username, "username can't be blank." )
|
[ ifBlank .username ( Username, "username can't be blank." )
|
||||||
, ifBlank .email ( Email, "email 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 : Decoder (List String)
|
||||||
errorsDecoder =
|
errorsDecoder =
|
||||||
decode (\email username password -> List.concat [ email, username, password ])
|
decode (\email username password -> List.concat [ email, username, password ])
|
||||||
|
|||||||
@@ -7,12 +7,22 @@ import Data.Article exposing (Article)
|
|||||||
import Data.UserPhoto as UserPhoto exposing (UserPhoto)
|
import Data.UserPhoto as UserPhoto exposing (UserPhoto)
|
||||||
import Date.Format
|
import Date.Format
|
||||||
import Html exposing (..)
|
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 Route exposing (Route)
|
||||||
import Views.Article.Favorite as Favorite
|
import Views.Article.Favorite as Favorite
|
||||||
import Views.Author
|
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 : (Article a -> msg) -> Article a -> Html msg
|
||||||
view toggleFavorite article =
|
view toggleFavorite article =
|
||||||
let
|
let
|
||||||
@@ -25,7 +35,7 @@ view toggleFavorite article =
|
|||||||
[ img [ UserPhoto.src author.image ] [] ]
|
[ img [ UserPhoto.src author.image ] [] ]
|
||||||
, div [ class "info" ]
|
, div [ class "info" ]
|
||||||
[ Views.Author.view author.username
|
[ Views.Author.view author.username
|
||||||
, viewTimestamp article
|
, span [ class "date" ] [ text (formattedTimestamp article) ]
|
||||||
]
|
]
|
||||||
, Favorite.button
|
, Favorite.button
|
||||||
toggleFavorite
|
toggleFavorite
|
||||||
@@ -41,9 +51,8 @@ view toggleFavorite article =
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
viewTimestamp : Article a -> Html msg
|
|
||||||
viewTimestamp article =
|
-- INTERNAL --
|
||||||
span [ class "date" ] [ text (formattedTimestamp article) ]
|
|
||||||
|
|
||||||
|
|
||||||
formattedTimestamp : Article a -> String
|
formattedTimestamp : Article a -> String
|
||||||
|
|||||||
Reference in New Issue
Block a user