espial-0.0.16: purs/src/Component/BMark.purs
module Component.BMark where
import Prelude hiding (div)
import Affjax (printError)
import Affjax.StatusCode (StatusCode(..))
import App (StarAction(..), destroy, editBookmark, markRead, toggleStar, lookupTitle)
import Component.Markdown as Markdown
import Data.Const (Const)
import Data.Either (Either(..))
import Data.Lens (Lens', lens, use, (%=), (.=))
import Data.Maybe (Maybe(..), fromMaybe, isJust)
import Data.Monoid (guard)
import Data.Nullable (toMaybe)
import Data.String (null, split, take, replaceAll) as S
import Data.String.Pattern (Pattern(..), Replacement(..))
import Effect.Aff (Aff)
import Effect.Class (liftEffect)
import Effect.Class.Console (log)
import Globals (app', setFocus, toLocaleDateString)
import Halogen as H
import Halogen.HTML (a, br_, button, div, div_, form, input, label, span, text, textarea)
import Halogen.HTML as HH
import Halogen.HTML.Events (onSubmit, onValueChange, onChecked, onClick)
import Halogen.HTML.Properties (ButtonType(..), InputType(..), autocomplete, checked, disabled, for, href, id, name, required, rows, target, title, type_, value)
import Model (Bookmark)
import Type.Proxy (Proxy(..))
import Util (attr, class_, encodeTag, fromNullableStr, ifElseH, whenA, whenH)
import Web.Event.Event (Event, preventDefault)
import DOM.HTML.Indexed.AutocompleteType (AutocompleteType(..))
-- | UI Events
data BAction
= BStar Boolean
| BDeleteAsk Boolean
| BLookupTitle
| BDestroy
| BEdit Boolean
| BEditField EditField
| BEditSubmit Event
| BMarkRead
-- | FormField Edits
data EditField
= Eurl String
| Etitle String
| Edescription String
| Etags String
| Eprivate Boolean
| Etoread Boolean
-- | Messages to parent
data BMessage
= BNotifyRemove
type BSlot = H.Slot (Const Void) BMessage
type BState =
{ bm :: Bookmark
, edit_bm :: Bookmark
, deleteAsk:: Boolean
, edit :: Boolean
, loading :: Boolean
, apiError :: Maybe String
}
_bm :: Lens' BState Bookmark
_bm = lens _.bm (_ { bm = _ })
_edit_bm :: Lens' BState Bookmark
_edit_bm = lens _.edit_bm (_ { edit_bm = _ })
_edit :: Lens' BState Boolean
_edit = lens _.edit (_ { edit = _ })
_apiError :: Lens' BState (Maybe String)
_apiError = lens _.apiError (_ { apiError = _ })
_markdown = Proxy :: Proxy "markdown"
type ChildSlots =
( markdown :: Markdown.Slot Unit
)
bmark :: forall q i. Bookmark -> H.Component q i BMessage Aff
bmark b' =
H.mkComponent
{ initialState: const (mkState b')
, render
, eval: H.mkEval $ H.defaultEval { handleAction = handleAction }
}
where
app = app' unit
mkState b =
{ bm: b
, edit_bm: b
, deleteAsk: false
, edit: false
, loading: false
, apiError: Nothing
}
render :: BState -> H.ComponentHTML BAction ChildSlots Aff
render s@{ bm, edit_bm, apiError } =
div [ id (show bm.bid) , class_ ("bookmark w-100 mw7 pa1 mb3" <> guard bm.private " private")] $
[ whenH app.dat.isowner
star
, ifElseH s.edit
display_edit
display
]
where
star _ =
div [ class_ ("star fl pointer" <> guard bm.selected " selected") ]
[ button [ class_ "moon-gray", onClick \_ -> BStar (not bm.selected) ] [ text "✭" ] ]
display _ =
div [ class_ "display" ] $
[ a [ href bm.url, target "_blank", class_ ("link f5 lh-title" <> guard bm.toread " unread")]
[ text $ if S.null bm.title then "[no title]" else bm.title ]
, br_
, a [ href bm.url , class_ "link f7 gray hover-blue" ] [ text bm.url ]
, a [ href (fromMaybe ("http://archive.is/" <> bm.url) (toMaybe bm.archiveUrl))
, class_ ("link f7 gray hover-blue ml2" <> (guard (isJust (toMaybe bm.archiveUrl)) " green"))
, target "_blank", title "archive link"]
[ if isJust (toMaybe bm.archiveUrl) then text "☑" else text "☐" ]
, br_
, div [ class_ "description mt1 mid-gray" ] [ HH.slot _markdown unit Markdown.component bm.description absurd ]
, div [ class_ "tags" ] $
whenA (not (S.null bm.tags)) $ \_ ->
map (\tag -> a [ class_ ("link tag mr1" <> guard (S.take 1 tag == ".") " private")
, href (linkToFilterTag tag) ]
[ text tag ])
(S.split (Pattern " ") bm.tags)
, a [ class_ "link f7 dib gray w4", href (linkToFilterSingle bm.slug), title shdatetime ]
[ text shdate ]
-- links
, whenH app.dat.isowner $ \_ ->
div [ class_ "edit_links di" ]
[ button [ type_ ButtonButton, onClick \_ -> BEdit true, class_ "edit light-silver hover-blue" ] [ text "edit " ]
, div [ class_ "delete_link di" ]
[ button [ type_ ButtonButton, onClick \_ -> BDeleteAsk true, class_ ("delete light-silver hover-blue" <> guard s.deleteAsk " dn") ] [ text "delete" ]
, span ([ class_ ("confirm red" <> guard (not s.deleteAsk) " dn") ] )
[ button [ type_ ButtonButton, onClick \_ -> BDeleteAsk false] [ text "cancel / " ]
, button [ type_ ButtonButton, onClick \_ -> BDestroy, class_ "red" ] [ text "destroy" ]
]
]
]
, whenH app.dat.isowner $ \_ ->
div [ class_ "read di" ] $
guard bm.toread
[ text " "
, button [ onClick \_ -> BMarkRead, class_ "mark_read" ] [ text "mark as read"]
]
]
display_edit _ =
div [ class_ "edit_bookmark_form pa2 pt0 bg-white" ] $
[ whenH (isJust apiError)
(alert_notification (fromMaybe "" apiError))
, form [ onSubmit BEditSubmit ]
[ div_ [ text "url" ]
, input [ type_ InputUrl , class_ "url w-100 mb2 pt1 edit_form_input" , required true , name "url"
, value (edit_bm.url) , onValueChange (editField Eurl) ]
, div_ [ text "title" ]
, div [class_ "flex"]
[input [ type_ InputText , class_ "title w-100 mb2 pt1 edit_form_input" , name "title"
, value (edit_bm.title) , onValueChange (editField Etitle) ]
, button [ disabled s.loading, type_ ButtonButton, onClick \_ -> BLookupTitle, class_ ("ml1 pa1 mb2 dark-gray ba b--moon-gray bg-near-white pointer rdim f7 " <> guard s.loading "bg-light-silver") ] [ text "fetch" ]
]
, div_ [ text "description" ]
, textarea [ class_ "description w-100 mb1 pt1 edit_form_input" , name "description", rows 5
, value (edit_bm.description) , onValueChange (editField Edescription) ]
, div [ id "tags_input_box"]
[ div_ [ text "tags" ]
, input [ id (tagid edit_bm), type_ InputText , class_ "tags w-100 mb1 pt1 edit_form_input" , name "tags"
, autocomplete AutocompleteOff, attr "autocapitalize" "off"
, value (edit_bm.tags) , onValueChange (editField Etags) ]
]
, div [ class_ "edit_form_checkboxes mv3"]
[ input [ type_ InputCheckbox , class_ "private pointer" , id "edit_private", name "private"
, checked (edit_bm.private) , onChecked (editField Eprivate) ]
, text " "
, label [ for "edit_private" , class_ "mr2" ] [ text "private" ]
, text " "
, input [ type_ InputCheckbox , class_ "toread pointer" , id "edit_toread", name "toread"
, checked (edit_bm.toread) , onChecked (editField Etoread) ]
, text " "
, label [ for "edit_toread" ] [ text "to-read" ]
]
, input [ type_ InputSubmit , class_ "mr1 pv1 ph2 dark-gray ba b--moon-gray bg-near-white pointer rdim" , value "save" ]
, text " "
, input [ type_ InputReset , class_ "pv1 ph2 dark-gray ba b--moon-gray bg-near-white pointer rdim" , value "cancel"
, onClick \_ -> BEdit false ]
]
]
alert_notification alert_text _ =
div [ class_ "alert alert-err" ] [ text alert_text ]
editField :: forall a. (a -> EditField) -> a -> BAction
editField f = BEditField <<< f
linkToFilterSingle slug = fromNullableStr app.userR <> "/b:" <> slug
linkToFilterTag tag = fromNullableStr app.userR <> "/t:" <> encodeTag tag
shdate = toLocaleDateString bm.time
shdatetime = S.take 16 bm.time `append` "Z"
tagid bm = show bm.bid <> "_tags"
handleAction :: BAction -> H.HalogenM BState BAction ChildSlots BMessage Aff Unit
-- | Star
handleAction (BStar e) = do
bm <- use _bm
H.liftAff (toggleStar bm.bid (if e then Star else UnStar))
_bm %= _ { selected = e }
_edit_bm %= _ { selected = e }
-- | Delete
handleAction (BDeleteAsk e) = do
H.modify_ (_ { deleteAsk = e })
-- | Destroy
handleAction (BDestroy) = do
bm <- use _bm
void $ H.liftAff (destroy bm.bid)
H.raise BNotifyRemove
-- | Mark Read
handleAction (BMarkRead) = do
bm <- use _bm
void (H.liftAff (markRead bm.bid))
_bm %= _ { toread = false }
-- | Start/Stop Editing
handleAction (BEdit e) = do
bm <- use _bm
_edit_bm .= bm
_edit .= e
_apiError .= Nothing
H.liftEffect $
when e
(setFocus (tagid bm))
-- | Update Form Field
handleAction (BEditField f) = do
_edit_bm %= case f of
Eurl e -> _ { url = e }
Etitle e -> _ { title = e }
Edescription e -> _ { description = e }
Etags e -> _ { tags = e }
Eprivate e -> _ { private = e }
Etoread e -> _ { toread = e }
-- | Lookup Title
handleAction BLookupTitle = do
H.modify_ (_ { loading = true })
edit_bm <- H.gets _.edit_bm
mtitle <- H.liftAff $ lookupTitle edit_bm
case mtitle of
Just title' -> _edit_bm %= (_ { title = title' })
Nothing -> pure $ unit
H.modify_ (_ { loading = false })
-- | Submit
handleAction (BEditSubmit e) = do
H.liftEffect (preventDefault e)
edit_bm <- use _edit_bm
_apiError .= Nothing
let edit_bm' = edit_bm { tags = S.replaceAll (Pattern ",") (Replacement " ") edit_bm.tags }
H.liftAff (editBookmark edit_bm') >>= case _ of
Left affErr -> do
_apiError .= Just (printError affErr)
liftEffect $ log (printError affErr)
Right { status: StatusCode s } | s >= 200 && s < 300 -> do
_bm .= edit_bm'
_edit .= false
Right res -> do
_apiError .= Just (res.body)
liftEffect $ log (res.body)