first refacto by concern
This commit is contained in:
parent
1381b35a01
commit
68dd8ec479
9 changed files with 187 additions and 127 deletions
|
@ -2,7 +2,7 @@
|
||||||
--
|
--
|
||||||
-- see: https://github.com/sol/hpack
|
-- see: https://github.com/sol/hpack
|
||||||
--
|
--
|
||||||
-- hash: b25d763a18f7abd2dd41f84f6e8923a72cfbde56ee75daf4de8e5c10a24b3c19
|
-- hash: baf75307a9c628aa3d68c8e108beda42a689496d986f883d8d19ca9e1da2961a
|
||||||
|
|
||||||
name: aggreact
|
name: aggreact
|
||||||
version: 0.1.0.0
|
version: 0.1.0.0
|
||||||
|
@ -29,6 +29,7 @@ library
|
||||||
exposed-modules:
|
exposed-modules:
|
||||||
Aggreact
|
Aggreact
|
||||||
Aggreact.Comments
|
Aggreact.Comments
|
||||||
|
Aggreact.Comments.Server
|
||||||
Aggreact.Comments.StoreService
|
Aggreact.Comments.StoreService
|
||||||
Aggreact.Comments.Types
|
Aggreact.Comments.Types
|
||||||
Aggreact.Comments.Views
|
Aggreact.Comments.Views
|
||||||
|
|
2
devel.sh
2
devel.sh
|
@ -1,5 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
target="aggreact"
|
target="aggreact"
|
||||||
ghcid \
|
ghcid \
|
||||||
--command "stack ghci $target --ghci-options=-fobject-code" \
|
--command "stack ghci --with-ghc ghci \"--docker-run-args=--interactive=true --tty=false\" $target --no-build --ghci-options=-fobject-code" \
|
||||||
--test "DevelMain.update"
|
--test "DevelMain.update"
|
||||||
|
|
|
@ -50,10 +50,7 @@ import Aggreact.User
|
||||||
|
|
||||||
import Clay (Css)
|
import Clay (Css)
|
||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
import qualified Data.IxSet.Typed as IxSet
|
|
||||||
import Data.Time (getCurrentTime)
|
import Data.Time (getCurrentTime)
|
||||||
import qualified Data.UUID as UUID
|
|
||||||
import Database.Store (Entity(..), Id (..))
|
|
||||||
import Network.Wai (Application)
|
import Network.Wai (Application)
|
||||||
import Servant
|
import Servant
|
||||||
import Servant.Errors
|
import Servant.Errors
|
||||||
|
@ -87,7 +84,7 @@ instance FromJSON Login
|
||||||
instance Form.FromForm Login where
|
instance Form.FromForm Login where
|
||||||
fromForm = Form.genericFromForm Form.FormOptions {fieldLabelModifier = identity}
|
fromForm = Form.genericFromForm Form.FormOptions {fieldLabelModifier = identity}
|
||||||
|
|
||||||
type Unprotected =
|
type Unauthenticated =
|
||||||
"login"
|
"login"
|
||||||
:> (ReqBody '[JSON, FormUrlEncoded] Login
|
:> (ReqBody '[JSON, FormUrlEncoded] Login
|
||||||
:> PostNoContent '[JSON, FormUrlEncoded]
|
:> PostNoContent '[JSON, FormUrlEncoded]
|
||||||
|
@ -96,8 +93,20 @@ type Unprotected =
|
||||||
NoContent)
|
NoContent)
|
||||||
:<|> Get '[HTML] LoginPage)
|
:<|> Get '[HTML] LoginPage)
|
||||||
|
|
||||||
type API auths = (Servant.Auth.Server.Auth auths User :> CommentAPI)
|
type API auths =
|
||||||
:<|> Unprotected
|
Auth auths User :> Authenticated
|
||||||
|
:<|> Unauthenticated
|
||||||
|
|
||||||
|
type Authenticated =
|
||||||
|
HomepageAPI
|
||||||
|
:<|> CommentAPI
|
||||||
|
|
||||||
|
serverAuthenticated :: Settings
|
||||||
|
-> Servant.Auth.Server.AuthResult User
|
||||||
|
-> Server (Authenticated)
|
||||||
|
serverAuthenticated settings@Settings{..} authresult =
|
||||||
|
homepageAPI settings authresult
|
||||||
|
:<|> commentAPI (Aggreact.Comments.Handlers userHandler commentHandler) authresult
|
||||||
|
|
||||||
data Settings =
|
data Settings =
|
||||||
Settings { cookieSettings :: CookieSettings
|
Settings { cookieSettings :: CookieSettings
|
||||||
|
@ -107,8 +116,8 @@ data Settings =
|
||||||
}
|
}
|
||||||
|
|
||||||
server :: Settings -> Server (API auths)
|
server :: Settings -> Server (API auths)
|
||||||
server settings@Settings{..} =
|
server settings =
|
||||||
commentAPI settings
|
serverAuthenticated settings
|
||||||
:<|> checkCreds settings
|
:<|> checkCreds settings
|
||||||
:<|> return LoginPage
|
:<|> return LoginPage
|
||||||
|
|
||||||
|
@ -139,30 +148,20 @@ checkCreds Settings{..} (Login loginNick loginPass) = do
|
||||||
|
|
||||||
-- / Auth
|
-- / Auth
|
||||||
|
|
||||||
type CommentAPI =
|
type HomepageAPI =
|
||||||
"main.css" :> Get '[CSS] Css
|
"main.css" :> Get '[CSS] Css
|
||||||
:<|> Get '[HTML] Homepage
|
:<|> Get '[HTML] Homepage
|
||||||
:<|> "comments" :> Capture "slug" Text
|
|
||||||
:> Get '[HTML,JSON] CommentsPage
|
|
||||||
:<|> "slugs"
|
|
||||||
:> Get '[JSON] [Slug]
|
|
||||||
:<|> "comments" :> ReqBody '[JSON, FormUrlEncoded] NewComment
|
|
||||||
:> PostCreated '[HTML,JSON] CreatedComment
|
|
||||||
:<|> "comment" :> Capture "commentId" Text
|
|
||||||
:> Get '[HTML,JSON] CommentPage
|
|
||||||
|
|
||||||
commentAPI :: Settings -> Servant.Auth.Server.AuthResult User -> Server CommentAPI
|
homepageAPI :: Settings
|
||||||
commentAPI settings@Settings{..} authResult =
|
-> Servant.Auth.Server.AuthResult User
|
||||||
|
-> Server HomepageAPI
|
||||||
|
homepageAPI settings@Settings{..} authResult =
|
||||||
let muser = case authResult of
|
let muser = case authResult of
|
||||||
(Servant.Auth.Server.Authenticated user) -> Just user
|
(Servant.Auth.Server.Authenticated user) -> Just user
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
in
|
in
|
||||||
return genCss
|
return genCss
|
||||||
:<|> initHomepage muser settings
|
:<|> initHomepage muser settings
|
||||||
:<|> showComments muser commentHandler
|
|
||||||
:<|> liftIO (getSlugs commentHandler)
|
|
||||||
:<|> postNewComment muser commentHandler
|
|
||||||
:<|> showComment muser commentHandler
|
|
||||||
|
|
||||||
initHomepage :: Maybe User -> Settings -> Handler Homepage
|
initHomepage :: Maybe User -> Settings -> Handler Homepage
|
||||||
initHomepage muser Settings{..} =
|
initHomepage muser Settings{..} =
|
||||||
|
@ -172,41 +171,6 @@ initHomepage muser Settings{..} =
|
||||||
<*> getCurrentTime
|
<*> getCurrentTime
|
||||||
<*> return muser
|
<*> return muser
|
||||||
|
|
||||||
showComments :: Maybe User -> CommentHandler -> Text -> Handler CommentsPage
|
|
||||||
showComments muser CommentHandler{..} s = do
|
|
||||||
cvs <- liftIO $ commentsView (Slug s)
|
|
||||||
now <- liftIO getCurrentTime
|
|
||||||
liftIO $ print cvs
|
|
||||||
return CommentsPage { url = s
|
|
||||||
, viewTime = now
|
|
||||||
, comments = IxSet.fromList cvs
|
|
||||||
, muser = muser
|
|
||||||
}
|
|
||||||
|
|
||||||
showComment :: Maybe User -> CommentHandler -> Text -> Handler CommentPage
|
|
||||||
showComment muser CommentHandler{..} i =
|
|
||||||
case UUID.fromText i of
|
|
||||||
Nothing -> notFound ""
|
|
||||||
Just uuid -> do
|
|
||||||
cs <- liftIO . readComment . Id $ uuid
|
|
||||||
now <- liftIO getCurrentTime
|
|
||||||
case cs of
|
|
||||||
Just c -> return CommentPage { commentPageUrl = i
|
|
||||||
, commentPageViewTime = now
|
|
||||||
, commentPageComment = c
|
|
||||||
, muser = muser
|
|
||||||
}
|
|
||||||
_ -> notFound ""
|
|
||||||
|
|
||||||
postNewComment :: Maybe User -> CommentHandler -> NewComment -> Handler CreatedComment
|
|
||||||
postNewComment Nothing _ch _ = unauthorized "You must log in to post new comments"
|
|
||||||
postNewComment muser@(Just (Entity i _ _)) ch nc =
|
|
||||||
CreatedComment <$> liftIO getCurrentTime
|
|
||||||
<*> liftIO (createComment ch (nc { userid = UserId (toS i) }))
|
|
||||||
<*> return muser
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- -- * User API
|
-- -- * User API
|
||||||
--
|
--
|
||||||
-- type UserAPI =
|
-- type UserAPI =
|
||||||
|
|
|
@ -7,30 +7,19 @@ Maintainer : yann.esposito@gmail.com
|
||||||
Stability : experimental
|
Stability : experimental
|
||||||
Portability : POSIX
|
Portability : POSIX
|
||||||
|
|
||||||
Main datastructures
|
This module is here just to re-export sub modules.
|
||||||
|
Because the original module was split because it was too big.
|
||||||
|
|
||||||
-}
|
-}
|
||||||
module Aggreact.Comments
|
module Aggreact.Comments
|
||||||
(
|
( module Aggreact.Comments.StoreService
|
||||||
-- * Types
|
, module Aggreact.Comments.Types
|
||||||
Comment
|
, module Aggreact.Comments.Views
|
||||||
, CommentPage (..)
|
, module Aggreact.Comments.Server
|
||||||
, CommentsPage (..)
|
)
|
||||||
, CreatedComment (..)
|
where
|
||||||
, NewComment (..)
|
|
||||||
, Comments
|
|
||||||
, Slug (..)
|
|
||||||
, ParentId (..)
|
|
||||||
, UserId (..)
|
|
||||||
-- * Usage for DB
|
|
||||||
, CommentDBConf
|
|
||||||
, newCommentHandler
|
|
||||||
, CommentHandler(..)
|
|
||||||
-- * HTML
|
|
||||||
, displayOneComment
|
|
||||||
) where
|
|
||||||
|
|
||||||
import Aggreact.Comments.StoreService
|
import Aggreact.Comments.StoreService
|
||||||
import Aggreact.Comments.Types
|
import Aggreact.Comments.Types
|
||||||
import Aggreact.Comments.Views
|
import Aggreact.Comments.Views
|
||||||
|
import Aggreact.Comments.Server
|
||||||
|
|
112
src/Aggreact/Comments/Server.hs
Normal file
112
src/Aggreact/Comments/Server.hs
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
-- Local Pragmas
|
||||||
|
{-# LANGUAGE Strict #-}
|
||||||
|
{-# LANGUAGE TypeOperators #-}
|
||||||
|
|
||||||
|
-- Common Pragmas (already stated in cabal file but repeated here for some tools)
|
||||||
|
{-# LANGUAGE BlockArguments #-} -- blabla do ... instead of blabla $ do ...
|
||||||
|
{-# LANGUAGE DerivingStrategies #-} -- remove warn about deriving strategies
|
||||||
|
{-# LANGUAGE ExplicitForAll #-} -- f :: forall a b. a -> b -> b
|
||||||
|
{-# LANGUAGE NamedWildCards #-} -- can use _type instead of just _
|
||||||
|
{-# LANGUAGE NoImplicitPrelude #-} -- to use protolude for example
|
||||||
|
{-# LANGUAGE NumericUnderscores #-} -- write 1_000 instead of 1000
|
||||||
|
{-# LANGUAGE OverloadedLists #-} -- write [1,2,3] :: Set
|
||||||
|
{-# LANGUAGE OverloadedStrings #-} -- write "foo" and it will be the correct text type
|
||||||
|
{-# LANGUAGE PartialTypeSignatures #-} -- write foo :: (_) => a -> Bool
|
||||||
|
{-# LANGUAGE RecordWildCards #-} -- f Conf{..} = field1 ...
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-} -- write expr :: Type inside an expression
|
||||||
|
{- |
|
||||||
|
Module : Aggreact.Comments.StoreService
|
||||||
|
Description : CommentStore service
|
||||||
|
Copyright : (c) 2018, Yann Esposito
|
||||||
|
License : ISC
|
||||||
|
Maintainer : yann.esposito@gmail.com
|
||||||
|
Stability : experimental
|
||||||
|
Portability : POSIX
|
||||||
|
|
||||||
|
Comment Routes and Servant Handlers
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Aggreact.Comments.Server
|
||||||
|
where
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
import Protolude
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
import Aggreact.Comments.Types
|
||||||
|
import Aggreact.Comments.StoreService (CommentHandler(..))
|
||||||
|
import Aggreact.Comments.Views
|
||||||
|
import Aggreact.User (UserHandler(..),User)
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
import Data.Time (getCurrentTime)
|
||||||
|
import Database.Store (Entity(..), Id (..))
|
||||||
|
import qualified Data.UUID as UUID
|
||||||
|
import qualified Data.IxSet.Typed as IxSet
|
||||||
|
import Servant
|
||||||
|
import Servant.Errors
|
||||||
|
import Servant.HTML.Blaze (HTML)
|
||||||
|
import Servant.Auth.Server (AuthResult(..))
|
||||||
|
|
||||||
|
type CommentAPI =
|
||||||
|
"comments"
|
||||||
|
:> Capture "slug" Text
|
||||||
|
:> Get '[HTML,JSON] CommentsPage
|
||||||
|
:<|> "slugs"
|
||||||
|
:> Get '[JSON] [Slug]
|
||||||
|
:<|> "comments"
|
||||||
|
:> ReqBody '[JSON, FormUrlEncoded] NewComment
|
||||||
|
:> PostCreated '[HTML,JSON] CreatedComment
|
||||||
|
:<|> "comment" :> Capture "commentId" Text
|
||||||
|
:> Get '[HTML,JSON] CommentPage
|
||||||
|
|
||||||
|
data Handlers =
|
||||||
|
Handlers { userHandler :: UserHandler
|
||||||
|
, commentHandler :: CommentHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
commentAPI :: Handlers -> AuthResult User -> Server CommentAPI
|
||||||
|
commentAPI Handlers{..} authResult =
|
||||||
|
let muser = case authResult of
|
||||||
|
(Authenticated user) -> Just user
|
||||||
|
_ -> Nothing
|
||||||
|
in
|
||||||
|
showComments muser commentHandler
|
||||||
|
:<|> liftIO (getSlugs commentHandler)
|
||||||
|
:<|> postNewComment muser commentHandler
|
||||||
|
:<|> showComment muser commentHandler
|
||||||
|
|
||||||
|
showComments :: Maybe User -> CommentHandler -> Text -> Handler CommentsPage
|
||||||
|
showComments muser CommentHandler{..} s = do
|
||||||
|
cvs <- liftIO $ commentsView (Slug s)
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
|
liftIO $ print cvs
|
||||||
|
return CommentsPage { url = s
|
||||||
|
, viewTime = now
|
||||||
|
, comments = IxSet.fromList cvs
|
||||||
|
, muser = muser
|
||||||
|
}
|
||||||
|
|
||||||
|
showComment :: Maybe User -> CommentHandler -> Text -> Handler CommentPage
|
||||||
|
showComment muser CommentHandler{..} i =
|
||||||
|
case UUID.fromText i of
|
||||||
|
Nothing -> notFound ""
|
||||||
|
Just uuid -> do
|
||||||
|
cs <- liftIO . readComment . Id $ uuid
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
|
case cs of
|
||||||
|
Just c -> return CommentPage { commentPageUrl = i
|
||||||
|
, commentPageViewTime = now
|
||||||
|
, commentPageComment = c
|
||||||
|
, muser = muser
|
||||||
|
}
|
||||||
|
_ -> notFound ""
|
||||||
|
|
||||||
|
postNewComment :: Maybe User -> CommentHandler -> NewComment -> Handler CreatedComment
|
||||||
|
postNewComment Nothing _ch _ = unauthorized "You must log in to post new comments"
|
||||||
|
postNewComment muser@(Just (Entity i _ _)) ch nc =
|
||||||
|
CreatedComment <$> liftIO getCurrentTime
|
||||||
|
<*> liftIO (createComment ch (nc { userid = UserId (toS i) }))
|
||||||
|
<*> return muser
|
|
@ -31,12 +31,7 @@ Maintainer : yann.esposito@gmail.com
|
||||||
Stability : experimental
|
Stability : experimental
|
||||||
Portability : POSIX
|
Portability : POSIX
|
||||||
|
|
||||||
Comment datastructures with class instances
|
Comment Store Service
|
||||||
|
|
||||||
|
|
||||||
- A @Comment@ is a NewComment with metas
|
|
||||||
- A @CommentView@ is a comment along its creator infos
|
|
||||||
- A @NewComment@ is the main infos for a Comment
|
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,8 @@ Maintainer : yann.esposito@gmail.com
|
||||||
Stability : experimental
|
Stability : experimental
|
||||||
Portability : POSIX
|
Portability : POSIX
|
||||||
|
|
||||||
Comment datastructures with class instances
|
Comment datastructures with class instances here to prevent most Orphan
|
||||||
|
instances.
|
||||||
|
|
||||||
- A @Comment@ is a NewComment with metas
|
- A @Comment@ is a NewComment with metas
|
||||||
- A @CommentView@ is a comment along its creator infos
|
- A @CommentView@ is a comment along its creator infos
|
||||||
|
|
|
@ -28,7 +28,10 @@ Maintainer : yann.esposito@gmail.com
|
||||||
Stability : experimental
|
Stability : experimental
|
||||||
Portability : POSIX
|
Portability : POSIX
|
||||||
|
|
||||||
Main datastructures
|
Comment Views
|
||||||
|
|
||||||
|
- JSON
|
||||||
|
- HTML
|
||||||
|
|
||||||
-}
|
-}
|
||||||
module Aggreact.Comments.Views
|
module Aggreact.Comments.Views
|
||||||
|
@ -40,7 +43,7 @@ import Protolude hiding (get, put)
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
import Aggreact.Comments.Types
|
import Aggreact.Comments.Types
|
||||||
import Aggreact.Html (boilerplate, loginWidget,
|
import Aggreact.Html (boilerplate, loginWidget,
|
||||||
urlEncode)
|
urlEncode, cvt, extlink)
|
||||||
import Aggreact.User (NewUser (..), User)
|
import Aggreact.User (NewUser (..), User)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
@ -57,44 +60,20 @@ import Text.Blaze.Html5 ((!))
|
||||||
import qualified Text.Blaze.Html5 as H
|
import qualified Text.Blaze.Html5 as H
|
||||||
import qualified Text.Blaze.Html5.Attributes as A
|
import qualified Text.Blaze.Html5.Attributes as A
|
||||||
|
|
||||||
{- * Structure
|
|
||||||
|
|
||||||
Each entity should have the following properties:
|
-- * Single Comment Page
|
||||||
|
|
||||||
* provide a type that represent the internal structure @Struct@
|
|
||||||
* provide a type that represent the metas structure @Metas@
|
|
||||||
* @Entity Metas Struct@ should be an instance of some Backend Store typeclass
|
|
||||||
* @Entity Metas Struct@ should be an instance of 'ToJSON'
|
|
||||||
* @Metas@ should be an instance of 'FromJSON', 'FromForm' and mainly one for all
|
|
||||||
content-type you like
|
|
||||||
|
|
||||||
-}
|
|
||||||
|
|
||||||
-- * Operations
|
|
||||||
|
|
||||||
|
-- |
|
||||||
data CommentPage =
|
data CommentPage =
|
||||||
CommentPage
|
CommentPage
|
||||||
{ commentPageUrl :: Text
|
{ commentPageUrl :: Text -- ^ the comment id for the url
|
||||||
, commentPageViewTime :: UTCTime
|
, commentPageViewTime :: UTCTime -- ^ the time of watching the comment
|
||||||
, commentPageComment :: Comment
|
, commentPageComment :: Comment -- ^ The comment
|
||||||
, muser :: Maybe User
|
, muser :: Maybe User -- ^ Viewer
|
||||||
}
|
}
|
||||||
instance ToJSON CommentPage where
|
instance ToJSON CommentPage where
|
||||||
toJSON cp = toJSON (commentPageComment cp)
|
toJSON cp = toJSON (commentPageComment cp)
|
||||||
|
|
||||||
-- | helper for conversions
|
|
||||||
cvt :: StringConv a [Char] => a -> H.AttributeValue
|
|
||||||
cvt = fromString . toS
|
|
||||||
|
|
||||||
extlink :: StringConv a [Char] => a -> Text -> H.Html
|
|
||||||
extlink url txt = H.a
|
|
||||||
! A.href (cvt url)
|
|
||||||
! A.target "_blank"
|
|
||||||
! A.rel "noopener noreferrer nofollow"
|
|
||||||
$ do
|
|
||||||
H.text txt
|
|
||||||
H.sup $ H.text "⬀"
|
|
||||||
|
|
||||||
instance H.ToMarkup CommentPage where
|
instance H.ToMarkup CommentPage where
|
||||||
toMarkup CommentPage{..} = boilerplate (loginWidget muser) $ do
|
toMarkup CommentPage{..} = boilerplate (loginWidget muser) $ do
|
||||||
let sl = commentPageComment & val & slug & toS
|
let sl = commentPageComment & val & slug & toS
|
||||||
|
@ -107,6 +86,8 @@ instance H.ToMarkup CommentPage where
|
||||||
commentForm sl muser (Just (cvt cid))
|
commentForm sl muser (Just (cvt cid))
|
||||||
|
|
||||||
|
|
||||||
|
-- * Created Comment Page
|
||||||
|
|
||||||
data CreatedComment =
|
data CreatedComment =
|
||||||
CreatedComment
|
CreatedComment
|
||||||
{ viewTime :: UTCTime
|
{ viewTime :: UTCTime
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
{-# LANGUAGE DerivingStrategies #-}
|
{-# LANGUAGE DerivingStrategies #-}
|
||||||
{-# LANGUAGE ExplicitForAll #-}
|
{-# LANGUAGE ExplicitForAll #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||||
{-# LANGUAGE NamedWildCards #-}
|
{-# LANGUAGE NamedWildCards #-}
|
||||||
{-# LANGUAGE NoImplicitPrelude #-}
|
{-# LANGUAGE NoImplicitPrelude #-}
|
||||||
|
@ -31,19 +32,22 @@ module Aggreact.Html
|
||||||
, urlEncode
|
, urlEncode
|
||||||
, loginWidget
|
, loginWidget
|
||||||
, LoginPage(..)
|
, LoginPage(..)
|
||||||
|
, cvt
|
||||||
|
, extlink
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Protolude
|
import Protolude
|
||||||
|
|
||||||
import qualified Data.Char as Char
|
import qualified Data.Char as Char
|
||||||
|
import Data.String (IsString (..))
|
||||||
import Prelude (String)
|
import Prelude (String)
|
||||||
import Text.Blaze.Html5 ((!))
|
import Text.Blaze.Html5 ((!))
|
||||||
import qualified Text.Blaze.Html5 as H
|
import qualified Text.Blaze.Html5 as H
|
||||||
import qualified Text.Blaze.Html5.Attributes as A
|
import qualified Text.Blaze.Html5.Attributes as A
|
||||||
import Text.Printf
|
import Text.Printf
|
||||||
|
|
||||||
import Aggreact.User (User,NewUser(..))
|
import Aggreact.User (NewUser (..), User)
|
||||||
import Database.Store (Entity (..))
|
import Database.Store (Entity (..))
|
||||||
|
|
||||||
container :: H.Html -> H.Html
|
container :: H.Html -> H.Html
|
||||||
|
@ -97,3 +101,17 @@ encode c
|
||||||
|
|
||||||
urlEncode :: String -> String
|
urlEncode :: String -> String
|
||||||
urlEncode = concatMap encode
|
urlEncode = concatMap encode
|
||||||
|
|
||||||
|
-- | helper for conversions
|
||||||
|
cvt :: StringConv a [Char] => a -> H.AttributeValue
|
||||||
|
cvt = fromString . toS
|
||||||
|
|
||||||
|
-- | external link (protected by best practice security measures)
|
||||||
|
extlink :: StringConv a [Char] => a -> Text -> H.Html
|
||||||
|
extlink url txt = H.a
|
||||||
|
! A.href (cvt url)
|
||||||
|
! A.target "_blank"
|
||||||
|
! A.rel "noopener noreferrer nofollow"
|
||||||
|
$ do
|
||||||
|
H.text txt
|
||||||
|
H.sup $ H.text "⬀"
|
||||||
|
|
Loading…
Reference in a new issue