155 lines
3.4 KiB
Elm
155 lines
3.4 KiB
Elm
module Data.Article
|
|
exposing
|
|
( Article
|
|
, Body
|
|
, Slug
|
|
, Tag
|
|
, bodyToHtml
|
|
, bodyToMarkdownString
|
|
, decoder
|
|
, decoderWithBody
|
|
, slugParser
|
|
, slugToString
|
|
, tagDecoder
|
|
, tagToString
|
|
)
|
|
|
|
import Data.Article.Author as Author exposing (Author)
|
|
import Date exposing (Date)
|
|
import Html exposing (Attribute, Html)
|
|
import Json.Decode as Decode exposing (Decoder)
|
|
import Json.Decode.Extra
|
|
import Json.Decode.Pipeline exposing (custom, decode, hardcoded, required)
|
|
import Markdown
|
|
import UrlParser
|
|
|
|
|
|
{-| An article, optionally with an article body.
|
|
|
|
To see the difference between { body : body } and { body : Maybe Body },
|
|
consider the difference between the "view individual article" page (which
|
|
renders one article, including its body) and the "article feed" -
|
|
which displays multiple articles, but without bodies.
|
|
|
|
This definition for `Article` means we can write:
|
|
|
|
viewArticle : Article Body -> Html msg
|
|
viewFeed : List (Article ()) -> Html msg
|
|
|
|
This indicates that `viewArticle` requires an article _with a `body` present_,
|
|
wereas `viewFeed` accepts articles with no bodies. (We could also have written
|
|
it as `List (Article a)` to specify that feeds can accept either articles that
|
|
have `body` present or not. Either work, given that feeds do not attempt to
|
|
read the `body` field from articles.)
|
|
|
|
This is an important distinction, because in Request.Article, the `feed`
|
|
function produces `List (Article ())` because the API does not return bodies.
|
|
Those articles are useful to the feed, but not to the individual article view.
|
|
|
|
-}
|
|
type alias Article a =
|
|
{ description : String
|
|
, slug : Slug
|
|
, title : String
|
|
, tags : List String
|
|
, createdAt : Date
|
|
, updatedAt : Date
|
|
, favorited : Bool
|
|
, favoritesCount : Int
|
|
, author : Author
|
|
, body : a
|
|
}
|
|
|
|
|
|
|
|
-- SERIALIZATION --
|
|
|
|
|
|
decoder : Decoder (Article ())
|
|
decoder =
|
|
baseArticleDecoder
|
|
|> hardcoded ()
|
|
|
|
|
|
decoderWithBody : Decoder (Article Body)
|
|
decoderWithBody =
|
|
baseArticleDecoder
|
|
|> required "body" bodyDecoder
|
|
|
|
|
|
baseArticleDecoder : Decoder (a -> Article a)
|
|
baseArticleDecoder =
|
|
decode Article
|
|
|> required "description" (Decode.map (Maybe.withDefault "") (Decode.nullable Decode.string))
|
|
|> required "slug" (Decode.map Slug Decode.string)
|
|
|> required "title" Decode.string
|
|
|> required "tagList" (Decode.list Decode.string)
|
|
|> required "createdAt" Json.Decode.Extra.date
|
|
|> required "updatedAt" Json.Decode.Extra.date
|
|
|> required "favorited" Decode.bool
|
|
|> required "favoritesCount" Decode.int
|
|
|> required "author" Author.decoder
|
|
|
|
|
|
|
|
-- IDENTIFIERS --
|
|
|
|
|
|
type Slug
|
|
= Slug String
|
|
|
|
|
|
slugParser : UrlParser.Parser (Slug -> a) a
|
|
slugParser =
|
|
UrlParser.custom "SLUG" (Ok << Slug)
|
|
|
|
|
|
slugToString : Slug -> String
|
|
slugToString (Slug slug) =
|
|
slug
|
|
|
|
|
|
|
|
-- TAGS --
|
|
|
|
|
|
type Tag
|
|
= Tag String
|
|
|
|
|
|
tagToString : Tag -> String
|
|
tagToString (Tag slug) =
|
|
slug
|
|
|
|
|
|
tagDecoder : Decoder Tag
|
|
tagDecoder =
|
|
Decode.map Tag Decode.string
|
|
|
|
|
|
|
|
-- BODY --
|
|
|
|
|
|
type Body
|
|
= Body Markdown
|
|
|
|
|
|
type alias Markdown =
|
|
String
|
|
|
|
|
|
bodyToHtml : Body -> List (Attribute msg) -> Html msg
|
|
bodyToHtml (Body markdown) attributes =
|
|
Markdown.toHtml attributes markdown
|
|
|
|
|
|
bodyToMarkdownString : Body -> String
|
|
bodyToMarkdownString (Body markdown) =
|
|
markdown
|
|
|
|
|
|
bodyDecoder : Decoder Body
|
|
bodyDecoder =
|
|
Decode.map Body Decode.string
|