Skip to content

Instantly share code, notes, and snippets.

@ajnsit
Created May 12, 2020 19:43
Show Gist options
  • Save ajnsit/f0fee9a83480289a5a052273ce21cc1b to your computer and use it in GitHub Desktop.
Save ajnsit/f0fee9a83480289a5a052273ce21cc1b to your computer and use it in GitHub Desktop.
Hacker News Search with Concur. Ref: https://news.ycombinator.com/item?id=23151516
module Main where
import Affjax as AX
import Affjax.ResponseFormat as ResponseFormat
import Concur.Core (Widget)
import Concur.React (HTML)
import Concur.React.DOM as D
import Concur.React.Props as P
import Concur.React.Run (runWidgetInDom)
import Control.Alt ((<|>))
import Control.Alternative (empty)
import Control.Applicative (pure, (*>), (<$))
import Control.Bind (bind, (>>=))
import Control.Category ((<<<))
import Data.Argonaut (Json, decodeJson, (.:), (.:?))
import Data.Array (fromFoldable)
import Data.Either (Either(..), either)
import Data.Function (($))
import Data.Functor (map, (<$>))
import Data.Maybe (Maybe(..), fromMaybe, maybe)
import Data.Ord ((<=))
import Data.Semigroup (append, (<>))
import Data.Show (show)
import Data.String as S
import Data.Traversable (traverse)
import Data.Unit (Unit)
import Effect (Effect)
import Effect.Aff.Class (liftAff)
main :: Effect Unit
main = runWidgetInDom "root" $ app ""
-------------------------------------------------------------
-- Define our Datatype, and how we will decode the response body
type Story =
{ title :: Maybe String
, points :: Maybe Int
, url :: Maybe String
, author :: Maybe String
, numComments :: Maybe Int
}
decodeStory :: Json -> Either String Story
decodeStory json = do
o <- decodeJson json
title <- o .:? "title"
url <- o .:? "url"
points <- o .:? "points"
numComments <- o .:? "num_comments"
author <- o .:? "author"
pure $ {title, points, url, author, numComments}
decodeStoriesArray :: Json -> Either String (Array Story)
decodeStoriesArray json = do
o <- decodeJson json
arr <- o .: "hits"
traverse decodeStory arr
-------------------------------------------------------------
-- Widgets
-- The complete app
app :: forall a. String -> Widget HTML a
app s = D.div'
[ D.h1' [D.text "My Hacker Stories"]
, search s
, D.hr'
, results s
] >>= app
-- The search widget
search :: String -> Widget HTML String
search s = D.div'
[ D.b' [D.text "Search: "]
, D.input
[ (Just <<< P.unsafeTargetValue) <$> P.onChange
, P.defaultValue s
]
, D.button
[Nothing <$ P.onClick, P.disabled (S.length s <= 0)]
[D.text "Submit"]
] >>= maybe (pure s) search
-- Display the results for a particular search term
results :: forall a. String -> Widget HTML a
results "" = empty
results s = do
let url = "https://hn.algolia.com/api/v1/search?query=" <> s
result <- D.text "Loading..."
<|> liftAff (AX.get ResponseFormat.json url)
case result of
Left err -> D.text $ S.joinWith " "
["GET", url, "response failed to decode:", AX.printError err]
Right response -> either
(D.text <<< append "Error")
(D.div' <<< map story)
(decodeStoriesArray response.body)
-- Render a story
story :: forall a. Story -> Widget HTML a
story item = D.div'
[ D.span'
[ D.a
(P.href <$> fromFoldable item.url)
[ D.text $ fromMaybe "" item.title]
], nbsp
, D.span' [D.text $ fromMaybe "" item.author], nbsp
, D.span' [D.text $ show $ fromMaybe 0 item.numComments], nbsp
, D.span' [D.text $ show $ fromMaybe 0 item.points], nbsp
, D.span'
[ D.button
[ P._type "button", P.onClick ]
[ D.text "Dismiss" ]
]
] *> empty
where
nbsp = D.text " "
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment