hyperbole 0.4.3 → 0.5.0
raw patch · 64 files changed
+5102/−2526 lines, 64 filesdep +aesondep +atomic-cssdep +attoparsecdep −web-viewPVP ok
version bump matches the API change (PVP)
Dependencies added: aeson, atomic-css, attoparsec, attoparsec-aeson, filepath, http-client, http-client-tls, network-uri, random
Dependencies removed: web-view
API changes (from Hackage documentation)
- Web.Hyperbole: ($dmbaseRoute) :: (Route a, Generic a, GenRoute (Rep a)) => Maybe a
- Web.Hyperbole: ($dmcollectValids) :: (Form form val, Generic (form val), GCollect (Rep (form val)) val) => form val -> [val ()]
- Web.Hyperbole: ($dmcolorName) :: (ToColor a, Show a) => a -> Text
- Web.Hyperbole: ($dmcookiePath) :: Session a => Maybe [Segment]
- Web.Hyperbole: ($dmdefaultParam) :: (DefaultParam a, Default a) => a
- Web.Hyperbole: ($dmformParse) :: (Form form val, Generic (form Identity), GFormParse (Rep (form Identity))) => Form -> Either Text (form Identity)
- Web.Hyperbole: ($dmgenFieldsWith) :: (Form form val, Generic (form val), Generic (form (FormField val)), GConvert (Rep (form val)) (Rep (form (FormField val)))) => form val -> form (FormField val)
- Web.Hyperbole: ($dmgenForm) :: (Form form val, Generic (form val), GenFields (Rep (form val))) => form val
- Web.Hyperbole: ($dmmatchRoute) :: (Route a, Generic a, GenRoute (Rep a)) => [Segment] -> Maybe a
- Web.Hyperbole: ($dmparseParam) :: (FromParam a, Read a) => ParamValue -> Either Text a
- Web.Hyperbole: ($dmparseQuery) :: (FromQuery a, Generic a, GFromQuery (Rep a)) => QueryData -> Either Text a
- Web.Hyperbole: ($dmroutePath) :: (Route a, Generic a, Eq a, GenRoute (Rep a)) => a -> [Segment]
- Web.Hyperbole: ($dmsessionKey) :: (Session a, Generic a, GDatatypeName (Rep a)) => Param
- Web.Hyperbole: ($dmtoParam) :: (ToParam a, Show a) => a -> ParamValue
- Web.Hyperbole: ($dmtoQuery) :: (ToQuery a, Generic a, GToQuery (Rep a)) => a -> QueryData
- Web.Hyperbole: Absolute :: Position
- Web.Hyperbole: AlignCenter :: Align
- Web.Hyperbole: AlignJustify :: Align
- Web.Hyperbole: AlignLeft :: Align
- Web.Hyperbole: AlignRight :: Align
- Web.Hyperbole: All :: a -> Sides a
- Web.Hyperbole: B :: a -> Sides a
- Web.Hyperbole: BL :: a -> a -> Sides a
- Web.Hyperbole: BR :: a -> a -> Sides a
- Web.Hyperbole: BgColor :: HexColor -> TransitionProperty
- Web.Hyperbole: Block :: Display
- Web.Hyperbole: Color :: HexColor -> TransitionProperty
- Web.Hyperbole: Decimal :: ListType
- Web.Hyperbole: Disc :: ListType
- Web.Hyperbole: Fixed :: Position
- Web.Hyperbole: Height :: PxRem -> TransitionProperty
- Web.Hyperbole: HexColor :: Text -> HexColor
- Web.Hyperbole: Inner :: Inner
- Web.Hyperbole: L :: a -> Sides a
- Web.Hyperbole: MaxWidth :: Int -> Media
- Web.Hyperbole: MinWidth :: Int -> Media
- Web.Hyperbole: None :: None
- Web.Hyperbole: Pct :: Float -> Length
- Web.Hyperbole: PxRem :: PxRem -> Length
- Web.Hyperbole: R :: a -> Sides a
- Web.Hyperbole: Relative :: Position
- Web.Hyperbole: Sticky :: Position
- Web.Hyperbole: T :: a -> Sides a
- Web.Hyperbole: TL :: a -> a -> Sides a
- Web.Hyperbole: TR :: a -> a -> Sides a
- Web.Hyperbole: TRBL :: a -> a -> a -> a -> Sides a
- Web.Hyperbole: Url :: Text -> Text -> [Segment] -> Query -> Url
- Web.Hyperbole: Width :: PxRem -> TransitionProperty
- Web.Hyperbole: X :: a -> Sides a
- Web.Hyperbole: XY :: a -> a -> Sides a
- Web.Hyperbole: Y :: a -> Sides a
- Web.Hyperbole: [body] :: Request -> ByteString
- Web.Hyperbole: [cookies] :: Request -> Cookies
- Web.Hyperbole: [domain] :: Url -> Text
- Web.Hyperbole: [host] :: Request -> Host
- Web.Hyperbole: [method] :: Request -> Method
- Web.Hyperbole: [path] :: Url -> [Segment]
- Web.Hyperbole: [query] :: Url -> Query
- Web.Hyperbole: [scheme] :: Url -> Text
- Web.Hyperbole: active :: Mod c -> Mod c
- Web.Hyperbole: anyInvalid :: forall form (val :: Type -> Type). (Form form val, ValidationState val) => form val -> Bool
- Web.Hyperbole: basicDocument :: Text -> ByteString -> ByteString
- Web.Hyperbole: bg :: ToColor clr => clr -> Mod ctx
- Web.Hyperbole: bold :: Mod c
- Web.Hyperbole: border :: Sides PxRem -> Mod c
- Web.Hyperbole: borderColor :: ToColor clr => clr -> Mod ctx
- Web.Hyperbole: class DefaultParam a
- Web.Hyperbole: class Form (form :: Type -> Type -> Type) (val :: Type -> Type) | form -> val
- Web.Hyperbole: class ToColor a
- Web.Hyperbole: cleanSegment :: Segment -> Segment
- Web.Hyperbole: collectValids :: Form form val => form val -> [val ()]
- Web.Hyperbole: color :: ToColor clr => clr -> Mod ctx
- Web.Hyperbole: colorName :: ToColor a => a -> Text
- Web.Hyperbole: colorValue :: ToColor a => a -> HexColor
- Web.Hyperbole: cssResetLink :: Text
- Web.Hyperbole: data Align
- Web.Hyperbole: data Attributes (c :: k)
- Web.Hyperbole: data Display
- Web.Hyperbole: data FormField (v :: k -> Type) (a :: k)
- Web.Hyperbole: data Inner
- Web.Hyperbole: data Layer c a
- Web.Hyperbole: data Length
- Web.Hyperbole: data ListType
- Web.Hyperbole: data Media
- Web.Hyperbole: data Ms
- Web.Hyperbole: data None
- Web.Hyperbole: data Position
- Web.Hyperbole: data PxRem
- Web.Hyperbole: data Shadow
- Web.Hyperbole: data Sides a
- Web.Hyperbole: data TableHead a
- Web.Hyperbole: data TransitionProperty
- Web.Hyperbole: data Url
- Web.Hyperbole: data View context a
- Web.Hyperbole: defaultParam :: DefaultParam a => a
- Web.Hyperbole: display :: (Style Display a, ToClassName a) => a -> Mod c
- Web.Hyperbole: el_ :: View c () -> View c ()
- Web.Hyperbole: even :: Mod c -> Mod c
- Web.Hyperbole: fieldValid :: View (Input id v a) (v a)
- Web.Hyperbole: flexCol :: Mod c
- Web.Hyperbole: flexRow :: Mod c
- Web.Hyperbole: fontSize :: Length -> Mod c
- Web.Hyperbole: formFields :: forall form (val :: Type -> Type). Form form val => form (FormField val)
- Web.Hyperbole: formFieldsWith :: forall form (val :: Type -> Type). Form form val => form val -> form (FormField val)
- Web.Hyperbole: formParse :: Form form val => Form -> Either Text (form Identity)
- Web.Hyperbole: gap :: Length -> Mod c
- Web.Hyperbole: genFieldsWith :: Form form val => form val -> form (FormField val)
- Web.Hyperbole: genForm :: Form form val => form val
- Web.Hyperbole: grow :: Mod c
- Web.Hyperbole: height :: Length -> Mod c
- Web.Hyperbole: hide :: Mod c
- Web.Hyperbole: hover :: Mod c -> Mod c
- Web.Hyperbole: italic :: Mod c
- Web.Hyperbole: layer :: Mod c -> View c () -> Layer c ()
- Web.Hyperbole: layout :: Mod c -> View c () -> View c ()
- Web.Hyperbole: list :: (ToClassName a, Style ListType a) => a -> Mod c
- Web.Hyperbole: media :: Media -> Mod c -> Mod c
- Web.Hyperbole: minHeight :: Length -> Mod c
- Web.Hyperbole: minWidth :: Length -> Mod c
- Web.Hyperbole: newtype HexColor
- Web.Hyperbole: odd :: Mod c -> Mod c
- Web.Hyperbole: offset :: Sides Length -> Mod c
- Web.Hyperbole: onRequest :: Mod id -> Mod id
- Web.Hyperbole: opacity :: Float -> Mod c
- Web.Hyperbole: pad :: Sides Length -> Mod c
- Web.Hyperbole: parent :: Text -> Mod c -> Mod c
- Web.Hyperbole: pathSegments :: Text -> [Segment]
- Web.Hyperbole: pathUrl :: [Segment] -> Url
- Web.Hyperbole: pointer :: Mod c
- Web.Hyperbole: popup :: Sides Length -> Mod c
- Web.Hyperbole: position :: Position -> Mod c
- Web.Hyperbole: renderLazyText :: View () () -> Text
- Web.Hyperbole: renderPath :: [Segment] -> Text
- Web.Hyperbole: renderUrl :: Url -> Text
- Web.Hyperbole: respondEarly :: forall (es :: [Effect]) id. (Hyperbole :> es, HyperView id es) => id -> View id () -> Eff es ()
- Web.Hyperbole: root :: Mod c
- Web.Hyperbole: rounded :: Length -> Mod c
- Web.Hyperbole: routeUrl :: Route a => a -> Url
- Web.Hyperbole: scroll :: Mod c
- Web.Hyperbole: shadow :: (Style Shadow a, ToClassName a) => a -> Mod c
- Web.Hyperbole: stack :: Mod c -> Layer c () -> View c ()
- Web.Hyperbole: textAlign :: Align -> Mod c
- Web.Hyperbole: transition :: Ms -> TransitionProperty -> Mod c
- Web.Hyperbole: truncate :: Mod c
- Web.Hyperbole: type Mod context = Attributes context -> Attributes context
- Web.Hyperbole: underline :: Mod c
- Web.Hyperbole: url :: Text -> Url
- Web.Hyperbole: view :: forall (es :: [Effect]). Hyperbole :> es => View () () -> Eff es Response
- Web.Hyperbole: width :: Length -> Mod c
- Web.Hyperbole: zIndex :: Int -> Mod c
- Web.Hyperbole.Application: basicDocument :: Text -> ByteString -> ByteString
- Web.Hyperbole.Data.QueryData: ($dmdefaultParam) :: (DefaultParam a, Default a) => a
- Web.Hyperbole.Data.QueryData: ($dmparseParam) :: (FromParam a, Read a) => ParamValue -> Either Text a
- Web.Hyperbole.Data.QueryData: ($dmparseQuery) :: (FromQuery a, Generic a, GFromQuery (Rep a)) => QueryData -> Either Text a
- Web.Hyperbole.Data.QueryData: ($dmtoParam) :: (ToParam a, Show a) => a -> ParamValue
- Web.Hyperbole.Data.QueryData: ($dmtoQuery) :: (ToQuery a, Generic a, GToQuery (Rep a)) => a -> QueryData
- Web.Hyperbole.Data.QueryData: Param :: Text -> Param
- Web.Hyperbole.Data.QueryData: ParamValue :: Text -> ParamValue
- Web.Hyperbole.Data.QueryData: [text] :: ParamValue -> Text
- Web.Hyperbole.Data.QueryData: class DefaultParam a
- Web.Hyperbole.Data.QueryData: class FromParam a
- Web.Hyperbole.Data.QueryData: class ToParam a
- Web.Hyperbole.Data.QueryData: defaultParam :: DefaultParam a => a
- Web.Hyperbole.Data.QueryData: fromList :: [(Param, ParamValue)] -> QueryData
- Web.Hyperbole.Data.QueryData: instance (GHC.Generics.Selector s, Web.Hyperbole.Data.QueryData.FromParam a, Web.Hyperbole.Data.QueryData.DefaultParam a) => Web.Hyperbole.Data.QueryData.GFromQuery (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a))
- Web.Hyperbole.Data.QueryData: instance (GHC.Generics.Selector s, Web.Hyperbole.Data.QueryData.ToParam a, GHC.Classes.Eq a, Web.Hyperbole.Data.QueryData.DefaultParam a) => Web.Hyperbole.Data.QueryData.GToQuery (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a))
- Web.Hyperbole.Data.QueryData: instance (GHC.Read.Read k, GHC.Read.Read v, GHC.Classes.Ord k) => Web.Hyperbole.Data.QueryData.FromParam (Data.Map.Internal.Map k v)
- Web.Hyperbole.Data.QueryData: instance (GHC.Show.Show k, GHC.Show.Show v) => Web.Hyperbole.Data.QueryData.ToParam (Data.Map.Internal.Map k v)
- Web.Hyperbole.Data.QueryData: instance (Web.Hyperbole.Data.QueryData.FromParam a, Web.Hyperbole.Data.QueryData.FromParam b) => Web.Hyperbole.Data.QueryData.FromParam (Data.Either.Either a b)
- Web.Hyperbole.Data.QueryData: instance (Web.Hyperbole.Data.QueryData.ToParam a, Web.Hyperbole.Data.QueryData.ToParam b) => Web.Hyperbole.Data.QueryData.ToParam (Data.Either.Either a b)
- Web.Hyperbole.Data.QueryData: instance Data.Default.Internal.Default a => Web.Hyperbole.Data.QueryData.DefaultParam a
- Web.Hyperbole.Data.QueryData: instance Data.String.IsString Web.Hyperbole.Data.QueryData.Param
- Web.Hyperbole.Data.QueryData: instance Data.String.IsString Web.Hyperbole.Data.QueryData.ParamValue
- Web.Hyperbole.Data.QueryData: instance GHC.Classes.Eq Web.Hyperbole.Data.QueryData.Param
- Web.Hyperbole.Data.QueryData: instance GHC.Classes.Eq Web.Hyperbole.Data.QueryData.ParamValue
- Web.Hyperbole.Data.QueryData: instance GHC.Classes.Ord Web.Hyperbole.Data.QueryData.Param
- Web.Hyperbole.Data.QueryData: instance GHC.Classes.Ord Web.Hyperbole.Data.QueryData.ParamValue
- Web.Hyperbole.Data.QueryData: instance GHC.Read.Read a => Web.Hyperbole.Data.QueryData.FromParam [a]
- Web.Hyperbole.Data.QueryData: instance GHC.Show.Show Web.Hyperbole.Data.QueryData.Param
- Web.Hyperbole.Data.QueryData: instance GHC.Show.Show Web.Hyperbole.Data.QueryData.ParamValue
- Web.Hyperbole.Data.QueryData: instance GHC.Show.Show a => Web.Hyperbole.Data.QueryData.ToParam [a]
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.DefaultParam Data.Text.Internal.Text
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam Data.Text.Internal.Text
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam Data.Time.Clock.Internal.UTCTime.UTCTime
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam GHC.Num.Integer.Integer
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam GHC.Types.Bool
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam GHC.Types.Char
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam GHC.Types.Double
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam GHC.Types.Float
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam GHC.Types.Int
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam GHC.Types.Word
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam GHC.Word.Word16
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam GHC.Word.Word32
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam GHC.Word.Word64
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam GHC.Word.Word8
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.FromParam a => Web.Hyperbole.Data.QueryData.FromParam (GHC.Maybe.Maybe a)
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam Data.Text.Internal.Text
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam Data.Time.Clock.Internal.UTCTime.UTCTime
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam GHC.Num.Integer.Integer
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam GHC.Types.Bool
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam GHC.Types.Char
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam GHC.Types.Double
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam GHC.Types.Float
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam GHC.Types.Int
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam GHC.Types.Word
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam GHC.Word.Word16
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam GHC.Word.Word32
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam GHC.Word.Word64
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam GHC.Word.Word8
- Web.Hyperbole.Data.QueryData: instance Web.Hyperbole.Data.QueryData.ToParam a => Web.Hyperbole.Data.QueryData.ToParam (GHC.Maybe.Maybe a)
- Web.Hyperbole.Data.QueryData: newtype Param
- Web.Hyperbole.Data.QueryData: newtype ParamValue
- Web.Hyperbole.Data.QueryData: parseParam :: FromParam a => ParamValue -> Either Text a
- Web.Hyperbole.Data.QueryData: parseParams :: (Traversable t, FromParam a) => t ParamValue -> Either Text (t a)
- Web.Hyperbole.Data.QueryData: readQueryParam :: Read a => ParamValue -> Either Text a
- Web.Hyperbole.Data.QueryData: showQueryParam :: Show a => a -> ParamValue
- Web.Hyperbole.Data.QueryData: toList :: QueryData -> [(Param, ParamValue)]
- Web.Hyperbole.Data.QueryData: toParam :: ToParam a => a -> ParamValue
- Web.Hyperbole.Data.Session: ($dmcookiePath) :: Session a => Maybe [Segment]
- Web.Hyperbole.Data.Session: ($dmsessionKey) :: (Session a, Generic a, GDatatypeName (Rep a)) => Param
- Web.Hyperbole.Data.Session: Cookie :: Param -> Maybe ParamValue -> Maybe [Segment] -> Cookie
- Web.Hyperbole.Data.Session: Cookies :: Map Param Cookie -> Cookies
- Web.Hyperbole.Data.Session: [key] :: Cookie -> Param
- Web.Hyperbole.Data.Session: [path] :: Cookie -> Maybe [Segment]
- Web.Hyperbole.Data.Session: [value] :: Cookie -> Maybe ParamValue
- Web.Hyperbole.Data.Session: class GDatatypeName (f :: k -> Type)
- Web.Hyperbole.Data.Session: class Session a
- Web.Hyperbole.Data.Session: cookiePath :: Session a => Maybe [Segment]
- Web.Hyperbole.Data.Session: data Cookie
- Web.Hyperbole.Data.Session: delete :: Param -> Cookies -> Cookies
- Web.Hyperbole.Data.Session: deletedCookie :: Session a => Cookie
- Web.Hyperbole.Data.Session: fromList :: [Cookie] -> Cookies
- Web.Hyperbole.Data.Session: gDatatypeName :: forall (p :: k). GDatatypeName f => f p -> Text
- Web.Hyperbole.Data.Session: insert :: Cookie -> Cookies -> Cookies
- Web.Hyperbole.Data.Session: instance GHC.Base.Monoid Web.Hyperbole.Data.Session.Cookies
- Web.Hyperbole.Data.Session: instance GHC.Base.Semigroup Web.Hyperbole.Data.Session.Cookies
- Web.Hyperbole.Data.Session: instance GHC.Classes.Eq Web.Hyperbole.Data.Session.Cookie
- Web.Hyperbole.Data.Session: instance GHC.Classes.Eq Web.Hyperbole.Data.Session.Cookies
- Web.Hyperbole.Data.Session: instance GHC.Show.Show Web.Hyperbole.Data.Session.Cookie
- Web.Hyperbole.Data.Session: instance GHC.Show.Show Web.Hyperbole.Data.Session.Cookies
- Web.Hyperbole.Data.Session: instance forall k (d :: GHC.Generics.Meta) (f :: k -> *). GHC.Generics.Datatype d => Web.Hyperbole.Data.Session.GDatatypeName (GHC.Generics.M1 GHC.Generics.D d f)
- Web.Hyperbole.Data.Session: lookup :: Param -> Cookies -> Maybe ParamValue
- Web.Hyperbole.Data.Session: newtype Cookies
- Web.Hyperbole.Data.Session: sessionCookie :: (Session a, ToParam a) => a -> Cookie
- Web.Hyperbole.Data.Session: sessionKey :: Session a => Param
- Web.Hyperbole.Data.Session: toList :: Cookies -> [Cookie]
- Web.Hyperbole.Effect.Event: getEvent :: forall id (es :: [Effect]). (HyperView id es, Hyperbole :> es) => Eff es (Maybe (Event id (Action id)))
- Web.Hyperbole.Effect.Event: lookupEvent :: Query -> Maybe (Event Text Text)
- Web.Hyperbole.Effect.Event: lookupParam :: ByteString -> Query -> Maybe Text
- Web.Hyperbole.Effect.Handler: class RunHandlers (views :: [Type]) (es :: [Effect])
- Web.Hyperbole.Effect.Handler: guardNoEvent :: forall (es :: [Effect]). Hyperbole :> es => Eff es ()
- Web.Hyperbole.Effect.Handler: instance (Web.Hyperbole.HyperView.HyperView view es, Web.Hyperbole.Effect.Handler.RunHandlers views es) => Web.Hyperbole.Effect.Handler.RunHandlers (view : views) es
- Web.Hyperbole.Effect.Handler: instance Web.Hyperbole.Effect.Handler.RunHandlers '[] es
- Web.Hyperbole.Effect.Handler: loadToResponse :: forall (es :: [Effect]) (total :: [Type]). Eff es (View (Root total) ()) -> Eff es Response
- Web.Hyperbole.Effect.Handler: runHandler :: forall id (es :: [Effect]). (HyperView id es, Hyperbole :> es) => (Action id -> Eff (Reader id ': es) (View id ())) -> Eff es ()
- Web.Hyperbole.Effect.Handler: runHandlers :: RunHandlers views es => Eff es ()
- Web.Hyperbole.Effect.Handler: runLoad :: forall (views :: [Type]) (es :: [Effect]). (Hyperbole :> es, RunHandlers views es) => Eff es (View (Root views) ()) -> Eff es Response
- Web.Hyperbole.Effect.Hyperbole: HyperState :: Request -> Client -> HyperState
- Web.Hyperbole.Effect.Hyperbole: [RespondEarly] :: forall (a :: Type -> Type) b. Response -> Hyperbole a b
- Web.Hyperbole.Effect.Hyperbole: [client] :: HyperState -> Client
- Web.Hyperbole.Effect.Hyperbole: [request] :: HyperState -> Request
- Web.Hyperbole.Effect.Hyperbole: data HyperState
- Web.Hyperbole.Effect.Response: respondEarly :: forall (es :: [Effect]) id. (Hyperbole :> es, HyperView id es) => id -> View id () -> Eff es ()
- Web.Hyperbole.Effect.Server: Client :: Cookies -> Maybe QueryData -> Client
- Web.Hyperbole.Effect.Server: ContentHtml :: ContentType
- Web.Hyperbole.Effect.Server: ContentText :: ContentType
- Web.Hyperbole.Effect.Server: Empty :: Response
- Web.Hyperbole.Effect.Server: Err :: ResponseError -> Response
- Web.Hyperbole.Effect.Server: ErrAuth :: ResponseError
- Web.Hyperbole.Effect.Server: ErrNotHandled :: Event Text Text -> ResponseError
- Web.Hyperbole.Effect.Server: ErrOther :: Text -> ResponseError
- Web.Hyperbole.Effect.Server: ErrParse :: Text -> ResponseError
- Web.Hyperbole.Effect.Server: ErrQuery :: Text -> ResponseError
- Web.Hyperbole.Effect.Server: ErrSession :: Param -> Text -> ResponseError
- Web.Hyperbole.Effect.Server: Event :: id -> act -> Event id act
- Web.Hyperbole.Effect.Server: Host :: ByteString -> Host
- Web.Hyperbole.Effect.Server: InvalidMessage :: Text -> SocketError
- Web.Hyperbole.Effect.Server: Metadata :: [(ByteString, Text)] -> Metadata
- Web.Hyperbole.Effect.Server: NotFound :: Response
- Web.Hyperbole.Effect.Server: Redirect :: Url -> Response
- Web.Hyperbole.Effect.Server: Request :: Host -> [Segment] -> Query -> ByteString -> Method -> Cookies -> Request
- Web.Hyperbole.Effect.Server: Response :: TargetViewId -> View () () -> Response
- Web.Hyperbole.Effect.Server: TargetViewId :: Text -> TargetViewId
- Web.Hyperbole.Effect.Server: [LoadRequest] :: forall (a :: Type -> Type). Server a Request
- Web.Hyperbole.Effect.Server: [SendResponse] :: forall (a :: Type -> Type). Client -> Response -> Server a ()
- Web.Hyperbole.Effect.Server: [action] :: Event id act -> act
- Web.Hyperbole.Effect.Server: [body] :: Request -> ByteString
- Web.Hyperbole.Effect.Server: [cookies] :: Request -> Cookies
- Web.Hyperbole.Effect.Server: [host] :: Request -> Host
- Web.Hyperbole.Effect.Server: [method] :: Request -> Method
- Web.Hyperbole.Effect.Server: [path] :: Request -> [Segment]
- Web.Hyperbole.Effect.Server: [query] :: Request -> Query
- Web.Hyperbole.Effect.Server: [session] :: Client -> Cookies
- Web.Hyperbole.Effect.Server: [text] :: Host -> ByteString
- Web.Hyperbole.Effect.Server: [viewId] :: Event id act -> id
- Web.Hyperbole.Effect.Server: contentType :: ContentType -> (HeaderName, ByteString)
- Web.Hyperbole.Effect.Server: cookiesFromHeader :: [(ByteString, ByteString)] -> Cookies
- Web.Hyperbole.Effect.Server: data Client
- Web.Hyperbole.Effect.Server: data ContentType
- Web.Hyperbole.Effect.Server: data Event id act
- Web.Hyperbole.Effect.Server: data Request
- Web.Hyperbole.Effect.Server: data Response
- Web.Hyperbole.Effect.Server: data ResponseError
- Web.Hyperbole.Effect.Server: data Server (a :: Type -> Type) b
- Web.Hyperbole.Effect.Server: data SocketError
- Web.Hyperbole.Effect.Server: errNotHandled :: Event Text Text -> String
- Web.Hyperbole.Effect.Server: instance (GHC.Show.Show act, GHC.Show.Show id) => GHC.Show.Show (Web.Hyperbole.Effect.Server.Event id act)
- Web.Hyperbole.Effect.Server: instance GHC.Base.Monoid Web.Hyperbole.Effect.Server.Metadata
- Web.Hyperbole.Effect.Server: instance GHC.Base.Semigroup Web.Hyperbole.Effect.Server.Metadata
- Web.Hyperbole.Effect.Server: instance GHC.Classes.Eq Web.Hyperbole.Effect.Server.SocketError
- Web.Hyperbole.Effect.Server: instance GHC.Show.Show Web.Hyperbole.Effect.Server.Host
- Web.Hyperbole.Effect.Server: instance GHC.Show.Show Web.Hyperbole.Effect.Server.Request
- Web.Hyperbole.Effect.Server: instance GHC.Show.Show Web.Hyperbole.Effect.Server.ResponseError
- Web.Hyperbole.Effect.Server: instance GHC.Show.Show Web.Hyperbole.Effect.Server.SocketError
- Web.Hyperbole.Effect.Server: newtype Host
- Web.Hyperbole.Effect.Server: newtype Metadata
- Web.Hyperbole.Effect.Server: newtype TargetViewId
- Web.Hyperbole.Effect.Server: renderCookie :: [Segment] -> Cookie -> ByteString
- Web.Hyperbole.Effect.Server: runServerSockets :: forall (es :: [Effect]). IOE :> es => Connection -> Request -> Eff (Server ': es) Response -> Eff es Response
- Web.Hyperbole.Effect.Server: runServerWai :: forall (es :: [Effect]) a. IOE :> es => (ByteString -> ByteString) -> Request -> (Response -> IO ResponseReceived) -> Eff (Server ': es) a -> Eff es (Maybe ResponseReceived)
- Web.Hyperbole.HyperView: ($dmparseAction) :: (ViewAction a, Read a) => Text -> Maybe a
- Web.Hyperbole.HyperView: ($dmparseViewId) :: (ViewId a, Read a) => Text -> Maybe a
- Web.Hyperbole.HyperView: ($dmtoAction) :: (ViewAction a, Show a) => a -> Text
- Web.Hyperbole.HyperView: ($dmtoViewId) :: (ViewId a, Show a) => a -> Text
- Web.Hyperbole.HyperView: --
- Web.Hyperbole.HyperView: -- </pre>
- Web.Hyperbole.HyperView: -- <pre>
- Web.Hyperbole.HyperView: -- data Action Message = SetMessage Text | ClearMessage
- Web.Hyperbole.HyperView: -- deriving (Show, Read, ViewAction)
- Web.Hyperbole.HyperView: -- the page knows how to handle them
- Web.Hyperbole.HyperView: -- type Require = '[ChildView]
- Web.Hyperbole.HyperView: -- | Include any child hyperviews here. The compiler will make sure that
- Web.Hyperbole.HyperView: Root :: Root (views :: [Type])
- Web.Hyperbole.HyperView: class HasViewId (m :: k -> Type) (view :: k)
- Web.Hyperbole.HyperView: class (ViewId id, ViewAction Action id) => HyperView id (es :: [Effect]) where {
- Web.Hyperbole.HyperView: class ViewAction a
- Web.Hyperbole.HyperView: class ViewId a
- Web.Hyperbole.HyperView: data Action id;
- Web.Hyperbole.HyperView: data Root (views :: [Type])
- Web.Hyperbole.HyperView: hyper :: (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx ()
- Web.Hyperbole.HyperView: hyperUnsafe :: ViewId id => id -> View id () -> View ctx ()
- Web.Hyperbole.HyperView: instance GHC.Read.Read (Web.Hyperbole.HyperView.Action (Web.Hyperbole.HyperView.Root views))
- Web.Hyperbole.HyperView: instance GHC.Read.Read (Web.Hyperbole.HyperView.Root views)
- Web.Hyperbole.HyperView: instance GHC.Show.Show (Web.Hyperbole.HyperView.Action (Web.Hyperbole.HyperView.Root views))
- Web.Hyperbole.HyperView: instance GHC.Show.Show (Web.Hyperbole.HyperView.Root views)
- Web.Hyperbole.HyperView: instance Web.Hyperbole.HyperView.HasViewId (Effectful.Internal.Monad.Eff (Effectful.Reader.Dynamic.Reader view : es)) view
- Web.Hyperbole.HyperView: instance Web.Hyperbole.HyperView.HasViewId (Web.View.View.View ctx) ctx
- Web.Hyperbole.HyperView: instance Web.Hyperbole.HyperView.HyperView (Web.Hyperbole.HyperView.Root views) es
- Web.Hyperbole.HyperView: instance Web.Hyperbole.HyperView.ViewAction ()
- Web.Hyperbole.HyperView: instance Web.Hyperbole.HyperView.ViewAction (Web.Hyperbole.HyperView.Action (Web.Hyperbole.HyperView.Root views))
- Web.Hyperbole.HyperView: instance Web.Hyperbole.HyperView.ViewId (Web.Hyperbole.HyperView.Root views)
- Web.Hyperbole.HyperView: parseAction :: ViewAction a => Text -> Maybe a
- Web.Hyperbole.HyperView: parseViewId :: ViewId a => Text -> Maybe a
- Web.Hyperbole.HyperView: toAction :: ViewAction a => a -> Text
- Web.Hyperbole.HyperView: toViewId :: ViewId a => a -> Text
- Web.Hyperbole.HyperView: type HyperViewHandled id ctx = (ElemOr id Require ctx NotHandled id ctx Require ctx :: Constraint, CheckDescendents id ctx)
- Web.Hyperbole.HyperView: type NotDesc (id :: t) (ctx :: t1) (x :: t2) (cs :: t3) = TypeError 'Text "" ':<>: 'ShowType x ':<>: 'Text ", a child of HyperView " ':<>: 'ShowType id ':<>: 'Text ", not handled by context " ':<>: 'ShowType ctx ':$$: 'Text " Require = " ':<>: 'ShowType cs :: k
- Web.Hyperbole.HyperView: type NotHandled id (ctx :: t) (views :: [Type]) = TypeError 'Text "HyperView " ':<>: 'ShowType id ':<>: 'Text " not found in (Require " ':<>: 'ShowType ctx ':<>: 'Text ")" ':$$: 'Text " " ':<>: 'ShowType views ':$$: 'Text "Try adding it to the HyperView instance:" ':$$: 'Text " instance HyperView " ':<>: 'ShowType ctx ':<>: 'Text " where" ':$$: 'Text " type Action " ':<>: 'ShowType ctx ':<>: 'Text " = " ':<>: 'ShowType Action id ':<>: 'Text "" ':$$: 'Text " type Require " ':<>: 'ShowType ctx ':<>: 'Text " = [" ':<>: 'ShowType id ':<>: 'Text ", ...]" :: k
- Web.Hyperbole.HyperView: type NotInPage (x :: a) (total :: [a]) = TypeError 'Text "" ':<>: 'ShowType x ':<>: 'Text " not included in: " ':$$: 'Text " Page es " ':<>: 'ShowType total ':$$: 'Text "try expanding the page views to:" ':$$: 'Text " Page es " ':<>: 'ShowType x ': total :: k
- Web.Hyperbole.HyperView: type Require id = '[] :: [Type];
- Web.Hyperbole.HyperView: type family AllInPage (ids :: [a]) (total :: [a])
- Web.Hyperbole.HyperView: update :: HyperView id es => Action id -> Eff (Reader id ': es) (View id ())
- Web.Hyperbole.HyperView: viewId :: HasViewId m view => m view
- Web.Hyperbole.HyperView: }
- Web.Hyperbole.Route: ($dmbaseRoute) :: (Route a, Generic a, GenRoute (Rep a)) => Maybe a
- Web.Hyperbole.Route: ($dmmatchRoute) :: (Route a, Generic a, GenRoute (Rep a)) => [Segment] -> Maybe a
- Web.Hyperbole.Route: ($dmroutePath) :: (Route a, Generic a, Eq a, GenRoute (Rep a)) => a -> [Segment]
- Web.Hyperbole.Route: data Url
- Web.Hyperbole.Route: findRoute :: Route a => [Segment] -> Maybe a
- Web.Hyperbole.Route: pathUrl :: [Segment] -> Url
- Web.Hyperbole.Route: routeUrl :: Route a => a -> Url
- Web.Hyperbole.View: ($dmcolorName) :: (ToColor a, Show a) => a -> Text
- Web.Hyperbole.View: Absolute :: Position
- Web.Hyperbole.View: AlignCenter :: Align
- Web.Hyperbole.View: AlignJustify :: Align
- Web.Hyperbole.View: AlignLeft :: Align
- Web.Hyperbole.View: AlignRight :: Align
- Web.Hyperbole.View: All :: a -> Sides a
- Web.Hyperbole.View: B :: a -> Sides a
- Web.Hyperbole.View: BL :: a -> a -> Sides a
- Web.Hyperbole.View: BR :: a -> a -> Sides a
- Web.Hyperbole.View: BgColor :: HexColor -> TransitionProperty
- Web.Hyperbole.View: Block :: Display
- Web.Hyperbole.View: Color :: HexColor -> TransitionProperty
- Web.Hyperbole.View: Decimal :: ListType
- Web.Hyperbole.View: Disc :: ListType
- Web.Hyperbole.View: Fixed :: Position
- Web.Hyperbole.View: Height :: PxRem -> TransitionProperty
- Web.Hyperbole.View: HexColor :: Text -> HexColor
- Web.Hyperbole.View: Inner :: Inner
- Web.Hyperbole.View: L :: a -> Sides a
- Web.Hyperbole.View: MaxWidth :: Int -> Media
- Web.Hyperbole.View: MinWidth :: Int -> Media
- Web.Hyperbole.View: None :: None
- Web.Hyperbole.View: Pct :: Float -> Length
- Web.Hyperbole.View: PxRem :: PxRem -> Length
- Web.Hyperbole.View: R :: a -> Sides a
- Web.Hyperbole.View: Relative :: Position
- Web.Hyperbole.View: Sticky :: Position
- Web.Hyperbole.View: T :: a -> Sides a
- Web.Hyperbole.View: TL :: a -> a -> Sides a
- Web.Hyperbole.View: TR :: a -> a -> Sides a
- Web.Hyperbole.View: TRBL :: a -> a -> a -> a -> Sides a
- Web.Hyperbole.View: Url :: Text -> Text -> [Segment] -> Query -> Url
- Web.Hyperbole.View: Width :: PxRem -> TransitionProperty
- Web.Hyperbole.View: X :: a -> Sides a
- Web.Hyperbole.View: XY :: a -> a -> Sides a
- Web.Hyperbole.View: Y :: a -> Sides a
- Web.Hyperbole.View: [domain] :: Url -> Text
- Web.Hyperbole.View: [path] :: Url -> [Segment]
- Web.Hyperbole.View: [query] :: Url -> Query
- Web.Hyperbole.View: [scheme] :: Url -> Text
- Web.Hyperbole.View: active :: Mod c -> Mod c
- Web.Hyperbole.View: att :: Name -> AttValue -> Mod c
- Web.Hyperbole.View: bg :: ToColor clr => clr -> Mod ctx
- Web.Hyperbole.View: bold :: Mod c
- Web.Hyperbole.View: border :: Sides PxRem -> Mod c
- Web.Hyperbole.View: borderColor :: ToColor clr => clr -> Mod ctx
- Web.Hyperbole.View: class ToColor a
- Web.Hyperbole.View: cleanSegment :: Segment -> Segment
- Web.Hyperbole.View: color :: ToColor clr => clr -> Mod ctx
- Web.Hyperbole.View: colorName :: ToColor a => a -> Text
- Web.Hyperbole.View: colorValue :: ToColor a => a -> HexColor
- Web.Hyperbole.View: cssResetLink :: Text
- Web.Hyperbole.View: data Align
- Web.Hyperbole.View: data Attributes (c :: k)
- Web.Hyperbole.View: data Display
- Web.Hyperbole.View: data Inner
- Web.Hyperbole.View: data Layer c a
- Web.Hyperbole.View: data Length
- Web.Hyperbole.View: data ListType
- Web.Hyperbole.View: data Media
- Web.Hyperbole.View: data Ms
- Web.Hyperbole.View: data None
- Web.Hyperbole.View: data Position
- Web.Hyperbole.View: data PxRem
- Web.Hyperbole.View: data Shadow
- Web.Hyperbole.View: data Sides a
- Web.Hyperbole.View: data TableHead a
- Web.Hyperbole.View: data TransitionProperty
- Web.Hyperbole.View: data Url
- Web.Hyperbole.View: data View context a
- Web.Hyperbole.View: display :: (Style Display a, ToClassName a) => a -> Mod c
- Web.Hyperbole.View: el_ :: View c () -> View c ()
- Web.Hyperbole.View: even :: Mod c -> Mod c
- Web.Hyperbole.View: flexCol :: Mod c
- Web.Hyperbole.View: flexRow :: Mod c
- Web.Hyperbole.View: fontSize :: Length -> Mod c
- Web.Hyperbole.View: gap :: Length -> Mod c
- Web.Hyperbole.View: grow :: Mod c
- Web.Hyperbole.View: height :: Length -> Mod c
- Web.Hyperbole.View: hide :: Mod c
- Web.Hyperbole.View: hover :: Mod c -> Mod c
- Web.Hyperbole.View: hyper :: (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx ()
- Web.Hyperbole.View: italic :: Mod c
- Web.Hyperbole.View: layer :: Mod c -> View c () -> Layer c ()
- Web.Hyperbole.View: layout :: Mod c -> View c () -> View c ()
- Web.Hyperbole.View: list :: (ToClassName a, Style ListType a) => a -> Mod c
- Web.Hyperbole.View: media :: Media -> Mod c -> Mod c
- Web.Hyperbole.View: minHeight :: Length -> Mod c
- Web.Hyperbole.View: minWidth :: Length -> Mod c
- Web.Hyperbole.View: newtype HexColor
- Web.Hyperbole.View: odd :: Mod c -> Mod c
- Web.Hyperbole.View: offset :: Sides Length -> Mod c
- Web.Hyperbole.View: opacity :: Float -> Mod c
- Web.Hyperbole.View: pad :: Sides Length -> Mod c
- Web.Hyperbole.View: parent :: Text -> Mod c -> Mod c
- Web.Hyperbole.View: pathSegments :: Text -> [Segment]
- Web.Hyperbole.View: pathUrl :: [Segment] -> Url
- Web.Hyperbole.View: pointer :: Mod c
- Web.Hyperbole.View: popup :: Sides Length -> Mod c
- Web.Hyperbole.View: position :: Position -> Mod c
- Web.Hyperbole.View: renderLazyByteString :: View () () -> ByteString
- Web.Hyperbole.View: renderLazyText :: View () () -> Text
- Web.Hyperbole.View: renderPath :: [Segment] -> Text
- Web.Hyperbole.View: renderText :: View () () -> Text
- Web.Hyperbole.View: renderUrl :: Url -> Text
- Web.Hyperbole.View: root :: Mod c
- Web.Hyperbole.View: rounded :: Length -> Mod c
- Web.Hyperbole.View: scroll :: Mod c
- Web.Hyperbole.View: shadow :: (Style Shadow a, ToClassName a) => a -> Mod c
- Web.Hyperbole.View: stack :: Mod c -> Layer c () -> View c ()
- Web.Hyperbole.View: textAlign :: Align -> Mod c
- Web.Hyperbole.View: transition :: Ms -> TransitionProperty -> Mod c
- Web.Hyperbole.View: truncate :: Mod c
- Web.Hyperbole.View: type Mod context = Attributes context -> Attributes context
- Web.Hyperbole.View: underline :: Mod c
- Web.Hyperbole.View: url :: Text -> Url
- Web.Hyperbole.View: width :: Length -> Mod c
- Web.Hyperbole.View: zIndex :: Int -> Mod c
- Web.Hyperbole.View.Element: Option :: (opt -> action) -> (opt -> Bool) -> Option opt (id :: k) action
- Web.Hyperbole.View.Element: [selected] :: Option opt (id :: k) action -> opt -> Bool
- Web.Hyperbole.View.Element: [toAction] :: Option opt (id :: k) action -> opt -> action
- Web.Hyperbole.View.Element: button :: ViewAction (Action id) => Action id -> Mod id -> View id () -> View id ()
- Web.Hyperbole.View.Element: data Option opt (id :: k) action
- Web.Hyperbole.View.Element: dropdown :: ViewAction (Action id) => (opt -> Action id) -> (opt -> Bool) -> Mod id -> View (Option opt id (Action id)) () -> View id ()
- Web.Hyperbole.View.Element: option :: (ViewAction (Action id), Eq opt) => opt -> View (Option opt id (Action id)) () -> View (Option opt id (Action id)) ()
- Web.Hyperbole.View.Element: route :: Route a => a -> Mod c -> View c () -> View c ()
- Web.Hyperbole.View.Element: search :: ViewAction (Action id) => (Text -> Action id) -> DelayMs -> Mod id -> View id ()
- Web.Hyperbole.View.Element: selected :: Bool -> Mod id
- Web.Hyperbole.View.Embed: cssResetEmbed :: ByteString
- Web.Hyperbole.View.Embed: cssResetLink :: Text
- Web.Hyperbole.View.Event: Alt :: Key
- Web.Hyperbole.View.Event: ArrowDown :: Key
- Web.Hyperbole.View.Event: ArrowLeft :: Key
- Web.Hyperbole.View.Event: ArrowRight :: Key
- Web.Hyperbole.View.Event: ArrowUp :: Key
- Web.Hyperbole.View.Event: CapsLock :: Key
- Web.Hyperbole.View.Event: Control :: Key
- Web.Hyperbole.View.Event: Enter :: Key
- Web.Hyperbole.View.Event: Escape :: Key
- Web.Hyperbole.View.Event: Fn :: Key
- Web.Hyperbole.View.Event: Meta :: Key
- Web.Hyperbole.View.Event: OtherKey :: Text -> Key
- Web.Hyperbole.View.Event: Shift :: Key
- Web.Hyperbole.View.Event: Space :: Key
- Web.Hyperbole.View.Event: data Key
- Web.Hyperbole.View.Event: dataTarget :: ViewId a => a -> Mod x
- Web.Hyperbole.View.Event: instance GHC.Read.Read Web.Hyperbole.View.Event.Key
- Web.Hyperbole.View.Event: instance GHC.Show.Show Web.Hyperbole.View.Event.Key
- Web.Hyperbole.View.Event: keyDataAttribute :: Key -> Text
- Web.Hyperbole.View.Event: onClick :: ViewAction (Action id) => Action id -> Mod id
- Web.Hyperbole.View.Event: onDblClick :: ViewAction (Action id) => Action id -> Mod id
- Web.Hyperbole.View.Event: onInput :: ViewAction (Action id) => (Text -> Action id) -> DelayMs -> Mod id
- Web.Hyperbole.View.Event: onKeyDown :: ViewAction (Action id) => Key -> Action id -> Mod id
- Web.Hyperbole.View.Event: onKeyUp :: ViewAction (Action id) => Key -> Action id -> Mod id
- Web.Hyperbole.View.Event: onLoad :: ViewAction (Action id) => Action id -> DelayMs -> Mod id
- Web.Hyperbole.View.Event: onRequest :: Mod id -> Mod id
- Web.Hyperbole.View.Event: onSubmit :: ViewAction (Action id) => Action id -> Mod id
- Web.Hyperbole.View.Event: target :: (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx ()
- Web.Hyperbole.View.Event: toActionInput :: ViewAction a => (Text -> a) -> Text
- Web.Hyperbole.View.Event: type DelayMs = Int
- Web.Hyperbole.View.Forms: ($dmcollectValids) :: (Form form val, Generic (form val), GCollect (Rep (form val)) val) => form val -> [val ()]
- Web.Hyperbole.View.Forms: ($dmformParse) :: (Form form val, Generic (form Identity), GFormParse (Rep (form Identity))) => Form -> Either Text (form Identity)
- Web.Hyperbole.View.Forms: ($dmgenFieldsWith) :: (Form form val, Generic (form val), Generic (form (FormField val)), GConvert (Rep (form val)) (Rep (form (FormField val)))) => form val -> form (FormField val)
- Web.Hyperbole.View.Forms: ($dmgenForm) :: (Form form val, Generic (form val), GenFields (Rep (form val))) => form val
- Web.Hyperbole.View.Forms: Country :: InputType
- Web.Hyperbole.View.Forms: CountryName :: InputType
- Web.Hyperbole.View.Forms: CurrentPassword :: InputType
- Web.Hyperbole.View.Forms: Email :: InputType
- Web.Hyperbole.View.Forms: FormField :: FieldName a -> v a -> FormField (v :: k -> Type) (a :: k)
- Web.Hyperbole.View.Forms: FormFields :: id -> FormFields id
- Web.Hyperbole.View.Forms: FormOptions :: (String -> String) -> FormOptions
- Web.Hyperbole.View.Forms: Input :: FieldName a -> valid a -> Input id (valid :: Type -> Type) a
- Web.Hyperbole.View.Forms: Invalid :: Text -> Validated (a :: k)
- Web.Hyperbole.View.Forms: Name :: InputType
- Web.Hyperbole.View.Forms: NewPassword :: InputType
- Web.Hyperbole.View.Forms: NotInvalid :: Validated (a :: k)
- Web.Hyperbole.View.Forms: Number :: InputType
- Web.Hyperbole.View.Forms: OneTimeCode :: InputType
- Web.Hyperbole.View.Forms: Organization :: InputType
- Web.Hyperbole.View.Forms: PostalCode :: InputType
- Web.Hyperbole.View.Forms: Search :: InputType
- Web.Hyperbole.View.Forms: StreetAddress :: InputType
- Web.Hyperbole.View.Forms: TextInput :: InputType
- Web.Hyperbole.View.Forms: Username :: InputType
- Web.Hyperbole.View.Forms: Valid :: Validated (a :: k)
- Web.Hyperbole.View.Forms: [fieldLabelModifier] :: FormOptions -> String -> String
- Web.Hyperbole.View.Forms: [fieldName] :: FormField (v :: k -> Type) (a :: k) -> FieldName a
- Web.Hyperbole.View.Forms: [inputName] :: Input id (valid :: Type -> Type) a -> FieldName a
- Web.Hyperbole.View.Forms: [valid] :: Input id (valid :: Type -> Type) a -> valid a
- Web.Hyperbole.View.Forms: [validated] :: FormField (v :: k -> Type) (a :: k) -> v a
- Web.Hyperbole.View.Forms: anyInvalid :: forall form (val :: Type -> Type). (Form form val, ValidationState val) => form val -> Bool
- Web.Hyperbole.View.Forms: class Form (form :: Type -> Type -> Type) (val :: Type -> Type) | form -> val
- Web.Hyperbole.View.Forms: class FromParam a
- Web.Hyperbole.View.Forms: class GenField (f :: Type -> Type) a
- Web.Hyperbole.View.Forms: class GenFields (f :: k -> Type)
- Web.Hyperbole.View.Forms: class Generic a
- Web.Hyperbole.View.Forms: collectValids :: Form form val => form val -> [val ()]
- Web.Hyperbole.View.Forms: data FieldName (a :: k)
- Web.Hyperbole.View.Forms: data FormField (v :: k -> Type) (a :: k)
- Web.Hyperbole.View.Forms: data FormFields id
- Web.Hyperbole.View.Forms: data FormOptions
- Web.Hyperbole.View.Forms: data Identity a
- Web.Hyperbole.View.Forms: data Input id (valid :: Type -> Type) a
- Web.Hyperbole.View.Forms: data InputType
- Web.Hyperbole.View.Forms: data Invalid (a :: k)
- Web.Hyperbole.View.Forms: data Validated (a :: k)
- Web.Hyperbole.View.Forms: defaultFormOptions :: FormOptions
- Web.Hyperbole.View.Forms: field :: forall id v a. FormField v a -> (v a -> Mod (FormFields id)) -> View (Input id v a) () -> View (FormFields id) ()
- Web.Hyperbole.View.Forms: fieldValid :: View (Input id v a) (v a)
- Web.Hyperbole.View.Forms: form :: forall (form :: (Type -> Type) -> Type) (v :: Type -> Type) id. (Form form v, ViewAction (Action id)) => Action id -> Mod id -> View (FormFields id) () -> View id ()
- Web.Hyperbole.View.Forms: formData :: forall form (val :: Type -> Type) (es :: [Effect]). (Form form val, Hyperbole :> es) => Eff es (form Identity)
- Web.Hyperbole.View.Forms: formFields :: forall form (val :: Type -> Type). Form form val => form (FormField val)
- Web.Hyperbole.View.Forms: formFieldsWith :: forall form (val :: Type -> Type). Form form val => form val -> form (FormField val)
- Web.Hyperbole.View.Forms: formLookupParam :: FromParam a => Param -> Form -> Either Text (Maybe a)
- Web.Hyperbole.View.Forms: formParse :: Form form val => Form -> Either Text (form Identity)
- Web.Hyperbole.View.Forms: formParseParam :: FromParam a => Param -> Form -> Either Text a
- Web.Hyperbole.View.Forms: gGenFields :: forall (p :: k). GenFields f => f p
- Web.Hyperbole.View.Forms: genField :: GenField f a => String -> Field f a
- Web.Hyperbole.View.Forms: genFieldsWith :: Form form val => form val -> form (FormField val)
- Web.Hyperbole.View.Forms: genForm :: Form form val => form val
- Web.Hyperbole.View.Forms: input :: forall id (v :: Type -> Type) a. InputType -> Mod (Input id v a) -> View (Input id v a) ()
- Web.Hyperbole.View.Forms: instance (GHC.Generics.Selector s, Web.Hyperbole.Data.QueryData.FromParam a) => Web.Hyperbole.View.Forms.GFormParse (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a))
- Web.Hyperbole.View.Forms: instance (GHC.Generics.Selector s, Web.Hyperbole.View.Forms.GenField f a, Web.Hyperbole.View.Forms.Field f a GHC.Types.~ f a) => Web.Hyperbole.View.Forms.GenFields (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R (f a)))
- Web.Hyperbole.View.Forms: instance (GHC.Generics.Selector s, Web.Hyperbole.View.Forms.GenFieldFrom f g a, Web.Hyperbole.View.Forms.Field g a GHC.Types.~ g a) => Web.Hyperbole.View.Forms.GConvert (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R (f a))) (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R (g a)))
- Web.Hyperbole.View.Forms: instance (GHC.Generics.Selector s, Web.Hyperbole.View.Forms.MergeField a b c) => Web.Hyperbole.View.Forms.GMerge (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a)) (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R b)) (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R c))
- Web.Hyperbole.View.Forms: instance (GHC.Generics.Selector s, Web.Hyperbole.View.Forms.ValidationState v) => Web.Hyperbole.View.Forms.GCollect (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R (v a))) v
- Web.Hyperbole.View.Forms: instance GHC.Show.Show Web.Hyperbole.View.Forms.InputType
- Web.Hyperbole.View.Forms: instance Web.Hyperbole.View.Forms.GCollect GHC.Generics.U1 v
- Web.Hyperbole.View.Forms: instance Web.Hyperbole.View.Forms.GenField (Web.Hyperbole.View.Forms.FormField GHC.Maybe.Maybe) a
- Web.Hyperbole.View.Forms: instance Web.Hyperbole.View.Forms.GenField (Web.Hyperbole.View.Forms.FormField Web.Hyperbole.View.Forms.Validated) a
- Web.Hyperbole.View.Forms: instance Web.Hyperbole.View.Forms.GenField GHC.Maybe.Maybe a
- Web.Hyperbole.View.Forms: instance Web.Hyperbole.View.Forms.GenField Web.Hyperbole.View.Forms.FieldName a
- Web.Hyperbole.View.Forms: instance Web.Hyperbole.View.Forms.GenField Web.Hyperbole.View.Forms.Validated a
- Web.Hyperbole.View.Forms: instance Web.Hyperbole.View.Forms.GenFieldFrom val (Web.Hyperbole.View.Forms.FormField val) a
- Web.Hyperbole.View.Forms: instance Web.Hyperbole.View.Forms.GenFields GHC.Generics.U1
- Web.Hyperbole.View.Forms: instance Web.Hyperbole.View.Forms.ValidationState Web.Hyperbole.View.Forms.Validated
- Web.Hyperbole.View.Forms: instance forall k (a :: k). GHC.Base.Monoid (Web.Hyperbole.View.Forms.Validated a)
- Web.Hyperbole.View.Forms: instance forall k (a :: k). GHC.Base.Semigroup (Web.Hyperbole.View.Forms.Validated a)
- Web.Hyperbole.View.Forms: instance forall k (a :: k). GHC.Show.Show (Web.Hyperbole.View.Forms.FieldName a)
- Web.Hyperbole.View.Forms: instance forall k (a :: k). GHC.Show.Show (Web.Hyperbole.View.Forms.Validated a)
- Web.Hyperbole.View.Forms: instance forall k (a :: k). Web.Hyperbole.View.Forms.MergeField (Web.Hyperbole.View.Forms.FieldName a) (Web.Hyperbole.View.Forms.Validated a) (Web.Hyperbole.View.Forms.FormField Web.Hyperbole.View.Forms.Validated a)
- Web.Hyperbole.View.Forms: instance forall k (f :: k -> *) (c :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GFormParse f => Web.Hyperbole.View.Forms.GFormParse (GHC.Generics.M1 GHC.Generics.C c f)
- Web.Hyperbole.View.Forms: instance forall k (f :: k -> *) (c :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GenFields f => Web.Hyperbole.View.Forms.GenFields (GHC.Generics.M1 GHC.Generics.C c f)
- Web.Hyperbole.View.Forms: instance forall k (f :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GFormParse f => Web.Hyperbole.View.Forms.GFormParse (GHC.Generics.M1 GHC.Generics.D d f)
- Web.Hyperbole.View.Forms: instance forall k (f :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GenFields f => Web.Hyperbole.View.Forms.GenFields (GHC.Generics.M1 GHC.Generics.D d f)
- Web.Hyperbole.View.Forms: instance forall k (f :: k -> *) (g :: k -> *). (Web.Hyperbole.View.Forms.GFormParse f, Web.Hyperbole.View.Forms.GFormParse g) => Web.Hyperbole.View.Forms.GFormParse (f GHC.Generics.:*: g)
- Web.Hyperbole.View.Forms: instance forall k (f :: k -> *) (g :: k -> *). (Web.Hyperbole.View.Forms.GenFields f, Web.Hyperbole.View.Forms.GenFields g) => Web.Hyperbole.View.Forms.GenFields (f GHC.Generics.:*: g)
- Web.Hyperbole.View.Forms: instance forall k (f :: k -> *) (v :: * -> *) (c :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GCollect f v => Web.Hyperbole.View.Forms.GCollect (GHC.Generics.M1 GHC.Generics.C c f) v
- Web.Hyperbole.View.Forms: instance forall k (f :: k -> *) (v :: * -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GCollect f v => Web.Hyperbole.View.Forms.GCollect (GHC.Generics.M1 GHC.Generics.D d f) v
- Web.Hyperbole.View.Forms: instance forall k (f :: k -> *) (v :: * -> *) (g :: k -> *). (Web.Hyperbole.View.Forms.GCollect f v, Web.Hyperbole.View.Forms.GCollect g v) => Web.Hyperbole.View.Forms.GCollect (f GHC.Generics.:*: g) v
- Web.Hyperbole.View.Forms: instance forall k (ra :: k -> *) (rb :: k -> *) (rc :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GMerge ra rb rc => Web.Hyperbole.View.Forms.GMerge (GHC.Generics.M1 GHC.Generics.C d ra) (GHC.Generics.M1 GHC.Generics.C d rb) (GHC.Generics.M1 GHC.Generics.C d rc)
- Web.Hyperbole.View.Forms: instance forall k (ra :: k -> *) (rb :: k -> *) (rc :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GMerge ra rb rc => Web.Hyperbole.View.Forms.GMerge (GHC.Generics.M1 GHC.Generics.D d ra) (GHC.Generics.M1 GHC.Generics.D d rb) (GHC.Generics.M1 GHC.Generics.D d rc)
- Web.Hyperbole.View.Forms: instance forall k (ra :: k -> *) (rc :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GConvert ra rc => Web.Hyperbole.View.Forms.GConvert (GHC.Generics.M1 GHC.Generics.C d ra) (GHC.Generics.M1 GHC.Generics.C d rc)
- Web.Hyperbole.View.Forms: instance forall k (ra :: k -> *) (rc :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GConvert ra rc => Web.Hyperbole.View.Forms.GConvert (GHC.Generics.M1 GHC.Generics.D d ra) (GHC.Generics.M1 GHC.Generics.D d rc)
- Web.Hyperbole.View.Forms: instance forall k (ra0 :: k -> *) (rb0 :: k -> *) (rc0 :: k -> *) (ra1 :: k -> *) (rb1 :: k -> *) (rc1 :: k -> *). (Web.Hyperbole.View.Forms.GMerge ra0 rb0 rc0, Web.Hyperbole.View.Forms.GMerge ra1 rb1 rc1) => Web.Hyperbole.View.Forms.GMerge (ra0 GHC.Generics.:*: ra1) (rb0 GHC.Generics.:*: rb1) (rc0 GHC.Generics.:*: rc1)
- Web.Hyperbole.View.Forms: instance forall k (ra0 :: k -> *) (rc0 :: k -> *) (ra1 :: k -> *) (rc1 :: k -> *). (Web.Hyperbole.View.Forms.GConvert ra0 rc0, Web.Hyperbole.View.Forms.GConvert ra1 rc1) => Web.Hyperbole.View.Forms.GConvert (ra0 GHC.Generics.:*: ra1) (rc0 GHC.Generics.:*: rc1)
- Web.Hyperbole.View.Forms: instance forall k (v :: k -> *) (a :: k). GHC.Show.Show (v a) => GHC.Show.Show (Web.Hyperbole.View.Forms.FormField v a)
- Web.Hyperbole.View.Forms: invalidText :: forall a id. View (Input id (Validated :: Type -> Type) a) ()
- Web.Hyperbole.View.Forms: label :: forall id (v :: Type -> Type) a. Text -> View (Input id v a) ()
- Web.Hyperbole.View.Forms: placeholder :: Text -> Mod id
- Web.Hyperbole.View.Forms: submit :: Mod (FormFields id) -> View (FormFields id) () -> View (FormFields id) ()
- Web.Hyperbole.View.Forms: textarea :: forall id (v :: Type -> Type) a. Mod (Input id v a) -> Maybe Text -> View (Input id v a) ()
- Web.Hyperbole.View.Forms: type family Field (context :: Type -> Type) a
- Web.Hyperbole.View.Forms: validate :: forall {k} (a :: k). Bool -> Text -> Validated a
+ Web.Hyperbole: ($sel:html:View) :: View c a -> Eff '[Reader c] (Html a)
+ Web.Hyperbole: (@) :: Attributable h => h -> (Attributes h -> Attributes h) -> h
+ Web.Hyperbole: FieldName :: Text -> FieldName a
+ Web.Hyperbole: ListItem :: View c a -> ListItem c a
+ Web.Hyperbole: Table :: View c a -> Table c a
+ Web.Hyperbole: TableColumn :: TableHead c () -> (dt -> View c ()) -> TableColumn c dt
+ Web.Hyperbole: TableColumns :: Eff '[State [TableColumn c dt]] a -> TableColumns c dt a
+ Web.Hyperbole: TableHead :: View id a -> TableHead id a
+ Web.Hyperbole: URI :: String -> Maybe URIAuth -> String -> String -> String -> URI
+ Web.Hyperbole: View :: Eff '[Reader c] (Html a) -> View c a
+ Web.Hyperbole: [$sel:body:Request] :: Request -> ByteString
+ Web.Hyperbole: [$sel:cookies:Request] :: Request -> Cookies
+ Web.Hyperbole: [$sel:dataCell:TableColumn] :: TableColumn c dt -> dt -> View c ()
+ Web.Hyperbole: [$sel:event:Request] :: Request -> Maybe (Event TargetViewId Encoded)
+ Web.Hyperbole: [$sel:headCell:TableColumn] :: TableColumn c dt -> TableHead c ()
+ Web.Hyperbole: [$sel:host:Request] :: Request -> Host
+ Web.Hyperbole: [$sel:html:View] :: View c a -> Eff '[Reader c] (Html a)
+ Web.Hyperbole: [$sel:method:Request] :: Request -> Method
+ Web.Hyperbole: [$sel:path:Request] :: Request -> Path
+ Web.Hyperbole: [$sel:query:Request] :: Request -> Query
+ Web.Hyperbole: [$sel:requestId:Request] :: Request -> RequestId
+ Web.Hyperbole: [uriAuthority] :: URI -> Maybe URIAuth
+ Web.Hyperbole: [uriFragment] :: URI -> String
+ Web.Hyperbole: [uriPath] :: URI -> String
+ Web.Hyperbole: [uriQuery] :: URI -> String
+ Web.Hyperbole: [uriScheme] :: URI -> String
+ Web.Hyperbole: autofocus :: Attributable h => Attributes h -> Attributes h
+ Web.Hyperbole: charset :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole: checkbox :: Bool -> View (Input id a) ()
+ Web.Hyperbole: checked :: Attributable a => Bool -> Attributes a -> Attributes a
+ Web.Hyperbole: class () => Default a
+ Web.Hyperbole: class FromEncoded a
+ Web.Hyperbole: class FromForm (form :: Type)
+ Web.Hyperbole: class FromFormF (f :: (Type -> Type) -> Type)
+ Web.Hyperbole: class () => FromJSON a
+ Web.Hyperbole: class GenFields f (form :: (Type -> Type) -> Type)
+ Web.Hyperbole: class ToEncoded a
+ Web.Hyperbole: class () => ToJSON a
+ Web.Hyperbole: class_ :: Attributable h => AttValue -> Attributes h -> Attributes h
+ Web.Hyperbole: content :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole: cssEmbed :: ByteString
+ Web.Hyperbole: data DocumentHead
+ Web.Hyperbole: data FormFields id
+ Web.Hyperbole: data () => URI
+ Web.Hyperbole: decodeFormValue :: FromParam a => Maybe Text -> Either String a
+ Web.Hyperbole: def :: Default a => a
+ Web.Hyperbole: disabled :: Styleable h => CSS h -> CSS h
+ Web.Hyperbole: document :: View DocumentHead () -> ByteString -> ByteString
+ Web.Hyperbole: fieldNames :: forall form. GenFields FieldName form => form FieldName
+ Web.Hyperbole: fromForm :: (FromForm form, Generic form, GFormParse (Rep form)) => Form -> Either String form
+ Web.Hyperbole: fromFormF :: (FromFormF f, Generic (f Identity), GFormParse (Rep (f Identity))) => Form -> Either String (f Identity)
+ Web.Hyperbole: genFields :: (GenFields f form, Generic (form f), GFieldsGen (Rep (form f))) => form f
+ Web.Hyperbole: httpEquiv :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole: img :: Text -> View c ()
+ Web.Hyperbole: infixl 5 @
+ Web.Hyperbole: isInvalid :: Validated a -> Bool
+ Web.Hyperbole: loading :: Styleable h => CSS h -> CSS h
+ Web.Hyperbole: meta :: View c ()
+ Web.Hyperbole: mobileFriendly :: View DocumentHead ()
+ Web.Hyperbole: modAttributes :: Attributable h => (Map Name AttValue -> Map Name AttValue) -> h -> h
+ Web.Hyperbole: modifyContext :: forall ctx0 ctx1. (ctx0 -> ctx1) -> View ctx1 () -> View ctx0 ()
+ Web.Hyperbole: newtype FieldName a
+ Web.Hyperbole: newtype ListItem c a
+ Web.Hyperbole: newtype Table c a
+ Web.Hyperbole: newtype TableColumns c dt a
+ Web.Hyperbole: newtype TableHead id a
+ Web.Hyperbole: newtype View c a
+ Web.Hyperbole: onMouseEnter :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a
+ Web.Hyperbole: onMouseLeave :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a
+ Web.Hyperbole: pageTitle :: Hyperbole :> es => Text -> Eff es ()
+ Web.Hyperbole: parseCookie :: (Session a, FromEncoded a) => CookieValue -> Either String a
+ Web.Hyperbole: pushEvent :: (ToJSON a, Hyperbole :> es) => Text -> a -> Eff es ()
+ Web.Hyperbole: quickStart :: View DocumentHead ()
+ Web.Hyperbole: quickStartDocument :: ByteString -> ByteString
+ Web.Hyperbole: radio :: (Eq b, ToParam b) => b -> View (Radio id a b) ()
+ Web.Hyperbole: radioGroup :: b -> View (Radio id a b) () -> View (Input id a) ()
+ Web.Hyperbole: respondError :: Hyperbole :> es => ResponseError -> Eff es a
+ Web.Hyperbole: respondErrorView :: Hyperbole :> es => Text -> View Body () -> Eff es a
+ Web.Hyperbole: routeUri :: Route a => a -> URI
+ Web.Hyperbole: script' :: ByteString -> View c ()
+ Web.Hyperbole: scriptEmbed :: ByteString
+ Web.Hyperbole: scriptEmbedSourceMap :: ByteString
+ Web.Hyperbole: scriptLiveReload :: ByteString
+ Web.Hyperbole: select :: Eq opt => opt -> View (Option opt id) () -> View (Input id a) ()
+ Web.Hyperbole: src :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole: title :: Text -> View c ()
+ Web.Hyperbole: toCookie :: (Session a, ToEncoded a) => a -> CookieValue
+ Web.Hyperbole: trigger :: (HyperView id es, HyperViewHandled id view, Hyperbole :> es) => id -> Action id -> Eff (Reader view : es) ()
+ Web.Hyperbole: type AttValue = Text
+ Web.Hyperbole: type Name = Text
+ Web.Hyperbole: type_ :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole: uri :: QuasiQuoter
+ Web.Hyperbole: usersTable :: View c ()
+ Web.Hyperbole: whenLoading :: Styleable h => (CSS h -> CSS h) -> CSS h -> CSS h
+ Web.Hyperbole.Application: ServerOptions :: (ByteString -> ByteString) -> (ResponseError -> ServerError) -> ServerOptions
+ Web.Hyperbole.Application: [$sel:serverError:ServerOptions] :: ServerOptions -> ResponseError -> ServerError
+ Web.Hyperbole.Application: [$sel:toDocument:ServerOptions] :: ServerOptions -> ByteString -> ByteString
+ Web.Hyperbole.Application: data ServerOptions
+ Web.Hyperbole.Application: defaultError :: ResponseError -> ServerError
+ Web.Hyperbole.Application: defaultErrorMessage :: ResponseError -> Text
+ Web.Hyperbole.Application: liveAppWith :: ServerOptions -> Eff '[Hyperbole, Concurrent, IOE] Response -> Application
+ Web.Hyperbole.Application: quickStartDocument :: ByteString -> ByteString
+ Web.Hyperbole.Data.Cookie: Cookie :: Key -> Maybe Path -> Maybe CookieValue -> Cookie
+ Web.Hyperbole.Data.Cookie: CookieValue :: ByteString -> CookieValue
+ Web.Hyperbole.Data.Cookie: Cookies :: Map Key Cookie -> Cookies
+ Web.Hyperbole.Data.Cookie: [$sel:key:Cookie] :: Cookie -> Key
+ Web.Hyperbole.Data.Cookie: [$sel:path:Cookie] :: Cookie -> Maybe Path
+ Web.Hyperbole.Data.Cookie: [$sel:value:Cookie] :: Cookie -> Maybe CookieValue
+ Web.Hyperbole.Data.Cookie: data Cookie
+ Web.Hyperbole.Data.Cookie: delete :: Key -> Cookies -> Cookies
+ Web.Hyperbole.Data.Cookie: fromList :: [Cookie] -> Cookies
+ Web.Hyperbole.Data.Cookie: insert :: Cookie -> Cookies -> Cookies
+ Web.Hyperbole.Data.Cookie: instance GHC.Base.Monoid Web.Hyperbole.Data.Cookie.Cookies
+ Web.Hyperbole.Data.Cookie: instance GHC.Base.Semigroup Web.Hyperbole.Data.Cookie.Cookies
+ Web.Hyperbole.Data.Cookie: instance GHC.Classes.Eq Web.Hyperbole.Data.Cookie.Cookie
+ Web.Hyperbole.Data.Cookie: instance GHC.Classes.Eq Web.Hyperbole.Data.Cookie.CookieValue
+ Web.Hyperbole.Data.Cookie: instance GHC.Classes.Eq Web.Hyperbole.Data.Cookie.Cookies
+ Web.Hyperbole.Data.Cookie: instance GHC.Show.Show Web.Hyperbole.Data.Cookie.Cookie
+ Web.Hyperbole.Data.Cookie: instance GHC.Show.Show Web.Hyperbole.Data.Cookie.CookieValue
+ Web.Hyperbole.Data.Cookie: instance GHC.Show.Show Web.Hyperbole.Data.Cookie.Cookies
+ Web.Hyperbole.Data.Cookie: lookup :: Key -> Cookies -> Maybe CookieValue
+ Web.Hyperbole.Data.Cookie: newtype CookieValue
+ Web.Hyperbole.Data.Cookie: newtype Cookies
+ Web.Hyperbole.Data.Cookie: parse :: [(ByteString, ByteString)] -> Either String Cookies
+ Web.Hyperbole.Data.Cookie: parseValue :: ByteString -> ByteString -> Either String Cookie
+ Web.Hyperbole.Data.Cookie: render :: Path -> Cookie -> ByteString
+ Web.Hyperbole.Data.Cookie: toList :: Cookies -> [Cookie]
+ Web.Hyperbole.Data.Cookie: type Key = Text
+ Web.Hyperbole.Data.Encoded: ConName :: Text -> ConName
+ Web.Hyperbole.Data.Encoded: Encoded :: ConName -> [ParamValue] -> Encoded
+ Web.Hyperbole.Data.Encoded: [$sel:text:ConName] :: ConName -> Text
+ Web.Hyperbole.Data.Encoded: class FromEncoded a
+ Web.Hyperbole.Data.Encoded: class GFromEncoded f
+ Web.Hyperbole.Data.Encoded: class GToEncoded f
+ Web.Hyperbole.Data.Encoded: class ToEncoded a
+ Web.Hyperbole.Data.Encoded: data Encoded
+ Web.Hyperbole.Data.Encoded: decode :: FromEncoded a => Text -> Maybe a
+ Web.Hyperbole.Data.Encoded: decodeEither :: FromEncoded a => Text -> Either String a
+ Web.Hyperbole.Data.Encoded: decodeParam :: Text -> ParamValue
+ Web.Hyperbole.Data.Encoded: desanitizeParamText :: Text -> Text
+ Web.Hyperbole.Data.Encoded: encode :: ToEncoded a => a -> Text
+ Web.Hyperbole.Data.Encoded: encodeParam :: ParamValue -> Text
+ Web.Hyperbole.Data.Encoded: encodedParseText :: Text -> Either String Encoded
+ Web.Hyperbole.Data.Encoded: encodedToText :: Encoded -> Text
+ Web.Hyperbole.Data.Encoded: fromResult :: Result a -> Either String a
+ Web.Hyperbole.Data.Encoded: gParseEncoded :: GFromEncoded f => Encoded -> Either String (f p, [ParamValue])
+ Web.Hyperbole.Data.Encoded: gToEncoded :: GToEncoded f => f p -> Encoded
+ Web.Hyperbole.Data.Encoded: genericDecode :: (Generic a, GFromEncoded (Rep a)) => Text -> Maybe a
+ Web.Hyperbole.Data.Encoded: genericParseEncoded :: (Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a
+ Web.Hyperbole.Data.Encoded: genericToEncoded :: (Generic a, GToEncoded (Rep a)) => a -> Encoded
+ Web.Hyperbole.Data.Encoded: instance Data.Aeson.Types.FromJSON.FromJSON Web.Hyperbole.Data.Encoded.Encoded
+ Web.Hyperbole.Data.Encoded: instance Data.Aeson.Types.ToJSON.ToJSON Web.Hyperbole.Data.Encoded.Encoded
+ Web.Hyperbole.Data.Encoded: instance Data.String.IsString Web.Hyperbole.Data.Encoded.ConName
+ Web.Hyperbole.Data.Encoded: instance GHC.Base.Monoid Web.Hyperbole.Data.Encoded.ConName
+ Web.Hyperbole.Data.Encoded: instance GHC.Base.Monoid Web.Hyperbole.Data.Encoded.Encoded
+ Web.Hyperbole.Data.Encoded: instance GHC.Base.Semigroup Web.Hyperbole.Data.Encoded.ConName
+ Web.Hyperbole.Data.Encoded: instance GHC.Base.Semigroup Web.Hyperbole.Data.Encoded.Encoded
+ Web.Hyperbole.Data.Encoded: instance GHC.Classes.Eq Web.Hyperbole.Data.Encoded.ConName
+ Web.Hyperbole.Data.Encoded: instance GHC.Classes.Eq Web.Hyperbole.Data.Encoded.Encoded
+ Web.Hyperbole.Data.Encoded: instance GHC.Show.Show Web.Hyperbole.Data.Encoded.ConName
+ Web.Hyperbole.Data.Encoded: instance GHC.Show.Show Web.Hyperbole.Data.Encoded.Encoded
+ Web.Hyperbole.Data.Encoded: instance Web.Hyperbole.Data.Encoded.FromEncoded Web.Hyperbole.Data.Encoded.Encoded
+ Web.Hyperbole.Data.Encoded: instance Web.Hyperbole.Data.Encoded.GFromEncoded GHC.Generics.U1
+ Web.Hyperbole.Data.Encoded: instance Web.Hyperbole.Data.Encoded.GToEncoded GHC.Generics.U1
+ Web.Hyperbole.Data.Encoded: instance Web.Hyperbole.Data.Encoded.ToEncoded Web.Hyperbole.Data.Encoded.Encoded
+ Web.Hyperbole.Data.Encoded: instance Web.Hyperbole.Data.Param.FromParam a => Web.Hyperbole.Data.Encoded.GFromEncoded (GHC.Generics.K1 GHC.Generics.R a)
+ Web.Hyperbole.Data.Encoded: instance Web.Hyperbole.Data.Param.ToParam a => Web.Hyperbole.Data.Encoded.GToEncoded (GHC.Generics.K1 GHC.Generics.R a)
+ Web.Hyperbole.Data.Encoded: instance forall k (c :: GHC.Generics.Meta) (f :: k -> *). (GHC.Generics.Constructor c, Web.Hyperbole.Data.Encoded.GFromEncoded f) => Web.Hyperbole.Data.Encoded.GFromEncoded (GHC.Generics.M1 GHC.Generics.C c f)
+ Web.Hyperbole.Data.Encoded: instance forall k (c :: GHC.Generics.Meta) (f :: k -> *). (GHC.Generics.Constructor c, Web.Hyperbole.Data.Encoded.GToEncoded f) => Web.Hyperbole.Data.Encoded.GToEncoded (GHC.Generics.M1 GHC.Generics.C c f)
+ Web.Hyperbole.Data.Encoded: instance forall k (f :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.Data.Encoded.GFromEncoded f => Web.Hyperbole.Data.Encoded.GFromEncoded (GHC.Generics.M1 GHC.Generics.D d f)
+ Web.Hyperbole.Data.Encoded: instance forall k (f :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.Data.Encoded.GToEncoded f => Web.Hyperbole.Data.Encoded.GToEncoded (GHC.Generics.M1 GHC.Generics.D d f)
+ Web.Hyperbole.Data.Encoded: instance forall k (f :: k -> *) (g :: k -> *). (Web.Hyperbole.Data.Encoded.GFromEncoded f, Web.Hyperbole.Data.Encoded.GFromEncoded g) => Web.Hyperbole.Data.Encoded.GFromEncoded (f GHC.Generics.:*: g)
+ Web.Hyperbole.Data.Encoded: instance forall k (f :: k -> *) (g :: k -> *). (Web.Hyperbole.Data.Encoded.GFromEncoded f, Web.Hyperbole.Data.Encoded.GFromEncoded g) => Web.Hyperbole.Data.Encoded.GFromEncoded (f GHC.Generics.:+: g)
+ Web.Hyperbole.Data.Encoded: instance forall k (f :: k -> *) (g :: k -> *). (Web.Hyperbole.Data.Encoded.GToEncoded f, Web.Hyperbole.Data.Encoded.GToEncoded g) => Web.Hyperbole.Data.Encoded.GToEncoded (f GHC.Generics.:*: g)
+ Web.Hyperbole.Data.Encoded: instance forall k (f :: k -> *) (g :: k -> *). (Web.Hyperbole.Data.Encoded.GToEncoded f, Web.Hyperbole.Data.Encoded.GToEncoded g) => Web.Hyperbole.Data.Encoded.GToEncoded (f GHC.Generics.:+: g)
+ Web.Hyperbole.Data.Encoded: instance forall k (f :: k -> *) (s :: GHC.Generics.Meta). Web.Hyperbole.Data.Encoded.GFromEncoded f => Web.Hyperbole.Data.Encoded.GFromEncoded (GHC.Generics.M1 GHC.Generics.S s f)
+ Web.Hyperbole.Data.Encoded: instance forall k (f :: k -> *) (s :: GHC.Generics.Meta). Web.Hyperbole.Data.Encoded.GToEncoded f => Web.Hyperbole.Data.Encoded.GToEncoded (GHC.Generics.M1 GHC.Generics.S s f)
+ Web.Hyperbole.Data.Encoded: newtype ConName
+ Web.Hyperbole.Data.Encoded: paramParser :: Parser ParamValue
+ Web.Hyperbole.Data.Encoded: parseEncoded :: (FromEncoded a, Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a
+ Web.Hyperbole.Data.Encoded: toEncoded :: (ToEncoded a, Generic a, GToEncoded (Rep a)) => a -> Encoded
+ Web.Hyperbole.Data.JSON: JSON :: a -> JSON a
+ Web.Hyperbole.Data.JSON: class () => FromJSON a
+ Web.Hyperbole.Data.JSON: class () => ToJSON a
+ Web.Hyperbole.Data.JSON: instance Data.Aeson.Types.FromJSON.FromJSON a => Web.Hyperbole.Data.Encoded.FromEncoded (Web.Hyperbole.Data.JSON.JSON a)
+ Web.Hyperbole.Data.JSON: instance Data.Aeson.Types.ToJSON.ToJSON a => Web.Hyperbole.Data.Encoded.ToEncoded (Web.Hyperbole.Data.JSON.JSON a)
+ Web.Hyperbole.Data.JSON: newtype JSON a
+ Web.Hyperbole.Data.Param: Param :: Text -> Param
+ Web.Hyperbole.Data.Param: ParamValue :: Text -> ParamValue
+ Web.Hyperbole.Data.Param: [$sel:text:Param] :: Param -> Text
+ Web.Hyperbole.Data.Param: [$sel:value:ParamValue] :: ParamValue -> Text
+ Web.Hyperbole.Data.Param: class FromParam a
+ Web.Hyperbole.Data.Param: class ToParam a
+ Web.Hyperbole.Data.Param: data ParamValue
+ Web.Hyperbole.Data.Param: decodeFormValue :: FromParam a => Maybe Text -> Either String a
+ Web.Hyperbole.Data.Param: genericParseParam :: (Generic a, GFromJSON Zero (Rep a)) => ParamValue -> Either String a
+ Web.Hyperbole.Data.Param: genericToParam :: (Generic a, GToJSON Zero (Rep a)) => a -> ParamValue
+ Web.Hyperbole.Data.Param: instance (Web.Hyperbole.Data.Param.FromParam a, Web.Hyperbole.Data.Param.FromParam b) => Web.Hyperbole.Data.Param.FromParam (Data.Either.Either a b)
+ Web.Hyperbole.Data.Param: instance (Web.Hyperbole.Data.Param.ToParam a, Web.Hyperbole.Data.Param.ToParam b) => Web.Hyperbole.Data.Param.ToParam (Data.Either.Either a b)
+ Web.Hyperbole.Data.Param: instance Data.String.IsString Web.Hyperbole.Data.Param.Param
+ Web.Hyperbole.Data.Param: instance Data.String.IsString Web.Hyperbole.Data.Param.ParamValue
+ Web.Hyperbole.Data.Param: instance GHC.Classes.Eq Web.Hyperbole.Data.Param.Param
+ Web.Hyperbole.Data.Param: instance GHC.Classes.Eq Web.Hyperbole.Data.Param.ParamValue
+ Web.Hyperbole.Data.Param: instance GHC.Classes.Ord Web.Hyperbole.Data.Param.Param
+ Web.Hyperbole.Data.Param: instance GHC.Show.Show Web.Hyperbole.Data.Param.Param
+ Web.Hyperbole.Data.Param: instance GHC.Show.Show Web.Hyperbole.Data.Param.ParamValue
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam (GHC.Maybe.Maybe Data.Text.Internal.Text)
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam Data.Aeson.Types.Internal.Value
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam Data.Text.Internal.Text
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam Data.Time.Clock.Internal.UTCTime.UTCTime
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam GHC.Base.String
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam GHC.Num.Integer.Integer
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam GHC.Types.Bool
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam GHC.Types.Char
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam GHC.Types.Double
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam GHC.Types.Float
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam GHC.Types.Int
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam GHC.Types.Word
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam GHC.Word.Word16
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam GHC.Word.Word32
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam GHC.Word.Word64
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam GHC.Word.Word8
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam Network.URI.URI
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam a => Web.Hyperbole.Data.Param.FromParam (GHC.Maybe.Maybe a)
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.FromParam a => Web.Hyperbole.Data.Param.FromParam [a]
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam Data.Aeson.Types.Internal.Value
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam Data.Text.Internal.Text
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam Data.Time.Clock.Internal.UTCTime.UTCTime
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam GHC.Base.String
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam GHC.Num.Integer.Integer
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam GHC.Types.Bool
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam GHC.Types.Char
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam GHC.Types.Double
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam GHC.Types.Float
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam GHC.Types.Int
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam GHC.Types.Word
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam GHC.Word.Word16
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam GHC.Word.Word32
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam GHC.Word.Word64
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam GHC.Word.Word8
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam Network.URI.URI
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam a => Web.Hyperbole.Data.Param.ToParam (GHC.Maybe.Maybe a)
+ Web.Hyperbole.Data.Param: instance Web.Hyperbole.Data.Param.ToParam a => Web.Hyperbole.Data.Param.ToParam [a]
+ Web.Hyperbole.Data.Param: jsonOptions :: Options
+ Web.Hyperbole.Data.Param: jsonParam :: ToJSON a => a -> ParamValue
+ Web.Hyperbole.Data.Param: jsonParse :: FromJSON a => ParamValue -> Either String a
+ Web.Hyperbole.Data.Param: newtype Param
+ Web.Hyperbole.Data.Param: parseParam :: (FromParam a, Generic a, GFromJSON Zero (Rep a)) => ParamValue -> Either String a
+ Web.Hyperbole.Data.Param: parseQueryParam :: FromHttpApiData a => ParamValue -> Either String a
+ Web.Hyperbole.Data.Param: readParam :: Read a => ParamValue -> Either String a
+ Web.Hyperbole.Data.Param: showParam :: Show a => a -> ParamValue
+ Web.Hyperbole.Data.Param: toParam :: (ToParam a, Generic a, GToJSON Zero (Rep a)) => a -> ParamValue
+ Web.Hyperbole.Data.Param: toQueryParam :: ToHttpApiData a => a -> ParamValue
+ Web.Hyperbole.Data.QueryData: fromQueryData :: QueryData -> Query
+ Web.Hyperbole.Data.QueryData: instance (GHC.Generics.Selector s, Web.Hyperbole.Data.Param.FromParam a, Data.Default.Internal.Default a) => Web.Hyperbole.Data.QueryData.GFromQuery (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a))
+ Web.Hyperbole.Data.QueryData: instance (GHC.Generics.Selector s, Web.Hyperbole.Data.Param.ToParam a, GHC.Classes.Eq a, Data.Default.Internal.Default a) => Web.Hyperbole.Data.QueryData.GToQuery (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a))
+ Web.Hyperbole.Data.QueryData: instance GHC.Generics.Selector s => Web.Hyperbole.Data.QueryData.GFromQuery (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R Data.Text.Internal.Text))
+ Web.Hyperbole.Data.QueryData: instance GHC.Generics.Selector s => Web.Hyperbole.Data.QueryData.GToQuery (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R Data.Text.Internal.Text))
+ Web.Hyperbole.Data.QueryData: instance GHC.IsList.IsList Web.Hyperbole.Data.QueryData.QueryData
+ Web.Hyperbole.Data.URI: (./.) :: URI -> Path -> URI
+ Web.Hyperbole.Data.URI: (.?.) :: URI -> QueryItem -> URI
+ Web.Hyperbole.Data.URI: Endpoint :: URI -> Endpoint a
+ Web.Hyperbole.Data.URI: Path :: [Segment] -> Path
+ Web.Hyperbole.Data.URI: URI :: String -> Maybe URIAuth -> String -> String -> String -> URI
+ Web.Hyperbole.Data.URI: URIAuth :: String -> String -> String -> URIAuth
+ Web.Hyperbole.Data.URI: [$sel:segments:Path] :: Path -> [Segment]
+ Web.Hyperbole.Data.URI: [$sel:uri:Endpoint] :: Endpoint a -> URI
+ Web.Hyperbole.Data.URI: [uriAuthority] :: URI -> Maybe URIAuth
+ Web.Hyperbole.Data.URI: [uriFragment] :: URI -> String
+ Web.Hyperbole.Data.URI: [uriPath] :: URI -> String
+ Web.Hyperbole.Data.URI: [uriPort] :: URIAuth -> String
+ Web.Hyperbole.Data.URI: [uriQuery] :: URI -> String
+ Web.Hyperbole.Data.URI: [uriRegName] :: URIAuth -> String
+ Web.Hyperbole.Data.URI: [uriScheme] :: URI -> String
+ Web.Hyperbole.Data.URI: [uriUserInfo] :: URIAuth -> String
+ Web.Hyperbole.Data.URI: cleanSegment :: Segment -> Segment
+ Web.Hyperbole.Data.URI: data () => URI
+ Web.Hyperbole.Data.URI: data () => URIAuth
+ Web.Hyperbole.Data.URI: infixl 5 ./.
+ Web.Hyperbole.Data.URI: instance Data.String.IsString Web.Hyperbole.Data.URI.Path
+ Web.Hyperbole.Data.URI: instance GHC.Classes.Eq Web.Hyperbole.Data.URI.Path
+ Web.Hyperbole.Data.URI: instance GHC.IsList.IsList Web.Hyperbole.Data.URI.Path
+ Web.Hyperbole.Data.URI: instance GHC.Show.Show Web.Hyperbole.Data.URI.Path
+ Web.Hyperbole.Data.URI: newtype Endpoint a
+ Web.Hyperbole.Data.URI: newtype Path
+ Web.Hyperbole.Data.URI: parseQuery :: ByteString -> Query
+ Web.Hyperbole.Data.URI: parseURIReference :: String -> Maybe URI
+ Web.Hyperbole.Data.URI: path :: Text -> Path
+ Web.Hyperbole.Data.URI: pathToText :: Bool -> Path -> Text
+ Web.Hyperbole.Data.URI: pathUri :: Path -> URI
+ Web.Hyperbole.Data.URI: queryInsert :: ByteString -> Maybe ByteString -> QueryString -> QueryString
+ Web.Hyperbole.Data.URI: queryString :: [(ByteString, Maybe ByteString)] -> QueryString
+ Web.Hyperbole.Data.URI: renderQuery :: Bool -> Query -> ByteString
+ Web.Hyperbole.Data.URI: type Query = [QueryItem]
+ Web.Hyperbole.Data.URI: type QueryItem = (ByteString, Maybe ByteString)
+ Web.Hyperbole.Data.URI: type Segment = Text
+ Web.Hyperbole.Data.URI: uri :: QuasiQuoter
+ Web.Hyperbole.Data.URI: uriToText :: URI -> Text
+ Web.Hyperbole.Document: Document :: Document
+ Web.Hyperbole.Document: DocumentHead :: DocumentHead
+ Web.Hyperbole.Document: data Document
+ Web.Hyperbole.Document: data DocumentHead
+ Web.Hyperbole.Document: document :: View DocumentHead () -> ByteString -> ByteString
+ Web.Hyperbole.Document: mobileFriendly :: View DocumentHead ()
+ Web.Hyperbole.Document: quickStart :: View DocumentHead ()
+ Web.Hyperbole.Document: quickStartDocument :: ByteString -> ByteString
+ Web.Hyperbole.Effect.Client: pageTitle :: Hyperbole :> es => Text -> Eff es ()
+ Web.Hyperbole.Effect.Client: pushEvent :: (ToJSON a, Hyperbole :> es) => Text -> a -> Eff es ()
+ Web.Hyperbole.Effect.Client: trigger :: (HyperView id es, HyperViewHandled id view, Hyperbole :> es) => id -> Action id -> Eff (Reader view : es) ()
+ Web.Hyperbole.Effect.GenRandom: Token :: Text -> Token a
+ Web.Hyperbole.Effect.GenRandom: [$sel:value:Token] :: Token a -> Text
+ Web.Hyperbole.Effect.GenRandom: [GenRandomList] :: Random a => [a] -> GenRandom m a
+ Web.Hyperbole.Effect.GenRandom: [GenRandomToken] :: Int -> GenRandom m (Token a)
+ Web.Hyperbole.Effect.GenRandom: [GenRandom] :: Random a => (a, a) -> GenRandom m a
+ Web.Hyperbole.Effect.GenRandom: data GenRandom :: Effect
+ Web.Hyperbole.Effect.GenRandom: genRandom :: (Random a, GenRandom :> es) => (a, a) -> Eff es a
+ Web.Hyperbole.Effect.GenRandom: genRandomList :: (Random a, GenRandom :> es) => [a] -> Eff es a
+ Web.Hyperbole.Effect.GenRandom: genRandomToken :: GenRandom :> es => Int -> Eff es (Token a)
+ Web.Hyperbole.Effect.GenRandom: instance forall k (a :: k). Data.Aeson.Types.FromJSON.FromJSON (Web.Hyperbole.Effect.GenRandom.Token a)
+ Web.Hyperbole.Effect.GenRandom: instance forall k (a :: k). Data.Aeson.Types.ToJSON.ToJSON (Web.Hyperbole.Effect.GenRandom.Token a)
+ Web.Hyperbole.Effect.GenRandom: instance forall k (a :: k). GHC.Classes.Eq (Web.Hyperbole.Effect.GenRandom.Token a)
+ Web.Hyperbole.Effect.GenRandom: instance forall k (a :: k). GHC.Read.Read (Web.Hyperbole.Effect.GenRandom.Token a)
+ Web.Hyperbole.Effect.GenRandom: instance forall k (a :: k). GHC.Show.Show (Web.Hyperbole.Effect.GenRandom.Token a)
+ Web.Hyperbole.Effect.GenRandom: instance forall k (a :: k). Web.Hyperbole.Data.Param.FromParam (Web.Hyperbole.Effect.GenRandom.Token a)
+ Web.Hyperbole.Effect.GenRandom: instance forall k (a :: k). Web.Hyperbole.Data.Param.ToParam (Web.Hyperbole.Effect.GenRandom.Token a)
+ Web.Hyperbole.Effect.GenRandom: newtype Token a
+ Web.Hyperbole.Effect.GenRandom: runRandom :: IOE :> es => Eff (GenRandom : es) a -> Eff es a
+ Web.Hyperbole.Effect.Hyperbole: RemoteAction :: TargetViewId -> Encoded -> Remote
+ Web.Hyperbole.Effect.Hyperbole: RemoteEvent :: Text -> Value -> Remote
+ Web.Hyperbole.Effect.Hyperbole: [RespondNow] :: Response -> Hyperbole m a
+ Web.Hyperbole.Effect.Hyperbole: [TriggerAction] :: TargetViewId -> Encoded -> Hyperbole m ()
+ Web.Hyperbole.Effect.Hyperbole: [TriggerEvent] :: Text -> Value -> Hyperbole m ()
+ Web.Hyperbole.Effect.Hyperbole: data Remote
+ Web.Hyperbole.Effect.OAuth2: AuthFlow :: URI -> Token State -> AuthFlow
+ Web.Hyperbole.Effect.OAuth2: Authenticated :: TokenType -> Maybe Int -> Maybe Scopes -> Token Access -> Maybe (Token Refresh) -> Authenticated
+ Web.Hyperbole.Effect.OAuth2: Bearer :: TokenType
+ Web.Hyperbole.Effect.OAuth2: Config :: Token ClientId -> Token ClientSecret -> Endpoint Auth -> Endpoint (Token ()) -> Config
+ Web.Hyperbole.Effect.OAuth2: OAuth2BadEnv :: String -> String -> OAuth2Error
+ Web.Hyperbole.Effect.OAuth2: OAuth2BadResponse :: String -> ByteString -> OAuth2Error
+ Web.Hyperbole.Effect.OAuth2: OAuth2TokenRequest :: HttpException -> OAuth2Error
+ Web.Hyperbole.Effect.OAuth2: Scopes :: [Text] -> Scopes
+ Web.Hyperbole.Effect.OAuth2: Token :: Text -> Token a
+ Web.Hyperbole.Effect.OAuth2: [$sel:accessToken:Authenticated] :: Authenticated -> Token Access
+ Web.Hyperbole.Effect.OAuth2: [$sel:authorize:Config] :: Config -> Endpoint Auth
+ Web.Hyperbole.Effect.OAuth2: [$sel:clientId:Config] :: Config -> Token ClientId
+ Web.Hyperbole.Effect.OAuth2: [$sel:clientSecret:Config] :: Config -> Token ClientSecret
+ Web.Hyperbole.Effect.OAuth2: [$sel:expiresIn:Authenticated] :: Authenticated -> Maybe Int
+ Web.Hyperbole.Effect.OAuth2: [$sel:redirect:AuthFlow] :: AuthFlow -> URI
+ Web.Hyperbole.Effect.OAuth2: [$sel:refreshToken:Authenticated] :: Authenticated -> Maybe (Token Refresh)
+ Web.Hyperbole.Effect.OAuth2: [$sel:scope:Authenticated] :: Authenticated -> Maybe Scopes
+ Web.Hyperbole.Effect.OAuth2: [$sel:state:AuthFlow] :: AuthFlow -> Token State
+ Web.Hyperbole.Effect.OAuth2: [$sel:token:Config] :: Config -> Endpoint (Token ())
+ Web.Hyperbole.Effect.OAuth2: [$sel:tokenType:Authenticated] :: Authenticated -> TokenType
+ Web.Hyperbole.Effect.OAuth2: [$sel:value:Token] :: Token a -> Text
+ Web.Hyperbole.Effect.OAuth2: [AuthUrl] :: URI -> Scopes -> OAuth2 m URI
+ Web.Hyperbole.Effect.OAuth2: [ExchangeAuth] :: Token Code -> OAuth2 m Authenticated
+ Web.Hyperbole.Effect.OAuth2: [ExchangeRefresh] :: Token Refresh -> OAuth2 m Authenticated
+ Web.Hyperbole.Effect.OAuth2: [ValidateCode] :: OAuth2 m (Token Code)
+ Web.Hyperbole.Effect.OAuth2: authUrl :: OAuth2 :> es => URI -> Scopes -> Eff es URI
+ Web.Hyperbole.Effect.OAuth2: data Access
+ Web.Hyperbole.Effect.OAuth2: data Auth
+ Web.Hyperbole.Effect.OAuth2: data AuthFlow
+ Web.Hyperbole.Effect.OAuth2: data Authenticated
+ Web.Hyperbole.Effect.OAuth2: data ClientId
+ Web.Hyperbole.Effect.OAuth2: data ClientSecret
+ Web.Hyperbole.Effect.OAuth2: data Code
+ Web.Hyperbole.Effect.OAuth2: data Config
+ Web.Hyperbole.Effect.OAuth2: data OAuth2 :: Effect
+ Web.Hyperbole.Effect.OAuth2: data OAuth2Error
+ Web.Hyperbole.Effect.OAuth2: data Refresh
+ Web.Hyperbole.Effect.OAuth2: data State
+ Web.Hyperbole.Effect.OAuth2: data TokenType
+ Web.Hyperbole.Effect.OAuth2: exchangeAuth :: OAuth2 :> es => Token Code -> Eff es Authenticated
+ Web.Hyperbole.Effect.OAuth2: exchangeRefresh :: OAuth2 :> es => Token Refresh -> Eff es Authenticated
+ Web.Hyperbole.Effect.OAuth2: getConfigEnv :: Environment :> es => Eff es Config
+ Web.Hyperbole.Effect.OAuth2: instance Data.Aeson.Types.FromJSON.FromJSON Web.Hyperbole.Effect.OAuth2.Authenticated
+ Web.Hyperbole.Effect.OAuth2: instance Data.Aeson.Types.FromJSON.FromJSON Web.Hyperbole.Effect.OAuth2.Scopes
+ Web.Hyperbole.Effect.OAuth2: instance Data.Aeson.Types.FromJSON.FromJSON Web.Hyperbole.Effect.OAuth2.TokenType
+ Web.Hyperbole.Effect.OAuth2: instance Data.Aeson.Types.ToJSON.ToJSON Web.Hyperbole.Effect.OAuth2.Authenticated
+ Web.Hyperbole.Effect.OAuth2: instance Data.Aeson.Types.ToJSON.ToJSON Web.Hyperbole.Effect.OAuth2.Scopes
+ Web.Hyperbole.Effect.OAuth2: instance Data.Aeson.Types.ToJSON.ToJSON Web.Hyperbole.Effect.OAuth2.TokenType
+ Web.Hyperbole.Effect.OAuth2: instance Data.Default.Internal.Default Web.Hyperbole.Effect.OAuth2.AuthFlow
+ Web.Hyperbole.Effect.OAuth2: instance Data.String.IsString Web.Hyperbole.Effect.OAuth2.Scopes
+ Web.Hyperbole.Effect.OAuth2: instance GHC.Exception.Type.Exception Web.Hyperbole.Effect.OAuth2.OAuth2Error
+ Web.Hyperbole.Effect.OAuth2: instance GHC.Generics.Generic Web.Hyperbole.Effect.OAuth2.AuthFlow
+ Web.Hyperbole.Effect.OAuth2: instance GHC.Generics.Generic Web.Hyperbole.Effect.OAuth2.Authenticated
+ Web.Hyperbole.Effect.OAuth2: instance GHC.Generics.Generic Web.Hyperbole.Effect.OAuth2.Scopes
+ Web.Hyperbole.Effect.OAuth2: instance GHC.Generics.Generic Web.Hyperbole.Effect.OAuth2.TokenType
+ Web.Hyperbole.Effect.OAuth2: instance GHC.Read.Read Web.Hyperbole.Effect.OAuth2.TokenType
+ Web.Hyperbole.Effect.OAuth2: instance GHC.Show.Show Web.Hyperbole.Effect.OAuth2.Authenticated
+ Web.Hyperbole.Effect.OAuth2: instance GHC.Show.Show Web.Hyperbole.Effect.OAuth2.OAuth2Error
+ Web.Hyperbole.Effect.OAuth2: instance GHC.Show.Show Web.Hyperbole.Effect.OAuth2.Scopes
+ Web.Hyperbole.Effect.OAuth2: instance GHC.Show.Show Web.Hyperbole.Effect.OAuth2.TokenType
+ Web.Hyperbole.Effect.OAuth2: instance Web.Hyperbole.Data.Encoded.FromEncoded Web.Hyperbole.Effect.OAuth2.AuthFlow
+ Web.Hyperbole.Effect.OAuth2: instance Web.Hyperbole.Data.Encoded.FromEncoded Web.Hyperbole.Effect.OAuth2.Authenticated
+ Web.Hyperbole.Effect.OAuth2: instance Web.Hyperbole.Data.Encoded.ToEncoded Web.Hyperbole.Effect.OAuth2.AuthFlow
+ Web.Hyperbole.Effect.OAuth2: instance Web.Hyperbole.Data.Encoded.ToEncoded Web.Hyperbole.Effect.OAuth2.Authenticated
+ Web.Hyperbole.Effect.OAuth2: instance Web.Hyperbole.Data.Param.FromParam Web.Hyperbole.Effect.OAuth2.Authenticated
+ Web.Hyperbole.Effect.OAuth2: instance Web.Hyperbole.Data.Param.FromParam Web.Hyperbole.Effect.OAuth2.Scopes
+ Web.Hyperbole.Effect.OAuth2: instance Web.Hyperbole.Data.Param.FromParam Web.Hyperbole.Effect.OAuth2.TokenType
+ Web.Hyperbole.Effect.OAuth2: instance Web.Hyperbole.Data.Param.ToParam Web.Hyperbole.Effect.OAuth2.Authenticated
+ Web.Hyperbole.Effect.OAuth2: instance Web.Hyperbole.Data.Param.ToParam Web.Hyperbole.Effect.OAuth2.Scopes
+ Web.Hyperbole.Effect.OAuth2: instance Web.Hyperbole.Data.Param.ToParam Web.Hyperbole.Effect.OAuth2.TokenType
+ Web.Hyperbole.Effect.OAuth2: instance Web.Hyperbole.Effect.Session.Session Web.Hyperbole.Effect.OAuth2.AuthFlow
+ Web.Hyperbole.Effect.OAuth2: instance Web.Hyperbole.Effect.Session.Session Web.Hyperbole.Effect.OAuth2.Authenticated
+ Web.Hyperbole.Effect.OAuth2: newtype Scopes
+ Web.Hyperbole.Effect.OAuth2: newtype Token a
+ Web.Hyperbole.Effect.OAuth2: runOAuth2 :: (GenRandom :> es, IOE :> es, Hyperbole :> es) => Config -> Manager -> Eff (OAuth2 : es) a -> Eff es a
+ Web.Hyperbole.Effect.OAuth2: validateCode :: OAuth2 :> es => Eff es (Token Code)
+ Web.Hyperbole.Effect.Query: modifyQueryData :: Hyperbole :> es => (QueryData -> QueryData) -> Eff es ()
+ Web.Hyperbole.Effect.Response: hyperView :: HyperView id es => id -> View id () -> Eff es Response
+ Web.Hyperbole.Effect.Response: respondError :: Hyperbole :> es => ResponseError -> Eff es a
+ Web.Hyperbole.Effect.Response: respondErrorView :: Hyperbole :> es => Text -> View Body () -> Eff es a
+ Web.Hyperbole.Effect.Session: class GDatatypeName f
+ Web.Hyperbole.Effect.Session: class Session a
+ Web.Hyperbole.Effect.Session: cookiePath :: Session a => Maybe Path
+ Web.Hyperbole.Effect.Session: gDatatypeName :: GDatatypeName f => f p -> Text
+ Web.Hyperbole.Effect.Session: genericTypeName :: forall a. (Generic a, GDatatypeName (Rep a)) => Text
+ Web.Hyperbole.Effect.Session: instance forall k (d :: GHC.Generics.Meta) (f :: k -> *). GHC.Generics.Datatype d => Web.Hyperbole.Effect.Session.GDatatypeName (GHC.Generics.M1 GHC.Generics.D d f)
+ Web.Hyperbole.Effect.Session: parseCookie :: (Session a, FromEncoded a) => CookieValue -> Either String a
+ Web.Hyperbole.Effect.Session: sessionCookie :: forall a. Session a => a -> Cookie
+ Web.Hyperbole.Effect.Session: sessionKey :: (Session a, Generic a, GDatatypeName (Rep a)) => Key
+ Web.Hyperbole.Effect.Session: toCookie :: (Session a, ToEncoded a) => a -> CookieValue
+ Web.Hyperbole.HyperView.Event: Alt :: Key
+ Web.Hyperbole.HyperView.Event: ArrowDown :: Key
+ Web.Hyperbole.HyperView.Event: ArrowLeft :: Key
+ Web.Hyperbole.HyperView.Event: ArrowRight :: Key
+ Web.Hyperbole.HyperView.Event: ArrowUp :: Key
+ Web.Hyperbole.HyperView.Event: CapsLock :: Key
+ Web.Hyperbole.HyperView.Event: Control :: Key
+ Web.Hyperbole.HyperView.Event: Enter :: Key
+ Web.Hyperbole.HyperView.Event: Escape :: Key
+ Web.Hyperbole.HyperView.Event: Fn :: Key
+ Web.Hyperbole.HyperView.Event: Meta :: Key
+ Web.Hyperbole.HyperView.Event: OtherKey :: Text -> Key
+ Web.Hyperbole.HyperView.Event: Shift :: Key
+ Web.Hyperbole.HyperView.Event: Space :: Key
+ Web.Hyperbole.HyperView.Event: data Key
+ Web.Hyperbole.HyperView.Event: dataTarget :: (ViewId id, ViewContext a ~ id, Attributable a) => id -> Attributes a -> Attributes a
+ Web.Hyperbole.HyperView.Event: event :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Name -> Action id -> Attributes a -> Attributes a
+ Web.Hyperbole.HyperView.Event: instance GHC.Read.Read Web.Hyperbole.HyperView.Event.Key
+ Web.Hyperbole.HyperView.Event: instance GHC.Show.Show Web.Hyperbole.HyperView.Event.Key
+ Web.Hyperbole.HyperView.Event: keyDataAttribute :: Key -> Text
+ Web.Hyperbole.HyperView.Event: onChange :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => (value -> Action id) -> Attributes a -> Attributes a
+ Web.Hyperbole.HyperView.Event: onClick :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a
+ Web.Hyperbole.HyperView.Event: onDblClick :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a
+ Web.Hyperbole.HyperView.Event: onInput :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => (Text -> Action id) -> DelayMs -> Attributes a -> Attributes a
+ Web.Hyperbole.HyperView.Event: onKeyDown :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Key -> Action id -> Attributes a -> Attributes a
+ Web.Hyperbole.HyperView.Event: onKeyUp :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Key -> Action id -> Attributes a -> Attributes a
+ Web.Hyperbole.HyperView.Event: onLoad :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> DelayMs -> Attributes a -> Attributes a
+ Web.Hyperbole.HyperView.Event: onMouseEnter :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a
+ Web.Hyperbole.HyperView.Event: onMouseLeave :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a
+ Web.Hyperbole.HyperView.Event: onSubmit :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a
+ Web.Hyperbole.HyperView.Event: target :: forall id ctx. (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx ()
+ Web.Hyperbole.HyperView.Event: toActionInput :: ViewAction a => (val -> a) -> Encoded
+ Web.Hyperbole.HyperView.Event: type DelayMs = Int
+ Web.Hyperbole.HyperView.Forms: Country :: InputType
+ Web.Hyperbole.HyperView.Forms: CountryName :: InputType
+ Web.Hyperbole.HyperView.Forms: CurrentPassword :: InputType
+ Web.Hyperbole.HyperView.Forms: Email :: InputType
+ Web.Hyperbole.HyperView.Forms: FieldName :: Text -> FieldName a
+ Web.Hyperbole.HyperView.Forms: Form :: HashMap Text [Text] -> Form
+ Web.Hyperbole.HyperView.Forms: FormFields :: id -> FormFields id
+ Web.Hyperbole.HyperView.Forms: FormOptions :: (String -> String) -> FormOptions
+ Web.Hyperbole.HyperView.Forms: Input :: FieldName a -> Input (id :: Type) (a :: Type)
+ Web.Hyperbole.HyperView.Forms: Invalid :: Text -> Validated a
+ Web.Hyperbole.HyperView.Forms: Name :: InputType
+ Web.Hyperbole.HyperView.Forms: NewPassword :: InputType
+ Web.Hyperbole.HyperView.Forms: NotInvalid :: Validated a
+ Web.Hyperbole.HyperView.Forms: Number :: InputType
+ Web.Hyperbole.HyperView.Forms: OneTimeCode :: InputType
+ Web.Hyperbole.HyperView.Forms: Organization :: InputType
+ Web.Hyperbole.HyperView.Forms: PostalCode :: InputType
+ Web.Hyperbole.HyperView.Forms: Search :: InputType
+ Web.Hyperbole.HyperView.Forms: Selection :: Input id a -> b -> Radio (id :: Type) (a :: Type) (b :: Type)
+ Web.Hyperbole.HyperView.Forms: StreetAddress :: InputType
+ Web.Hyperbole.HyperView.Forms: TextInput :: InputType
+ Web.Hyperbole.HyperView.Forms: Username :: InputType
+ Web.Hyperbole.HyperView.Forms: Valid :: Validated a
+ Web.Hyperbole.HyperView.Forms: [$sel:defaultOption:Selection] :: Radio (id :: Type) (a :: Type) (b :: Type) -> b
+ Web.Hyperbole.HyperView.Forms: [$sel:inputCtx:Selection] :: Radio (id :: Type) (a :: Type) (b :: Type) -> Input id a
+ Web.Hyperbole.HyperView.Forms: [$sel:inputName:Input] :: Input (id :: Type) (a :: Type) -> FieldName a
+ Web.Hyperbole.HyperView.Forms: [fieldLabelModifier] :: FormOptions -> String -> String
+ Web.Hyperbole.HyperView.Forms: [unForm] :: Form -> HashMap Text [Text]
+ Web.Hyperbole.HyperView.Forms: checkbox :: Bool -> View (Input id a) ()
+ Web.Hyperbole.HyperView.Forms: class FromForm (form :: Type)
+ Web.Hyperbole.HyperView.Forms: class FromFormF (f :: (Type -> Type) -> Type)
+ Web.Hyperbole.HyperView.Forms: class () => FromFormKey k
+ Web.Hyperbole.HyperView.Forms: class GFieldsGen f
+ Web.Hyperbole.HyperView.Forms: class GenField a
+ Web.Hyperbole.HyperView.Forms: class GenFields f (form :: (Type -> Type) -> Type)
+ Web.Hyperbole.HyperView.Forms: class () => Generic a
+ Web.Hyperbole.HyperView.Forms: data FormFields id
+ Web.Hyperbole.HyperView.Forms: data () => FormOptions
+ Web.Hyperbole.HyperView.Forms: data () => Identity a
+ Web.Hyperbole.HyperView.Forms: data Input (id :: Type) (a :: Type)
+ Web.Hyperbole.HyperView.Forms: data InputType
+ Web.Hyperbole.HyperView.Forms: data Radio (id :: Type) (a :: Type) (b :: Type)
+ Web.Hyperbole.HyperView.Forms: data Validated a
+ Web.Hyperbole.HyperView.Forms: field :: forall (id :: Type) (a :: Type). FieldName a -> View (Input id a) () -> View (FormFields id) ()
+ Web.Hyperbole.HyperView.Forms: fieldNames :: forall form. GenFields FieldName form => form FieldName
+ Web.Hyperbole.HyperView.Forms: form :: ViewAction (Action id) => Action id -> View (FormFields id) () -> View id ()
+ Web.Hyperbole.HyperView.Forms: formData :: forall form es. (FromForm form, Hyperbole :> es) => Eff es form
+ Web.Hyperbole.HyperView.Forms: fromForm :: (FromForm form, Generic form, GFormParse (Rep form)) => Form -> Either String form
+ Web.Hyperbole.HyperView.Forms: fromFormF :: (FromFormF f, Generic (f Identity), GFormParse (Rep (f Identity))) => Form -> Either String (f Identity)
+ Web.Hyperbole.HyperView.Forms: gFieldsGen :: GFieldsGen f => f p
+ Web.Hyperbole.HyperView.Forms: genField :: GenField a => String -> a
+ Web.Hyperbole.HyperView.Forms: genFields :: (GenFields f form, Generic (form f), GFieldsGen (Rep (form f))) => form f
+ Web.Hyperbole.HyperView.Forms: input :: InputType -> View (Input id a) ()
+ Web.Hyperbole.HyperView.Forms: instance (GHC.Generics.Selector s, Web.Hyperbole.Data.Param.FromParam a) => Web.Hyperbole.HyperView.Forms.GFormParse (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a))
+ Web.Hyperbole.HyperView.Forms: instance (GHC.Generics.Selector s, Web.Hyperbole.HyperView.Forms.GenField a) => Web.Hyperbole.HyperView.Forms.GFieldsGen (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a))
+ Web.Hyperbole.HyperView.Forms: instance GHC.Show.Show Web.Hyperbole.HyperView.Forms.InputType
+ Web.Hyperbole.HyperView.Forms: instance Web.Hyperbole.HyperView.Forms.FromFormF form => Web.Hyperbole.HyperView.Forms.FromForm (form Data.Functor.Identity.Identity)
+ Web.Hyperbole.HyperView.Forms: instance Web.Hyperbole.HyperView.Forms.GFieldsGen GHC.Generics.U1
+ Web.Hyperbole.HyperView.Forms: instance Web.Hyperbole.HyperView.Forms.GenField (GHC.Maybe.Maybe a)
+ Web.Hyperbole.HyperView.Forms: instance forall k (a :: k). Data.String.IsString (Web.Hyperbole.HyperView.Forms.FieldName a)
+ Web.Hyperbole.HyperView.Forms: instance forall k (a :: k). GHC.Base.Monoid (Web.Hyperbole.HyperView.Forms.Validated a)
+ Web.Hyperbole.HyperView.Forms: instance forall k (a :: k). GHC.Base.Semigroup (Web.Hyperbole.HyperView.Forms.Validated a)
+ Web.Hyperbole.HyperView.Forms: instance forall k (a :: k). GHC.Show.Show (Web.Hyperbole.HyperView.Forms.FieldName a)
+ Web.Hyperbole.HyperView.Forms: instance forall k (a :: k). GHC.Show.Show (Web.Hyperbole.HyperView.Forms.Validated a)
+ Web.Hyperbole.HyperView.Forms: instance forall k (a :: k). Web.Hyperbole.HyperView.Forms.GenField (Web.Hyperbole.HyperView.Forms.FieldName a)
+ Web.Hyperbole.HyperView.Forms: instance forall k (a :: k). Web.Hyperbole.HyperView.Forms.GenField (Web.Hyperbole.HyperView.Forms.Validated a)
+ Web.Hyperbole.HyperView.Forms: instance forall k (f :: k -> *) (c :: GHC.Generics.Meta). Web.Hyperbole.HyperView.Forms.GFieldsGen f => Web.Hyperbole.HyperView.Forms.GFieldsGen (GHC.Generics.M1 GHC.Generics.C c f)
+ Web.Hyperbole.HyperView.Forms: instance forall k (f :: k -> *) (c :: GHC.Generics.Meta). Web.Hyperbole.HyperView.Forms.GFormParse f => Web.Hyperbole.HyperView.Forms.GFormParse (GHC.Generics.M1 GHC.Generics.C c f)
+ Web.Hyperbole.HyperView.Forms: instance forall k (f :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.HyperView.Forms.GFieldsGen f => Web.Hyperbole.HyperView.Forms.GFieldsGen (GHC.Generics.M1 GHC.Generics.D d f)
+ Web.Hyperbole.HyperView.Forms: instance forall k (f :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.HyperView.Forms.GFormParse f => Web.Hyperbole.HyperView.Forms.GFormParse (GHC.Generics.M1 GHC.Generics.D d f)
+ Web.Hyperbole.HyperView.Forms: instance forall k (f :: k -> *) (g :: k -> *). (Web.Hyperbole.HyperView.Forms.GFieldsGen f, Web.Hyperbole.HyperView.Forms.GFieldsGen g) => Web.Hyperbole.HyperView.Forms.GFieldsGen (f GHC.Generics.:*: g)
+ Web.Hyperbole.HyperView.Forms: instance forall k (f :: k -> *) (g :: k -> *). (Web.Hyperbole.HyperView.Forms.GFormParse f, Web.Hyperbole.HyperView.Forms.GFormParse g) => Web.Hyperbole.HyperView.Forms.GFormParse (f GHC.Generics.:*: g)
+ Web.Hyperbole.HyperView.Forms: invalidText :: forall a id. Validated a -> View (Input id a) ()
+ Web.Hyperbole.HyperView.Forms: isInvalid :: Validated a -> Bool
+ Web.Hyperbole.HyperView.Forms: label :: View c () -> View c ()
+ Web.Hyperbole.HyperView.Forms: newtype FieldName a
+ Web.Hyperbole.HyperView.Forms: newtype () => Form
+ Web.Hyperbole.HyperView.Forms: radio :: (Eq b, ToParam b) => b -> View (Radio id a b) ()
+ Web.Hyperbole.HyperView.Forms: radioGroup :: b -> View (Radio id a b) () -> View (Input id a) ()
+ Web.Hyperbole.HyperView.Forms: select :: Eq opt => opt -> View (Option opt id) () -> View (Input id a) ()
+ Web.Hyperbole.HyperView.Forms: submit :: View (FormFields id) () -> View (FormFields id) ()
+ Web.Hyperbole.HyperView.Forms: textarea :: Maybe Text -> View (Input id a) ()
+ Web.Hyperbole.HyperView.Forms: type family Field (context :: Type -> Type) a
+ Web.Hyperbole.HyperView.Forms: validate :: Bool -> Text -> Validated a
+ Web.Hyperbole.HyperView.Handled: hyper :: forall id ctx. (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx ()
+ Web.Hyperbole.HyperView.Handled: hyperUnsafe :: ViewId id => id -> View id () -> View ctx ()
+ Web.Hyperbole.HyperView.Handled: type HyperViewHandled id ctx = (ElemOr id (ctx : Require ctx) (NotHandled id ctx (Require ctx)), CheckDescendents id ctx)
+ Web.Hyperbole.HyperView.Handled: type NotDesc id ctx x cs = TypeError ('Text "" :<>: 'ShowType x :<>: 'Text ", a child of HyperView " :<>: 'ShowType id :<>: 'Text ", not handled by context " :<>: 'ShowType ctx :$$: ('Text " Require = " ':<>: 'ShowType cs))
+ Web.Hyperbole.HyperView.Handled: type NotHandled id ctx (views :: [Type]) = TypeError ('Text "HyperView " :<>: 'ShowType id :<>: 'Text " not found in (Require " :<>: 'ShowType ctx :<>: 'Text ")" :$$: 'Text " " :<>: 'ShowType views :$$: 'Text "Try adding it to the HyperView instance:" :$$: 'Text " instance HyperView " :<>: 'ShowType ctx :<>: 'Text " where" :$$: 'Text " type Action " :<>: 'ShowType ctx :<>: 'Text " = " :<>: ShowType (Action id) :<>: 'Text "" :$$: 'Text " type Require " :<>: 'ShowType ctx :<>: 'Text " = [" :<>: ShowType id :<>: 'Text ", ...]")
+ Web.Hyperbole.HyperView.Handled: type NotInPage x total = TypeError ('Text "" :<>: 'ShowType x :<>: 'Text " not included in: " :$$: 'Text " Page es " :<>: ShowType total :$$: 'Text "try expanding the page views to:" :$$: 'Text " Page es " :<>: ShowType (x : total))
+ Web.Hyperbole.HyperView.Handled: type family AllInPage ids total :: Constraint
+ Web.Hyperbole.HyperView.Input: Option :: opt -> Option opt id
+ Web.Hyperbole.HyperView.Input: [$sel:defaultOption:Option] :: Option opt id -> opt
+ Web.Hyperbole.HyperView.Input: button :: ViewAction (Action id) => Action id -> View id () -> View id ()
+ Web.Hyperbole.HyperView.Input: checked :: Attributable a => Bool -> Attributes a -> Attributes a
+ Web.Hyperbole.HyperView.Input: data Option opt id
+ Web.Hyperbole.HyperView.Input: dropdown :: ViewAction (Action id) => (opt -> Action id) -> opt -> View (Option opt id) () -> View id ()
+ Web.Hyperbole.HyperView.Input: option :: (ViewAction (Action id), Eq opt, ToParam opt) => opt -> Text -> View (Option opt id) ()
+ Web.Hyperbole.HyperView.Input: route :: Route a => a -> View c () -> View c ()
+ Web.Hyperbole.HyperView.Input: search :: ViewAction (Action id) => (Text -> Action id) -> DelayMs -> View id ()
+ Web.Hyperbole.HyperView.Input: selected :: Attributable h => Bool -> Attributes h -> Attributes h
+ Web.Hyperbole.HyperView.Types: --
+ Web.Hyperbole.HyperView.Types: -- </pre>
+ Web.Hyperbole.HyperView.Types: -- <pre>
+ Web.Hyperbole.HyperView.Types: -- data Action Message = SetMessage Text | ClearMessage
+ Web.Hyperbole.HyperView.Types: -- deriving (Generic, ViewAction)
+ Web.Hyperbole.HyperView.Types: -- the page knows how to handle them
+ Web.Hyperbole.HyperView.Types: -- type Require = '[ChildView]
+ Web.Hyperbole.HyperView.Types: -- | Include any child hyperviews here. The compiler will make sure that
+ Web.Hyperbole.HyperView.Types: Root :: Root (views :: [Type])
+ Web.Hyperbole.HyperView.Types: class (ViewId id, ViewAction (Action id)) => HyperView id es where {
+ Web.Hyperbole.HyperView.Types: data Action id;
+ Web.Hyperbole.HyperView.Types: data Root (views :: [Type])
+ Web.Hyperbole.HyperView.Types: instance GHC.Generics.Generic (Web.Hyperbole.HyperView.Types.Action (Web.Hyperbole.HyperView.Types.Root views))
+ Web.Hyperbole.HyperView.Types: instance GHC.Generics.Generic (Web.Hyperbole.HyperView.Types.Root views)
+ Web.Hyperbole.HyperView.Types: instance Web.Hyperbole.HyperView.Types.HyperView (Web.Hyperbole.HyperView.Types.Root views) es
+ Web.Hyperbole.HyperView.Types: instance Web.Hyperbole.HyperView.ViewAction.ViewAction (Web.Hyperbole.HyperView.Types.Action (Web.Hyperbole.HyperView.Types.Root views))
+ Web.Hyperbole.HyperView.Types: instance Web.Hyperbole.HyperView.ViewId.ViewId (Web.Hyperbole.HyperView.Types.Root views)
+ Web.Hyperbole.HyperView.Types: type Require id = '[];
+ Web.Hyperbole.HyperView.Types: update :: (HyperView id es, Hyperbole :> es) => Action id -> Eff (Reader id : es) (View id ())
+ Web.Hyperbole.HyperView.Types: }
+ Web.Hyperbole.HyperView.ViewAction: class ViewAction a
+ Web.Hyperbole.HyperView.ViewAction: decodeAction :: ViewAction act => Text -> Maybe act
+ Web.Hyperbole.HyperView.ViewAction: encodeAction :: ViewAction act => act -> Text
+ Web.Hyperbole.HyperView.ViewAction: instance Web.Hyperbole.HyperView.ViewAction.ViewAction ()
+ Web.Hyperbole.HyperView.ViewAction: parseAction :: (ViewAction a, Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a
+ Web.Hyperbole.HyperView.ViewAction: toAction :: (ViewAction a, Generic a, GToEncoded (Rep a)) => a -> Encoded
+ Web.Hyperbole.HyperView.ViewId: class HasViewId m view
+ Web.Hyperbole.HyperView.ViewId: class ViewId a
+ Web.Hyperbole.HyperView.ViewId: decodeViewId :: ViewId id => Text -> Maybe id
+ Web.Hyperbole.HyperView.ViewId: encodeViewId :: ViewId id => id -> Text
+ Web.Hyperbole.HyperView.ViewId: instance Web.Hyperbole.HyperView.ViewId.HasViewId (Effectful.Internal.Monad.Eff (Effectful.Internal.MTL.Reader view : es)) view
+ Web.Hyperbole.HyperView.ViewId: instance Web.Hyperbole.HyperView.ViewId.HasViewId (Web.Hyperbole.View.Types.View ctx) ctx
+ Web.Hyperbole.HyperView.ViewId: parseViewId :: (ViewId a, Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a
+ Web.Hyperbole.HyperView.ViewId: toViewId :: (ViewId a, Generic a, GToEncoded (Rep a)) => a -> Encoded
+ Web.Hyperbole.HyperView.ViewId: viewId :: HasViewId m view => m view
+ Web.Hyperbole.Page: subPage :: Hyperbole :> es => Eff (Reader (Root inner) : es) a -> Eff es a
+ Web.Hyperbole.Route: genMatchRoute :: (Generic a, GenRoute (Rep a)) => [Segment] -> Maybe a
+ Web.Hyperbole.Route: genRoutePath :: (Generic a, GenRoute (Rep a)) => a -> [Segment]
+ Web.Hyperbole.Route: matchRouteRead :: Read a => Path -> Maybe a
+ Web.Hyperbole.Route: routePathShow :: Show a => a -> Path
+ Web.Hyperbole.Route: routeUri :: Route a => a -> URI
+ Web.Hyperbole.Server.Handler: class RunHandlers (views :: [Type]) es
+ Web.Hyperbole.Server.Handler: decodeEvent :: forall id es. HyperView id es => Event TargetViewId Encoded -> Eff es (Maybe (Event id (Action id)))
+ Web.Hyperbole.Server.Handler: instance (Web.Hyperbole.HyperView.Types.HyperView view es, Web.Hyperbole.Server.Handler.RunHandlers views es) => Web.Hyperbole.Server.Handler.RunHandlers (view : views) es
+ Web.Hyperbole.Server.Handler: instance Web.Hyperbole.Server.Handler.RunHandlers '[] es
+ Web.Hyperbole.Server.Handler: loadPageResponse :: Eff es (View (Root total) ()) -> Eff es Response
+ Web.Hyperbole.Server.Handler: runHandler :: forall id es. (HyperView id es, Hyperbole :> es) => Event TargetViewId Encoded -> (Action id -> Eff (Reader id : es) (View id ())) -> Eff es (Maybe Response)
+ Web.Hyperbole.Server.Handler: runHandlers :: (RunHandlers views es, Hyperbole :> es) => Event TargetViewId Encoded -> Eff es (Maybe Response)
+ Web.Hyperbole.Server.Handler: runLoad :: forall views es. (Hyperbole :> es, RunHandlers views es) => Eff es (View (Root views) ()) -> Eff es Response
+ Web.Hyperbole.Server.Message: ContentHtml :: ContentType
+ Web.Hyperbole.Server.Message: ContentText :: ContentType
+ Web.Hyperbole.Server.Message: InvalidCookie :: ByteString -> String -> MessageError
+ Web.Hyperbole.Server.Message: InvalidMessage :: String -> Text -> MessageError
+ Web.Hyperbole.Server.Message: Message :: Text -> Event TargetViewId Encoded -> RequestId -> Metadata -> MessageBody -> Message
+ Web.Hyperbole.Server.Message: MessageBody :: ByteString -> MessageBody
+ Web.Hyperbole.Server.Message: Metadata :: [(Text, Text)] -> Metadata
+ Web.Hyperbole.Server.Message: MissingMeta :: String -> MessageError
+ Web.Hyperbole.Server.Message: RenderedHtml :: ByteString -> RenderedHtml
+ Web.Hyperbole.Server.Message: [$sel:body:Message] :: Message -> MessageBody
+ Web.Hyperbole.Server.Message: [$sel:event:Message] :: Message -> Event TargetViewId Encoded
+ Web.Hyperbole.Server.Message: [$sel:messageType:Message] :: Message -> Text
+ Web.Hyperbole.Server.Message: [$sel:metadata:Message] :: Message -> Metadata
+ Web.Hyperbole.Server.Message: [$sel:requestId:Message] :: Message -> RequestId
+ Web.Hyperbole.Server.Message: [$sel:value:MessageBody] :: MessageBody -> ByteString
+ Web.Hyperbole.Server.Message: clientMetadata :: Path -> Client -> Metadata
+ Web.Hyperbole.Server.Message: data ContentType
+ Web.Hyperbole.Server.Message: data Message
+ Web.Hyperbole.Server.Message: data MessageError
+ Web.Hyperbole.Server.Message: instance GHC.Base.Monoid Web.Hyperbole.Server.Message.Metadata
+ Web.Hyperbole.Server.Message: instance GHC.Base.Monoid Web.Hyperbole.Server.Message.RenderedHtml
+ Web.Hyperbole.Server.Message: instance GHC.Base.Semigroup Web.Hyperbole.Server.Message.Metadata
+ Web.Hyperbole.Server.Message: instance GHC.Base.Semigroup Web.Hyperbole.Server.Message.RenderedHtml
+ Web.Hyperbole.Server.Message: instance GHC.Exception.Type.Exception Web.Hyperbole.Server.Message.MessageError
+ Web.Hyperbole.Server.Message: instance GHC.Show.Show Web.Hyperbole.Server.Message.Message
+ Web.Hyperbole.Server.Message: instance GHC.Show.Show Web.Hyperbole.Server.Message.MessageBody
+ Web.Hyperbole.Server.Message: instance GHC.Show.Show Web.Hyperbole.Server.Message.MessageError
+ Web.Hyperbole.Server.Message: instance GHC.Show.Show Web.Hyperbole.Server.Message.Metadata
+ Web.Hyperbole.Server.Message: lookupMetadata :: MetaKey -> Metadata -> Maybe Text
+ Web.Hyperbole.Server.Message: metaError :: Text -> Metadata
+ Web.Hyperbole.Server.Message: metaLine :: MetaKey -> Text -> Text
+ Web.Hyperbole.Server.Message: metaRedirect :: URI -> Metadata
+ Web.Hyperbole.Server.Message: metaRemotes :: [Remote] -> Metadata
+ Web.Hyperbole.Server.Message: metadata :: MetaKey -> Text -> Metadata
+ Web.Hyperbole.Server.Message: mimeType :: Text
+ Web.Hyperbole.Server.Message: newtype MessageBody
+ Web.Hyperbole.Server.Message: newtype Metadata
+ Web.Hyperbole.Server.Message: newtype RenderedHtml
+ Web.Hyperbole.Server.Message: parseActionMessage :: Text -> Either String Message
+ Web.Hyperbole.Server.Message: renderMetadata :: Metadata -> Text
+ Web.Hyperbole.Server.Message: requestMetadata :: Request -> Metadata
+ Web.Hyperbole.Server.Message: responseMetadata :: Path -> Client -> [Remote] -> Metadata
+ Web.Hyperbole.Server.Message: type MetaKey = Text
+ Web.Hyperbole.Server.Options: ServerOptions :: (ByteString -> ByteString) -> (ResponseError -> ServerError) -> ServerOptions
+ Web.Hyperbole.Server.Options: [$sel:serverError:ServerOptions] :: ServerOptions -> ResponseError -> ServerError
+ Web.Hyperbole.Server.Options: [$sel:toDocument:ServerOptions] :: ServerOptions -> ByteString -> ByteString
+ Web.Hyperbole.Server.Options: data ServerOptions
+ Web.Hyperbole.Server.Options: defaultError :: ResponseError -> ServerError
+ Web.Hyperbole.Server.Options: defaultErrorBody :: Text -> View Body ()
+ Web.Hyperbole.Server.Options: defaultErrorMessage :: ResponseError -> Text
+ Web.Hyperbole.Server.Socket: SocketRequest :: Maybe Request -> SocketRequest
+ Web.Hyperbole.Server.Socket: [$sel:request:SocketRequest] :: SocketRequest -> Maybe Request
+ Web.Hyperbole.Server.Socket: data SocketRequest
+ Web.Hyperbole.Server.Socket: handleRequestSocket :: (IOE :> es, Concurrent :> es) => ServerOptions -> Request -> Connection -> Eff (Hyperbole : es) Response -> Eff es ()
+ Web.Hyperbole.Server.Socket: sendError :: IOE :> es => Connection -> Metadata -> ServerError -> Eff es ()
+ Web.Hyperbole.Server.Socket: sendMessage :: MonadIO m => Connection -> Metadata -> RenderedHtml -> m ()
+ Web.Hyperbole.Server.Socket: sendRedirect :: IOE :> es => Connection -> Metadata -> URI -> Eff es ()
+ Web.Hyperbole.Server.Socket: sendUpdateView :: IOE :> es => Connection -> Metadata -> View Body () -> Eff es ()
+ Web.Hyperbole.Server.Wai: contentType :: ContentType -> (HeaderName, ByteString)
+ Web.Hyperbole.Server.Wai: fromCookieHeader :: ByteString -> Either MessageError Cookies
+ Web.Hyperbole.Server.Wai: fromWaiRequest :: Request -> ByteString -> Either MessageError Request
+ Web.Hyperbole.Server.Wai: handleRequestWai :: IOE :> es => ServerOptions -> Request -> (Response -> IO ResponseReceived) -> Eff (Hyperbole : es) Response -> Eff es ResponseReceived
+ Web.Hyperbole.Server.Wai: messageFromBody :: ByteString -> Either MessageError Message
+ Web.Hyperbole.Server.Wai: scriptMeta :: Metadata -> View Metadata ()
+ Web.Hyperbole.Server.Wai: sendResponse :: ServerOptions -> Request -> Client -> Response -> [Remote] -> (Response -> IO ResponseReceived) -> IO ResponseReceived
+ Web.Hyperbole.Types.Client: Client :: RequestId -> Cookies -> Maybe QueryData -> Maybe Text -> Client
+ Web.Hyperbole.Types.Client: [$sel:pageTitle:Client] :: Client -> Maybe Text
+ Web.Hyperbole.Types.Client: [$sel:query:Client] :: Client -> Maybe QueryData
+ Web.Hyperbole.Types.Client: [$sel:requestId:Client] :: Client -> RequestId
+ Web.Hyperbole.Types.Client: [$sel:session:Client] :: Client -> Cookies
+ Web.Hyperbole.Types.Client: clientModCookies :: (Cookies -> Cookies) -> Client -> Client
+ Web.Hyperbole.Types.Client: clientSetPageTitle :: Text -> Client -> Client
+ Web.Hyperbole.Types.Client: clientSetQuery :: QueryData -> Client -> Client
+ Web.Hyperbole.Types.Client: data Client
+ Web.Hyperbole.Types.Event: Event :: id -> act -> Event id act
+ Web.Hyperbole.Types.Event: TargetViewId :: Text -> TargetViewId
+ Web.Hyperbole.Types.Event: [$sel:action:Event] :: Event id act -> act
+ Web.Hyperbole.Types.Event: [$sel:text:TargetViewId] :: TargetViewId -> Text
+ Web.Hyperbole.Types.Event: [$sel:viewId:Event] :: Event id act -> id
+ Web.Hyperbole.Types.Event: data Event id act
+ Web.Hyperbole.Types.Event: instance (GHC.Show.Show act, GHC.Show.Show id) => GHC.Show.Show (Web.Hyperbole.Types.Event.Event id act)
+ Web.Hyperbole.Types.Event: instance Data.Aeson.Types.ToJSON.ToJSON Web.Hyperbole.Types.Event.TargetViewId
+ Web.Hyperbole.Types.Event: instance GHC.Show.Show Web.Hyperbole.Types.Event.TargetViewId
+ Web.Hyperbole.Types.Event: newtype TargetViewId
+ Web.Hyperbole.Types.Request: Host :: ByteString -> Host
+ Web.Hyperbole.Types.Request: Request :: Host -> Path -> Query -> ByteString -> Method -> Cookies -> Maybe (Event TargetViewId Encoded) -> RequestId -> Request
+ Web.Hyperbole.Types.Request: RequestId :: Text -> RequestId
+ Web.Hyperbole.Types.Request: [$sel:body:Request] :: Request -> ByteString
+ Web.Hyperbole.Types.Request: [$sel:cookies:Request] :: Request -> Cookies
+ Web.Hyperbole.Types.Request: [$sel:event:Request] :: Request -> Maybe (Event TargetViewId Encoded)
+ Web.Hyperbole.Types.Request: [$sel:host:Request] :: Request -> Host
+ Web.Hyperbole.Types.Request: [$sel:method:Request] :: Request -> Method
+ Web.Hyperbole.Types.Request: [$sel:path:Request] :: Request -> Path
+ Web.Hyperbole.Types.Request: [$sel:query:Request] :: Request -> Query
+ Web.Hyperbole.Types.Request: [$sel:requestId:Request] :: Request -> RequestId
+ Web.Hyperbole.Types.Request: [$sel:text:Host] :: Host -> ByteString
+ Web.Hyperbole.Types.Request: data Request
+ Web.Hyperbole.Types.Request: instance GHC.Show.Show Web.Hyperbole.Types.Request.Host
+ Web.Hyperbole.Types.Request: instance GHC.Show.Show Web.Hyperbole.Types.Request.Request
+ Web.Hyperbole.Types.Request: instance GHC.Show.Show Web.Hyperbole.Types.Request.RequestId
+ Web.Hyperbole.Types.Request: newtype Host
+ Web.Hyperbole.Types.Request: newtype RequestId
+ Web.Hyperbole.Types.Response: Body :: Body
+ Web.Hyperbole.Types.Response: Err :: ResponseError -> Response
+ Web.Hyperbole.Types.Response: ErrAuth :: Text -> ResponseError
+ Web.Hyperbole.Types.Response: ErrCustom :: ServerError -> ResponseError
+ Web.Hyperbole.Types.Response: ErrInternal :: ResponseError
+ Web.Hyperbole.Types.Response: ErrNotHandled :: Event TargetViewId Encoded -> ResponseError
+ Web.Hyperbole.Types.Response: ErrParse :: String -> ResponseError
+ Web.Hyperbole.Types.Response: ErrQuery :: String -> ResponseError
+ Web.Hyperbole.Types.Response: ErrServer :: Text -> ResponseError
+ Web.Hyperbole.Types.Response: ErrSession :: Text -> String -> ResponseError
+ Web.Hyperbole.Types.Response: NotFound :: ResponseError
+ Web.Hyperbole.Types.Response: Redirect :: URI -> Response
+ Web.Hyperbole.Types.Response: Response :: TargetViewId -> View Body () -> Response
+ Web.Hyperbole.Types.Response: ServerError :: Text -> View Body () -> ServerError
+ Web.Hyperbole.Types.Response: [$sel:body:ServerError] :: ServerError -> View Body ()
+ Web.Hyperbole.Types.Response: [$sel:message:ServerError] :: ServerError -> Text
+ Web.Hyperbole.Types.Response: data Body
+ Web.Hyperbole.Types.Response: data Response
+ Web.Hyperbole.Types.Response: data ResponseError
+ Web.Hyperbole.Types.Response: data ServerError
+ Web.Hyperbole.Types.Response: instance Data.String.IsString Web.Hyperbole.Types.Response.ResponseError
+ Web.Hyperbole.Types.Response: instance GHC.Show.Show Web.Hyperbole.Types.Response.ResponseError
+ Web.Hyperbole.View: ListItem :: View c a -> ListItem c a
+ Web.Hyperbole.View: Table :: View c a -> Table c a
+ Web.Hyperbole.View: TableColumn :: TableHead c () -> (dt -> View c ()) -> TableColumn c dt
+ Web.Hyperbole.View: TableColumns :: Eff '[State [TableColumn c dt]] a -> TableColumns c dt a
+ Web.Hyperbole.View: TableHead :: View id a -> TableHead id a
+ Web.Hyperbole.View: View :: Eff '[Reader c] (Html a) -> View c a
+ Web.Hyperbole.View: [$sel:dataCell:TableColumn] :: TableColumn c dt -> dt -> View c ()
+ Web.Hyperbole.View: [$sel:headCell:TableColumn] :: TableColumn c dt -> TableHead c ()
+ Web.Hyperbole.View: [$sel:html:View] :: View c a -> Eff '[Reader c] (Html a)
+ Web.Hyperbole.View: autofocus :: Attributable h => Attributes h -> Attributes h
+ Web.Hyperbole.View: charset :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View: content :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View: httpEquiv :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View: img :: Text -> View c ()
+ Web.Hyperbole.View: meta :: View c ()
+ Web.Hyperbole.View: modifyContext :: forall ctx0 ctx1. (ctx0 -> ctx1) -> View ctx1 () -> View ctx0 ()
+ Web.Hyperbole.View: newtype ListItem c a
+ Web.Hyperbole.View: newtype Table c a
+ Web.Hyperbole.View: newtype TableColumns c dt a
+ Web.Hyperbole.View: newtype TableHead id a
+ Web.Hyperbole.View: newtype View c a
+ Web.Hyperbole.View: placeholder :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View: script' :: ByteString -> View c ()
+ Web.Hyperbole.View: src :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View: title :: Text -> View c ()
+ Web.Hyperbole.View: type_ :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View: usersTable :: View c ()
+ Web.Hyperbole.View.CSS: disabled :: Styleable h => CSS h -> CSS h
+ Web.Hyperbole.View.CSS: loading :: Styleable h => CSS h -> CSS h
+ Web.Hyperbole.View.CSS: whenLoading :: Styleable h => (CSS h -> CSS h) -> CSS h -> CSS h
+ Web.Hyperbole.View.Embed: cssEmbed :: ByteString
+ Web.Hyperbole.View.Embed: scriptEmbedSourceMap :: ByteString
+ Web.Hyperbole.View.Embed: scriptLiveReload :: ByteString
+ Web.Hyperbole.View.Render: renderLazyByteString :: View () () -> ByteString
+ Web.Hyperbole.View.Render: renderText :: View () () -> Text
+ Web.Hyperbole.View.Tag: ListItem :: View c a -> ListItem c a
+ Web.Hyperbole.View.Tag: Table :: View c a -> Table c a
+ Web.Hyperbole.View.Tag: TableColumn :: TableHead c () -> (dt -> View c ()) -> TableColumn c dt
+ Web.Hyperbole.View.Tag: TableColumns :: Eff '[State [TableColumn c dt]] a -> TableColumns c dt a
+ Web.Hyperbole.View.Tag: TableHead :: View id a -> TableHead id a
+ Web.Hyperbole.View.Tag: [$sel:dataCell:TableColumn] :: TableColumn c dt -> dt -> View c ()
+ Web.Hyperbole.View.Tag: [$sel:headCell:TableColumn] :: TableColumn c dt -> TableHead c ()
+ Web.Hyperbole.View.Tag: autofocus :: Attributable h => Attributes h -> Attributes h
+ Web.Hyperbole.View.Tag: charset :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View.Tag: code :: Text -> View c ()
+ Web.Hyperbole.View.Tag: col :: View c () -> View c ()
+ Web.Hyperbole.View.Tag: content :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View.Tag: data TableColumn c dt
+ Web.Hyperbole.View.Tag: el :: View c () -> View c ()
+ Web.Hyperbole.View.Tag: form :: View c () -> View c ()
+ Web.Hyperbole.View.Tag: httpEquiv :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View.Tag: img :: Text -> View c ()
+ Web.Hyperbole.View.Tag: input :: View c ()
+ Web.Hyperbole.View.Tag: instance GHC.Base.Applicative (Web.Hyperbole.View.Tag.ListItem c)
+ Web.Hyperbole.View.Tag: instance GHC.Base.Applicative (Web.Hyperbole.View.Tag.Table c)
+ Web.Hyperbole.View.Tag: instance GHC.Base.Applicative (Web.Hyperbole.View.Tag.TableColumns c dt)
+ Web.Hyperbole.View.Tag: instance GHC.Base.Applicative (Web.Hyperbole.View.Tag.TableHead id)
+ Web.Hyperbole.View.Tag: instance GHC.Base.Functor (Web.Hyperbole.View.Tag.ListItem c)
+ Web.Hyperbole.View.Tag: instance GHC.Base.Functor (Web.Hyperbole.View.Tag.Table c)
+ Web.Hyperbole.View.Tag: instance GHC.Base.Functor (Web.Hyperbole.View.Tag.TableColumns c dt)
+ Web.Hyperbole.View.Tag: instance GHC.Base.Functor (Web.Hyperbole.View.Tag.TableHead id)
+ Web.Hyperbole.View.Tag: instance GHC.Base.Monad (Web.Hyperbole.View.Tag.ListItem c)
+ Web.Hyperbole.View.Tag: instance GHC.Base.Monad (Web.Hyperbole.View.Tag.Table c)
+ Web.Hyperbole.View.Tag: instance GHC.Base.Monad (Web.Hyperbole.View.Tag.TableColumns c dt)
+ Web.Hyperbole.View.Tag: instance GHC.Base.Monad (Web.Hyperbole.View.Tag.TableHead id)
+ Web.Hyperbole.View.Tag: instance Web.Atomic.Types.Styleable.Styleable (Web.Hyperbole.View.Tag.ListItem c a)
+ Web.Hyperbole.View.Tag: instance Web.Atomic.Types.Styleable.Styleable (Web.Hyperbole.View.Tag.Table c a)
+ Web.Hyperbole.View.Tag: instance Web.Atomic.Types.Styleable.Styleable (Web.Hyperbole.View.Tag.TableColumns c dt () -> Web.Hyperbole.View.Types.View c ())
+ Web.Hyperbole.View.Tag: instance Web.Atomic.Types.Styleable.Styleable (Web.Hyperbole.View.Tag.TableHead id a)
+ Web.Hyperbole.View.Tag: label :: View c () -> View c ()
+ Web.Hyperbole.View.Tag: li :: View c () -> ListItem c ()
+ Web.Hyperbole.View.Tag: link :: URI -> View c () -> View c ()
+ Web.Hyperbole.View.Tag: meta :: View c ()
+ Web.Hyperbole.View.Tag: name :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View.Tag: nav :: View c () -> View c ()
+ Web.Hyperbole.View.Tag: newtype ListItem c a
+ Web.Hyperbole.View.Tag: newtype Table c a
+ Web.Hyperbole.View.Tag: newtype TableColumns c dt a
+ Web.Hyperbole.View.Tag: newtype TableHead id a
+ Web.Hyperbole.View.Tag: ol :: ListItem c () -> View c ()
+ Web.Hyperbole.View.Tag: placeholder :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View.Tag: pre :: Text -> View c ()
+ Web.Hyperbole.View.Tag: row :: View c () -> View c ()
+ Web.Hyperbole.View.Tag: script :: Text -> View c ()
+ Web.Hyperbole.View.Tag: script' :: ByteString -> View c ()
+ Web.Hyperbole.View.Tag: space :: View c ()
+ Web.Hyperbole.View.Tag: src :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View.Tag: style :: ByteString -> View c ()
+ Web.Hyperbole.View.Tag: stylesheet :: Text -> View c ()
+ Web.Hyperbole.View.Tag: table :: [dt] -> TableColumns c dt () -> View c ()
+ Web.Hyperbole.View.Tag: tcol :: forall dt c. TableHead c () -> (dt -> View c ()) -> TableColumns c dt ()
+ Web.Hyperbole.View.Tag: td :: View c () -> View c ()
+ Web.Hyperbole.View.Tag: th :: View c () -> TableHead c ()
+ Web.Hyperbole.View.Tag: title :: Text -> View c ()
+ Web.Hyperbole.View.Tag: type_ :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View.Tag: ul :: ListItem c () -> View c ()
+ Web.Hyperbole.View.Tag: usersTable :: View c ()
+ Web.Hyperbole.View.Tag: value :: Attributable h => Text -> Attributes h -> Attributes h
+ Web.Hyperbole.View.Types: View :: Eff '[Reader c] (Html a) -> View c a
+ Web.Hyperbole.View.Types: [$sel:html:View] :: View c a -> Eff '[Reader c] (Html a)
+ Web.Hyperbole.View.Types: addContext :: ctx -> View ctx () -> View c ()
+ Web.Hyperbole.View.Types: context :: forall c. View c c
+ Web.Hyperbole.View.Types: instance Data.String.IsString (Web.Hyperbole.View.Types.View c ())
+ Web.Hyperbole.View.Types: instance GHC.Base.Applicative (Web.Hyperbole.View.Types.View ctx)
+ Web.Hyperbole.View.Types: instance GHC.Base.Functor (Web.Hyperbole.View.Types.View c)
+ Web.Hyperbole.View.Types: instance GHC.Base.Monad (Web.Hyperbole.View.Types.View ctx)
+ Web.Hyperbole.View.Types: instance Web.Atomic.Types.Attributable.Attributable (Web.Hyperbole.View.Types.View c a)
+ Web.Hyperbole.View.Types: instance Web.Atomic.Types.Styleable.Styleable (Web.Hyperbole.View.Types.View c a)
+ Web.Hyperbole.View.Types: modifyContext :: forall ctx0 ctx1. (ctx0 -> ctx1) -> View ctx1 () -> View ctx0 ()
+ Web.Hyperbole.View.Types: newtype View c a
+ Web.Hyperbole.View.Types: none :: View c ()
+ Web.Hyperbole.View.Types: raw :: Text -> View c ()
+ Web.Hyperbole.View.Types: runView :: forall c a. c -> View c a -> Html a
+ Web.Hyperbole.View.Types: tag :: Text -> View c () -> View c ()
+ Web.Hyperbole.View.Types: tag' :: Bool -> Text -> View c () -> View c ()
+ Web.Hyperbole.View.Types: text :: Text -> View c ()
+ Web.Hyperbole.View.Types: type family ViewContext (v :: Type)
- Web.Hyperbole: -- deriving (Show, Read, ViewAction)
+ Web.Hyperbole: -- deriving (Generic, ViewAction)
- Web.Hyperbole: -- | Include any child hyperviews here. The compiler will make sure that
+ Web.Hyperbole: -- | Generic representation type
- Web.Hyperbole: Invalid :: Text -> Validated (a :: k)
+ Web.Hyperbole: Invalid :: Text -> Validated a
- Web.Hyperbole: NotInvalid :: Validated (a :: k)
+ Web.Hyperbole: NotInvalid :: Validated a
- Web.Hyperbole: Request :: Host -> [Segment] -> Query -> ByteString -> Method -> Cookies -> Request
+ Web.Hyperbole: Request :: Host -> Path -> Query -> ByteString -> Method -> Cookies -> Maybe (Event TargetViewId Encoded) -> RequestId -> Request
- Web.Hyperbole: Valid :: Validated (a :: k)
+ Web.Hyperbole: Valid :: Validated a
- Web.Hyperbole: addContext :: context -> View context () -> View c ()
+ Web.Hyperbole: addContext :: ctx -> View ctx () -> View c ()
- Web.Hyperbole: att :: Name -> AttValue -> Mod c
+ Web.Hyperbole: att :: Attributable h => Name -> AttValue -> Attributes h -> Attributes h
- Web.Hyperbole: baseRoute :: Route a => Maybe a
+ Web.Hyperbole: baseRoute :: (Route a, Generic a, GenRoute (Rep a)) => Maybe a
- Web.Hyperbole: button :: ViewAction (Action id) => Action id -> Mod id -> View id () -> View id ()
+ Web.Hyperbole: button :: ViewAction (Action id) => Action id -> View id () -> View id ()
- Web.Hyperbole: class (e :: Effect) :> (es :: [Effect])
+ Web.Hyperbole: class () => (e :: Effect) :> (es :: [Effect])
- Web.Hyperbole: class Generic a
+ Web.Hyperbole: class () => Generic a where {
- Web.Hyperbole: class HasViewId (m :: k -> Type) (view :: k)
+ Web.Hyperbole: class HasViewId m view
- Web.Hyperbole: class (ViewId id, ViewAction Action id) => HyperView id (es :: [Effect]) where {
+ Web.Hyperbole: class (ViewId id, ViewAction (Action id)) => HyperView id es where {
- Web.Hyperbole: code :: Mod c -> Text -> View c ()
+ Web.Hyperbole: code :: Text -> View c ()
- Web.Hyperbole: col :: Mod c -> View c () -> View c ()
+ Web.Hyperbole: col :: View c () -> View c ()
- Web.Hyperbole: context :: View context context
+ Web.Hyperbole: context :: forall c. View c c
- Web.Hyperbole: cookiePath :: Session a => Maybe [Segment]
+ Web.Hyperbole: cookiePath :: Session a => Maybe Path
- Web.Hyperbole: data Eff (es :: [Effect]) a
+ Web.Hyperbole: data () => Eff (es :: [Effect]) a
- Web.Hyperbole: data Hyperbole (a :: Type -> Type) b
+ Web.Hyperbole: data Hyperbole :: Effect
- Web.Hyperbole: data Identity a
+ Web.Hyperbole: data () => Identity a
- Web.Hyperbole: data Option opt (id :: k) action
+ Web.Hyperbole: data Option opt id
- Web.Hyperbole: data Validated (a :: k)
+ Web.Hyperbole: data Validated a
- Web.Hyperbole: deleteParam :: forall (es :: [Effect]). Hyperbole :> es => Param -> Eff es ()
+ Web.Hyperbole: deleteParam :: Hyperbole :> es => Param -> Eff es ()
- Web.Hyperbole: deleteSession :: forall a (es :: [Effect]). (Session a, Hyperbole :> es) => Eff es ()
+ Web.Hyperbole: deleteSession :: forall a es. (Session a, Hyperbole :> es) => Eff es ()
- Web.Hyperbole: dropdown :: ViewAction (Action id) => (opt -> Action id) -> (opt -> Bool) -> Mod id -> View (Option opt id (Action id)) () -> View id ()
+ Web.Hyperbole: dropdown :: ViewAction (Action id) => (opt -> Action id) -> opt -> View (Option opt id) () -> View id ()
- Web.Hyperbole: el :: Mod c -> View c () -> View c ()
+ Web.Hyperbole: el :: View c () -> View c ()
- Web.Hyperbole: field :: forall id v a. FormField v a -> (v a -> Mod (FormFields id)) -> View (Input id v a) () -> View (FormFields id) ()
+ Web.Hyperbole: field :: forall (id :: Type) (a :: Type). FieldName a -> View (Input id a) () -> View (FormFields id) ()
- Web.Hyperbole: form :: forall (form :: (Type -> Type) -> Type) (v :: Type -> Type) id. (Form form v, ViewAction (Action id)) => Action id -> Mod id -> View (FormFields id) () -> View id ()
+ Web.Hyperbole: form :: ViewAction (Action id) => Action id -> View (FormFields id) () -> View id ()
- Web.Hyperbole: formData :: forall form (val :: Type -> Type) (es :: [Effect]). (Form form val, Hyperbole :> es) => Eff es (form Identity)
+ Web.Hyperbole: formData :: forall form es. (FromForm form, Hyperbole :> es) => Eff es form
- Web.Hyperbole: hyper :: (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx ()
+ Web.Hyperbole: hyper :: forall id ctx. (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx ()
- Web.Hyperbole: input :: forall id (v :: Type -> Type) a. InputType -> Mod (Input id v a) -> View (Input id v a) ()
+ Web.Hyperbole: input :: InputType -> View (Input id a) ()
- Web.Hyperbole: invalidText :: forall a id. View (Input id (Validated :: Type -> Type) a) ()
+ Web.Hyperbole: invalidText :: forall a id. Validated a -> View (Input id a) ()
- Web.Hyperbole: label :: forall id (v :: Type -> Type) a. Text -> View (Input id v a) ()
+ Web.Hyperbole: label :: View c () -> View c ()
- Web.Hyperbole: li :: Mod c -> View c () -> ListItem c ()
+ Web.Hyperbole: li :: View c () -> ListItem c ()
- Web.Hyperbole: link :: Url -> Mod c -> View c () -> View c ()
+ Web.Hyperbole: link :: URI -> View c () -> View c ()
- Web.Hyperbole: liveApp :: (ByteString -> ByteString) -> Eff '[Hyperbole, Server, Concurrent, IOE] Response -> Application
+ Web.Hyperbole: liveApp :: (ByteString -> ByteString) -> Eff '[Hyperbole, Concurrent, IOE] Response -> Application
- Web.Hyperbole: lookupParam :: forall a (es :: [Effect]). (FromParam a, Hyperbole :> es) => Param -> Eff es (Maybe a)
+ Web.Hyperbole: lookupParam :: (FromParam a, Hyperbole :> es) => Param -> Eff es (Maybe a)
- Web.Hyperbole: lookupSession :: forall a (es :: [Effect]). (Session a, FromParam a, Hyperbole :> es) => Eff es (Maybe a)
+ Web.Hyperbole: lookupSession :: forall a es. (Session a, Hyperbole :> es) => Eff es (Maybe a)
- Web.Hyperbole: matchRoute :: Route a => [Segment] -> Maybe a
+ Web.Hyperbole: matchRoute :: (Route a, Generic a, GenRoute (Rep a)) => Path -> Maybe a
- Web.Hyperbole: modifySession :: forall a (es :: [Effect]). (Session a, DefaultParam a, ToParam a, FromParam a, Hyperbole :> es) => (a -> a) -> Eff es a
+ Web.Hyperbole: modifySession :: (Session a, Default a, Hyperbole :> es) => (a -> a) -> Eff es a
- Web.Hyperbole: modifySession_ :: forall a (es :: [Effect]). (Session a, DefaultParam a, ToParam a, FromParam a, Hyperbole :> es) => (a -> a) -> Eff es ()
+ Web.Hyperbole: modifySession_ :: (Session a, Default a, Hyperbole :> es) => (a -> a) -> Eff es ()
- Web.Hyperbole: name :: Text -> Mod c
+ Web.Hyperbole: name :: Attributable h => Text -> Attributes h -> Attributes h
- Web.Hyperbole: nav :: Mod c -> View c () -> View c ()
+ Web.Hyperbole: nav :: View c () -> View c ()
- Web.Hyperbole: notFound :: forall (es :: [Effect]) a. Hyperbole :> es => Eff es a
+ Web.Hyperbole: notFound :: Hyperbole :> es => Eff es a
- Web.Hyperbole: ol :: Mod c -> ListItem c () -> View c ()
+ Web.Hyperbole: ol :: ListItem c () -> View c ()
- Web.Hyperbole: onClick :: ViewAction (Action id) => Action id -> Mod id
+ Web.Hyperbole: onClick :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a
- Web.Hyperbole: onDblClick :: ViewAction (Action id) => Action id -> Mod id
+ Web.Hyperbole: onDblClick :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a
- Web.Hyperbole: onInput :: ViewAction (Action id) => (Text -> Action id) -> DelayMs -> Mod id
+ Web.Hyperbole: onInput :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => (Text -> Action id) -> DelayMs -> Attributes a -> Attributes a
- Web.Hyperbole: onKeyDown :: ViewAction (Action id) => Key -> Action id -> Mod id
+ Web.Hyperbole: onKeyDown :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Key -> Action id -> Attributes a -> Attributes a
- Web.Hyperbole: onKeyUp :: ViewAction (Action id) => Key -> Action id -> Mod id
+ Web.Hyperbole: onKeyUp :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Key -> Action id -> Attributes a -> Attributes a
- Web.Hyperbole: onLoad :: ViewAction (Action id) => Action id -> DelayMs -> Mod id
+ Web.Hyperbole: onLoad :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> DelayMs -> Attributes a -> Attributes a
- Web.Hyperbole: option :: (ViewAction (Action id), Eq opt) => opt -> View (Option opt id (Action id)) () -> View (Option opt id (Action id)) ()
+ Web.Hyperbole: option :: (ViewAction (Action id), Eq opt, ToParam opt) => opt -> Text -> View (Option opt id) ()
- Web.Hyperbole: param :: forall a (es :: [Effect]). (FromParam a, Hyperbole :> es) => Param -> Eff es a
+ Web.Hyperbole: param :: (FromParam a, Hyperbole :> es) => Param -> Eff es a
- Web.Hyperbole: parseParam :: FromParam a => ParamValue -> Either Text a
+ Web.Hyperbole: parseParam :: (FromParam a, Generic a, GFromJSON Zero (Rep a)) => ParamValue -> Either String a
- Web.Hyperbole: parseQuery :: FromQuery a => QueryData -> Either Text a
+ Web.Hyperbole: parseQuery :: (FromQuery a, Generic a, GFromQuery (Rep a)) => QueryData -> Either String a
- Web.Hyperbole: placeholder :: Text -> Mod id
+ Web.Hyperbole: placeholder :: Attributable h => Text -> Attributes h -> Attributes h
- Web.Hyperbole: pre :: Mod c -> Text -> View c ()
+ Web.Hyperbole: pre :: Text -> View c ()
- Web.Hyperbole: query :: forall a (es :: [Effect]). (FromQuery a, Hyperbole :> es) => Eff es a
+ Web.Hyperbole: query :: (FromQuery a, Hyperbole :> es) => Eff es a
- Web.Hyperbole: queryParams :: forall (es :: [Effect]). Hyperbole :> es => Eff es QueryData
+ Web.Hyperbole: queryParams :: Hyperbole :> es => Eff es QueryData
- Web.Hyperbole: redirect :: forall (es :: [Effect]) a. Hyperbole :> es => Url -> Eff es a
+ Web.Hyperbole: redirect :: Hyperbole :> es => URI -> Eff es a
- Web.Hyperbole: request :: forall (es :: [Effect]). Hyperbole :> es => Eff es Request
+ Web.Hyperbole: request :: Hyperbole :> es => Eff es Request
- Web.Hyperbole: route :: Route a => a -> Mod c -> View c () -> View c ()
+ Web.Hyperbole: route :: Route a => a -> View c () -> View c ()
- Web.Hyperbole: routePath :: Route a => a -> [Segment]
+ Web.Hyperbole: routePath :: (Route a, Generic a, Eq a, GenRoute (Rep a)) => a -> Path
- Web.Hyperbole: routeRequest :: forall (es :: [Effect]) route. (Hyperbole :> es, Route route) => (route -> Eff es Response) -> Eff es Response
+ Web.Hyperbole: routeRequest :: (Hyperbole :> es, Route route) => (route -> Eff es Response) -> Eff es Response
- Web.Hyperbole: row :: Mod c -> View c () -> View c ()
+ Web.Hyperbole: row :: View c () -> View c ()
- Web.Hyperbole: runPage :: forall (es :: [Effect]) (views :: [Type]). (Hyperbole :> es, RunHandlers views es) => Eff es (Page views) -> Eff es Response
+ Web.Hyperbole: runPage :: (Hyperbole :> es, RunHandlers views es) => Page es views -> Eff es Response
- Web.Hyperbole: saveSession :: forall a (es :: [Effect]). (Session a, ToParam a, Hyperbole :> es) => a -> Eff es ()
+ Web.Hyperbole: saveSession :: forall a es. (Session a, Hyperbole :> es) => a -> Eff es ()
- Web.Hyperbole: search :: ViewAction (Action id) => (Text -> Action id) -> DelayMs -> Mod id -> View id ()
+ Web.Hyperbole: search :: ViewAction (Action id) => (Text -> Action id) -> DelayMs -> View id ()
- Web.Hyperbole: session :: forall a (es :: [Effect]). (Session a, DefaultParam a, FromParam a, Hyperbole :> es) => Eff es a
+ Web.Hyperbole: session :: (Session a, Default a, Hyperbole :> es) => Eff es a
- Web.Hyperbole: sessionKey :: Session a => Param
+ Web.Hyperbole: sessionKey :: (Session a, Generic a, GDatatypeName (Rep a)) => Key
- Web.Hyperbole: setParam :: forall a (es :: [Effect]). (ToParam a, Hyperbole :> es) => Param -> a -> Eff es ()
+ Web.Hyperbole: setParam :: (ToParam a, Hyperbole :> es) => Param -> a -> Eff es ()
- Web.Hyperbole: setQuery :: forall a (es :: [Effect]). (ToQuery a, Hyperbole :> es) => a -> Eff es ()
+ Web.Hyperbole: setQuery :: (ToQuery a, Hyperbole :> es) => a -> Eff es ()
- Web.Hyperbole: style :: Text -> View c ()
+ Web.Hyperbole: style :: ByteString -> View c ()
- Web.Hyperbole: submit :: Mod (FormFields id) -> View (FormFields id) () -> View (FormFields id) ()
+ Web.Hyperbole: submit :: View (FormFields id) () -> View (FormFields id) ()
- Web.Hyperbole: table :: Mod c -> [dt] -> Eff '[Writer [TableColumn c dt]] () -> View c ()
+ Web.Hyperbole: table :: [dt] -> TableColumns c dt () -> View c ()
- Web.Hyperbole: tag :: Text -> Mod c -> View c () -> View c ()
+ Web.Hyperbole: tag :: Text -> View c () -> View c ()
- Web.Hyperbole: target :: (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx ()
+ Web.Hyperbole: target :: forall id ctx. (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx ()
- Web.Hyperbole: tcol :: forall dt c. View (TableHead c) () -> (dt -> View dt ()) -> Eff '[Writer [TableColumn c dt]] ()
+ Web.Hyperbole: tcol :: forall dt c. TableHead c () -> (dt -> View c ()) -> TableColumns c dt ()
- Web.Hyperbole: td :: Mod () -> View () () -> View dt ()
+ Web.Hyperbole: td :: View c () -> View c ()
- Web.Hyperbole: textarea :: forall id (v :: Type -> Type) a. Mod (Input id v a) -> Maybe Text -> View (Input id v a) ()
+ Web.Hyperbole: textarea :: Maybe Text -> View (Input id a) ()
- Web.Hyperbole: th :: Mod c -> View c () -> View (TableHead c) ()
+ Web.Hyperbole: th :: View c () -> TableHead c ()
- Web.Hyperbole: toParam :: ToParam a => a -> ParamValue
+ Web.Hyperbole: toParam :: (ToParam a, Generic a, GToJSON Zero (Rep a)) => a -> ParamValue
- Web.Hyperbole: toQuery :: ToQuery a => a -> QueryData
+ Web.Hyperbole: toQuery :: (ToQuery a, Generic a, GToQuery (Rep a)) => a -> QueryData
- Web.Hyperbole: type Page (views :: [Type]) = View Root views ()
+ Web.Hyperbole: type Page es (views :: [Type]) = Eff (Reader (Root views) : es) (View (Root views) ())
- Web.Hyperbole: type Require id = '[] :: [Type];
+ Web.Hyperbole: type Require id = '[];
- Web.Hyperbole: type family Field (context :: Type -> Type) a
+ Web.Hyperbole: type family Rep a :: Type -> Type
- Web.Hyperbole: ul :: Mod c -> ListItem c () -> View c ()
+ Web.Hyperbole: ul :: ListItem c () -> View c ()
- Web.Hyperbole: update :: HyperView id es => Action id -> Eff (Reader id ': es) (View id ())
+ Web.Hyperbole: update :: (HyperView id es, Hyperbole :> es) => Action id -> Eff (Reader id : es) (View id ())
- Web.Hyperbole: validate :: forall {k} (a :: k). Bool -> Text -> Validated a
+ Web.Hyperbole: validate :: Bool -> Text -> Validated a
- Web.Hyperbole: value :: Text -> Mod c
+ Web.Hyperbole: value :: Attributable h => Text -> Attributes h -> Attributes h
- Web.Hyperbole.Application: liveApp :: (ByteString -> ByteString) -> Eff '[Hyperbole, Server, Concurrent, IOE] Response -> Application
+ Web.Hyperbole.Application: liveApp :: (ByteString -> ByteString) -> Eff '[Hyperbole, Concurrent, IOE] Response -> Application
- Web.Hyperbole.Application: routeRequest :: forall (es :: [Effect]) route. (Hyperbole :> es, Route route) => (route -> Eff es Response) -> Eff es Response
+ Web.Hyperbole.Application: routeRequest :: (Hyperbole :> es, Route route) => (route -> Eff es Response) -> Eff es Response
- Web.Hyperbole.Application: socketApp :: forall (es :: [Effect]). (IOE :> es, Concurrent :> es) => Eff (Hyperbole ': (Server ': es)) Response -> PendingConnection -> Eff es ()
+ Web.Hyperbole.Application: socketApp :: ServerOptions -> Request -> Eff '[Hyperbole, Concurrent, IOE] Response -> PendingConnection -> IO ()
- Web.Hyperbole.Application: waiApp :: (ByteString -> ByteString) -> Eff '[Hyperbole, Server, Concurrent, IOE] Response -> Application
+ Web.Hyperbole.Application: waiApp :: ServerOptions -> Eff '[Hyperbole, Concurrent, IOE] Response -> Application
- Web.Hyperbole.Data.QueryData: class GFromQuery (f :: k -> Type)
+ Web.Hyperbole.Data.QueryData: class GFromQuery f
- Web.Hyperbole.Data.QueryData: class GToQuery (f :: k -> Type)
+ Web.Hyperbole.Data.QueryData: class GToQuery f
- Web.Hyperbole.Data.QueryData: gParseQuery :: forall (p :: k). GFromQuery f => QueryData -> Either Text (f p)
+ Web.Hyperbole.Data.QueryData: gParseQuery :: GFromQuery f => QueryData -> Either String (f p)
- Web.Hyperbole.Data.QueryData: gToQuery :: forall (p :: k). GToQuery f => f p -> QueryData
+ Web.Hyperbole.Data.QueryData: gToQuery :: GToQuery f => f p -> QueryData
- Web.Hyperbole.Data.QueryData: parseQuery :: FromQuery a => QueryData -> Either Text a
+ Web.Hyperbole.Data.QueryData: parseQuery :: (FromQuery a, Generic a, GFromQuery (Rep a)) => QueryData -> Either String a
- Web.Hyperbole.Data.QueryData: require :: FromParam a => Param -> QueryData -> Either Text a
+ Web.Hyperbole.Data.QueryData: require :: FromParam a => Param -> QueryData -> Either String a
- Web.Hyperbole.Data.QueryData: toQuery :: ToQuery a => a -> QueryData
+ Web.Hyperbole.Data.QueryData: toQuery :: (ToQuery a, Generic a, GToQuery (Rep a)) => a -> QueryData
- Web.Hyperbole.Effect.Hyperbole: [GetClient] :: forall (a :: Type -> Type). Hyperbole a Client
+ Web.Hyperbole.Effect.Hyperbole: [GetClient] :: Hyperbole m Client
- Web.Hyperbole.Effect.Hyperbole: [GetRequest] :: forall (a :: Type -> Type). Hyperbole a Request
+ Web.Hyperbole.Effect.Hyperbole: [GetRequest] :: Hyperbole m Request
- Web.Hyperbole.Effect.Hyperbole: [ModClient] :: forall (a :: Type -> Type). (Client -> Client) -> Hyperbole a ()
+ Web.Hyperbole.Effect.Hyperbole: [ModClient] :: (Client -> Client) -> Hyperbole m ()
- Web.Hyperbole.Effect.Hyperbole: data Hyperbole (a :: Type -> Type) b
+ Web.Hyperbole.Effect.Hyperbole: data Hyperbole :: Effect
- Web.Hyperbole.Effect.Hyperbole: runHyperbole :: forall (es :: [Effect]). Server :> es => Eff (Hyperbole ': es) Response -> Eff es Response
+ Web.Hyperbole.Effect.Hyperbole: runHyperbole :: Request -> Eff (Hyperbole : es) Response -> Eff es (Response, Client, [Remote])
- Web.Hyperbole.Effect.Query: deleteParam :: forall (es :: [Effect]). Hyperbole :> es => Param -> Eff es ()
+ Web.Hyperbole.Effect.Query: deleteParam :: Hyperbole :> es => Param -> Eff es ()
- Web.Hyperbole.Effect.Query: lookupParam :: forall a (es :: [Effect]). (FromParam a, Hyperbole :> es) => Param -> Eff es (Maybe a)
+ Web.Hyperbole.Effect.Query: lookupParam :: (FromParam a, Hyperbole :> es) => Param -> Eff es (Maybe a)
- Web.Hyperbole.Effect.Query: modifyQuery :: forall (es :: [Effect]). Hyperbole :> es => (QueryData -> QueryData) -> Eff es ()
+ Web.Hyperbole.Effect.Query: modifyQuery :: (ToQuery a, FromQuery a, Default a, Hyperbole :> es) => (a -> a) -> Eff es a
- Web.Hyperbole.Effect.Query: param :: forall a (es :: [Effect]). (FromParam a, Hyperbole :> es) => Param -> Eff es a
+ Web.Hyperbole.Effect.Query: param :: (FromParam a, Hyperbole :> es) => Param -> Eff es a
- Web.Hyperbole.Effect.Query: query :: forall a (es :: [Effect]). (FromQuery a, Hyperbole :> es) => Eff es a
+ Web.Hyperbole.Effect.Query: query :: (FromQuery a, Hyperbole :> es) => Eff es a
- Web.Hyperbole.Effect.Query: queryParams :: forall (es :: [Effect]). Hyperbole :> es => Eff es QueryData
+ Web.Hyperbole.Effect.Query: queryParams :: Hyperbole :> es => Eff es QueryData
- Web.Hyperbole.Effect.Query: setParam :: forall a (es :: [Effect]). (ToParam a, Hyperbole :> es) => Param -> a -> Eff es ()
+ Web.Hyperbole.Effect.Query: setParam :: (ToParam a, Hyperbole :> es) => Param -> a -> Eff es ()
- Web.Hyperbole.Effect.Query: setQuery :: forall a (es :: [Effect]). (ToQuery a, Hyperbole :> es) => a -> Eff es ()
+ Web.Hyperbole.Effect.Query: setQuery :: (ToQuery a, Hyperbole :> es) => a -> Eff es ()
- Web.Hyperbole.Effect.Request: formBody :: forall (es :: [Effect]). Hyperbole :> es => Eff es Form
+ Web.Hyperbole.Effect.Request: formBody :: Hyperbole :> es => Eff es Form
- Web.Hyperbole.Effect.Request: reqPath :: forall (es :: [Effect]). Hyperbole :> es => Eff es [Segment]
+ Web.Hyperbole.Effect.Request: reqPath :: Hyperbole :> es => Eff es Path
- Web.Hyperbole.Effect.Request: request :: forall (es :: [Effect]). Hyperbole :> es => Eff es Request
+ Web.Hyperbole.Effect.Request: request :: Hyperbole :> es => Eff es Request
- Web.Hyperbole.Effect.Response: notFound :: forall (es :: [Effect]) a. Hyperbole :> es => Eff es a
+ Web.Hyperbole.Effect.Response: notFound :: Hyperbole :> es => Eff es a
- Web.Hyperbole.Effect.Response: parseError :: forall (es :: [Effect]) a. Hyperbole :> es => Text -> Eff es a
+ Web.Hyperbole.Effect.Response: parseError :: Hyperbole :> es => String -> Eff es a
- Web.Hyperbole.Effect.Response: redirect :: forall (es :: [Effect]) a. Hyperbole :> es => Url -> Eff es a
+ Web.Hyperbole.Effect.Response: redirect :: Hyperbole :> es => URI -> Eff es a
- Web.Hyperbole.Effect.Response: view :: forall (es :: [Effect]). Hyperbole :> es => View () () -> Eff es Response
+ Web.Hyperbole.Effect.Response: view :: View Body () -> Response
- Web.Hyperbole.Effect.Session: clientSessionCookies :: forall (es :: [Effect]). Hyperbole :> es => Eff es Cookies
+ Web.Hyperbole.Effect.Session: clientSessionCookies :: Hyperbole :> es => Eff es Cookies
- Web.Hyperbole.Effect.Session: deleteSession :: forall a (es :: [Effect]). (Session a, Hyperbole :> es) => Eff es ()
+ Web.Hyperbole.Effect.Session: deleteSession :: forall a es. (Session a, Hyperbole :> es) => Eff es ()
- Web.Hyperbole.Effect.Session: lookupSession :: forall a (es :: [Effect]). (Session a, FromParam a, Hyperbole :> es) => Eff es (Maybe a)
+ Web.Hyperbole.Effect.Session: lookupSession :: forall a es. (Session a, Hyperbole :> es) => Eff es (Maybe a)
- Web.Hyperbole.Effect.Session: modifyCookies :: forall (es :: [Effect]). Hyperbole :> es => (Cookies -> Cookies) -> Eff es ()
+ Web.Hyperbole.Effect.Session: modifyCookies :: Hyperbole :> es => (Cookies -> Cookies) -> Eff es ()
- Web.Hyperbole.Effect.Session: modifySession :: forall a (es :: [Effect]). (Session a, DefaultParam a, ToParam a, FromParam a, Hyperbole :> es) => (a -> a) -> Eff es a
+ Web.Hyperbole.Effect.Session: modifySession :: (Session a, Default a, Hyperbole :> es) => (a -> a) -> Eff es a
- Web.Hyperbole.Effect.Session: modifySession_ :: forall a (es :: [Effect]). (Session a, DefaultParam a, ToParam a, FromParam a, Hyperbole :> es) => (a -> a) -> Eff es ()
+ Web.Hyperbole.Effect.Session: modifySession_ :: (Session a, Default a, Hyperbole :> es) => (a -> a) -> Eff es ()
- Web.Hyperbole.Effect.Session: parseSession :: forall a (es :: [Effect]). (FromParam a, Hyperbole :> es) => Param -> ParamValue -> Eff es a
+ Web.Hyperbole.Effect.Session: parseSession :: (Session a, Hyperbole :> es) => Key -> CookieValue -> Eff es a
- Web.Hyperbole.Effect.Session: requestSessionCookies :: forall (es :: [Effect]). Hyperbole :> es => Eff es Cookies
+ Web.Hyperbole.Effect.Session: requestSessionCookies :: Hyperbole :> es => Eff es Cookies
- Web.Hyperbole.Effect.Session: saveSession :: forall a (es :: [Effect]). (Session a, ToParam a, Hyperbole :> es) => a -> Eff es ()
+ Web.Hyperbole.Effect.Session: saveSession :: forall a es. (Session a, Hyperbole :> es) => a -> Eff es ()
- Web.Hyperbole.Effect.Session: session :: forall a (es :: [Effect]). (Session a, DefaultParam a, FromParam a, Hyperbole :> es) => Eff es a
+ Web.Hyperbole.Effect.Session: session :: (Session a, Default a, Hyperbole :> es) => Eff es a
- Web.Hyperbole.Effect.Session: sessionCookies :: forall (es :: [Effect]). Hyperbole :> es => Eff es Cookies
+ Web.Hyperbole.Effect.Session: sessionCookies :: Hyperbole :> es => Eff es Cookies
- Web.Hyperbole.Effect.Session: setCookie :: forall a (es :: [Effect]). (ToParam a, Hyperbole :> es) => Cookie -> Eff es ()
+ Web.Hyperbole.Effect.Session: setCookie :: (ToParam a, Hyperbole :> es) => Cookie -> Eff es ()
- Web.Hyperbole.Page: runPage :: forall (es :: [Effect]) (views :: [Type]). (Hyperbole :> es, RunHandlers views es) => Eff es (Page views) -> Eff es Response
+ Web.Hyperbole.Page: runPage :: (Hyperbole :> es, RunHandlers views es) => Page es views -> Eff es Response
- Web.Hyperbole.Page: type Page (views :: [Type]) = View Root views ()
+ Web.Hyperbole.Page: type Page es (views :: [Type]) = Eff (Reader (Root views) : es) (View (Root views) ())
- Web.Hyperbole.Route: baseRoute :: Route a => Maybe a
+ Web.Hyperbole.Route: baseRoute :: (Route a, Generic a, GenRoute (Rep a)) => Maybe a
- Web.Hyperbole.Route: class GenRoute (f :: k -> Type)
+ Web.Hyperbole.Route: class GenRoute f
- Web.Hyperbole.Route: genPaths :: forall (p :: k). GenRoute f => f p -> [Text]
+ Web.Hyperbole.Route: genPaths :: GenRoute f => f p -> [Text]
- Web.Hyperbole.Route: genRoute :: forall (p :: k). GenRoute f => [Text] -> Maybe (f p)
+ Web.Hyperbole.Route: genRoute :: GenRoute f => [Text] -> Maybe (f p)
- Web.Hyperbole.Route: genRouteRead :: forall {k} x (a :: k). Read x => [Text] -> Maybe (K1 R x a)
+ Web.Hyperbole.Route: genRouteRead :: Read x => [Text] -> Maybe (K1 R x a)
- Web.Hyperbole.Route: matchRoute :: Route a => [Segment] -> Maybe a
+ Web.Hyperbole.Route: matchRoute :: (Route a, Generic a, GenRoute (Rep a)) => Path -> Maybe a
- Web.Hyperbole.Route: routePath :: Route a => a -> [Segment]
+ Web.Hyperbole.Route: routePath :: (Route a, Generic a, Eq a, GenRoute (Rep a)) => a -> Path
- Web.Hyperbole.TypeList: type Elem e (es :: [Type]) = ElemOr e es NotElem e es :: Constraint
+ Web.Hyperbole.TypeList: type Elem e es = ElemOr e es (NotElem e es)
- Web.Hyperbole.TypeList: type NotElem (x :: t) (orig :: [Type]) = TypeError 'ShowType x ':<>: 'Text " not found in " ':<>: 'ShowType orig :: k
+ Web.Hyperbole.TypeList: type NotElem x (orig :: [Type]) = TypeError ('ShowType x ':<>: 'Text " not found in " ':<>: 'ShowType orig)
- Web.Hyperbole.TypeList: type family TupleList a :: [Type]
+ Web.Hyperbole.TypeList: type family TupleList a
- Web.Hyperbole.View: addContext :: context -> View context () -> View c ()
+ Web.Hyperbole.View: addContext :: ctx -> View ctx () -> View c ()
- Web.Hyperbole.View: code :: Mod c -> Text -> View c ()
+ Web.Hyperbole.View: code :: Text -> View c ()
- Web.Hyperbole.View: col :: Mod c -> View c () -> View c ()
+ Web.Hyperbole.View: col :: View c () -> View c ()
- Web.Hyperbole.View: context :: View context context
+ Web.Hyperbole.View: context :: forall c. View c c
- Web.Hyperbole.View: el :: Mod c -> View c () -> View c ()
+ Web.Hyperbole.View: el :: View c () -> View c ()
- Web.Hyperbole.View: li :: Mod c -> View c () -> ListItem c ()
+ Web.Hyperbole.View: li :: View c () -> ListItem c ()
- Web.Hyperbole.View: link :: Url -> Mod c -> View c () -> View c ()
+ Web.Hyperbole.View: link :: URI -> View c () -> View c ()
- Web.Hyperbole.View: name :: Text -> Mod c
+ Web.Hyperbole.View: name :: Attributable h => Text -> Attributes h -> Attributes h
- Web.Hyperbole.View: nav :: Mod c -> View c () -> View c ()
+ Web.Hyperbole.View: nav :: View c () -> View c ()
- Web.Hyperbole.View: ol :: Mod c -> ListItem c () -> View c ()
+ Web.Hyperbole.View: ol :: ListItem c () -> View c ()
- Web.Hyperbole.View: pre :: Mod c -> Text -> View c ()
+ Web.Hyperbole.View: pre :: Text -> View c ()
- Web.Hyperbole.View: row :: Mod c -> View c () -> View c ()
+ Web.Hyperbole.View: row :: View c () -> View c ()
- Web.Hyperbole.View: style :: Text -> View c ()
+ Web.Hyperbole.View: style :: ByteString -> View c ()
- Web.Hyperbole.View: table :: Mod c -> [dt] -> Eff '[Writer [TableColumn c dt]] () -> View c ()
+ Web.Hyperbole.View: table :: [dt] -> TableColumns c dt () -> View c ()
- Web.Hyperbole.View: tag :: Text -> Mod c -> View c () -> View c ()
+ Web.Hyperbole.View: tag :: Text -> View c () -> View c ()
- Web.Hyperbole.View: tcol :: forall dt c. View (TableHead c) () -> (dt -> View dt ()) -> Eff '[Writer [TableColumn c dt]] ()
+ Web.Hyperbole.View: tcol :: forall dt c. TableHead c () -> (dt -> View c ()) -> TableColumns c dt ()
- Web.Hyperbole.View: td :: Mod () -> View () () -> View dt ()
+ Web.Hyperbole.View: td :: View c () -> View c ()
- Web.Hyperbole.View: th :: Mod c -> View c () -> View (TableHead c) ()
+ Web.Hyperbole.View: th :: View c () -> TableHead c ()
- Web.Hyperbole.View: ul :: Mod c -> ListItem c () -> View c ()
+ Web.Hyperbole.View: ul :: ListItem c () -> View c ()
- Web.Hyperbole.View: value :: Text -> Mod c
+ Web.Hyperbole.View: value :: Attributable h => Text -> Attributes h -> Attributes h
Files
- CHANGELOG.md +30/−2
- README.md +40/−187
- client/dist/hyperbole.js +1/−1
- client/dist/hyperbole.js.map +1/−0
- client/util/live-reload.js +48/−0
- hyperbole.cabal +59/−13
- src/Web/Hyperbole.hs +196/−148
- src/Web/Hyperbole/Application.hs +51/−105
- src/Web/Hyperbole/Data/Cookie.hs +78/−0
- src/Web/Hyperbole/Data/Encoded.hs +289/−0
- src/Web/Hyperbole/Data/JSON.hs +21/−0
- src/Web/Hyperbole/Data/Param.hs +274/−0
- src/Web/Hyperbole/Data/QueryData.hs +68/−225
- src/Web/Hyperbole/Data/Session.hs +0/−89
- src/Web/Hyperbole/Data/URI.hs +131/−0
- src/Web/Hyperbole/Document.hs +63/−0
- src/Web/Hyperbole/Effect/Client.hs +52/−0
- src/Web/Hyperbole/Effect/Event.hs +0/−36
- src/Web/Hyperbole/Effect/GenRandom.hs +56/−0
- src/Web/Hyperbole/Effect/Handler.hs +0/−139
- src/Web/Hyperbole/Effect/Hyperbole.hs +42/−36
- src/Web/Hyperbole/Effect/OAuth2.hs +290/−0
- src/Web/Hyperbole/Effect/Query.hs +31/−14
- src/Web/Hyperbole/Effect/Request.hs +6/−4
- src/Web/Hyperbole/Effect/Response.hs +33/−19
- src/Web/Hyperbole/Effect/Server.hs +0/−292
- src/Web/Hyperbole/Effect/Session.hs +86/−24
- src/Web/Hyperbole/HyperView.hs +14/−215
- src/Web/Hyperbole/HyperView/Event.hs +134/−0
- src/Web/Hyperbole/HyperView/Forms.hs +644/−0
- src/Web/Hyperbole/HyperView/Handled.hs +122/−0
- src/Web/Hyperbole/HyperView/Input.hs +100/−0
- src/Web/Hyperbole/HyperView/Types.hs +58/−0
- src/Web/Hyperbole/HyperView/ViewAction.hs +40/−0
- src/Web/Hyperbole/HyperView/ViewId.hs +56/−0
- src/Web/Hyperbole/Page.hs +17/−8
- src/Web/Hyperbole/Route.hs +29/−35
- src/Web/Hyperbole/Server/Handler.hs +86/−0
- src/Web/Hyperbole/Server/Message.hs +229/−0
- src/Web/Hyperbole/Server/Options.hs +65/−0
- src/Web/Hyperbole/Server/Socket.hs +139/−0
- src/Web/Hyperbole/Server/Wai.hs +172/−0
- src/Web/Hyperbole/Types/Client.hs +29/−0
- src/Web/Hyperbole/Types/Event.hs +25/−0
- src/Web/Hyperbole/Types/Request.hs +31/−0
- src/Web/Hyperbole/Types/Response.hs +53/−0
- src/Web/Hyperbole/View.hs +10/−8
- src/Web/Hyperbole/View/CSS.hs +27/−0
- src/Web/Hyperbole/View/Element.hs +0/−72
- src/Web/Hyperbole/View/Embed.hs +21/−3
- src/Web/Hyperbole/View/Event.hs +0/−126
- src/Web/Hyperbole/View/Forms.hs +0/−559
- src/Web/Hyperbole/View/Render.hs +17/−0
- src/Web/Hyperbole/View/Tag.hs +255/−0
- src/Web/Hyperbole/View/Types.hs +122/−0
- test/Test/EncodedSpec.hs +237/−0
- test/Test/FormSpec.hs +48/−5
- test/Test/ParamSpec.hs +96/−0
- test/Test/QuerySpec.hs +68/−77
- test/Test/RouteSpec.hs +3/−1
- test/Test/SessionSpec.hs +85/−0
- test/Test/URISpec.hs +34/−0
- test/Test/ViewActionSpec.hs +47/−40
- test/Test/ViewIdSpec.hs +43/−43
CHANGELOG.md view
@@ -1,10 +1,38 @@ # Revision history for hyperbole -## 0.4.3 -- 2024-01-31+## 0.5.0 -- 2025-09-26 +Improvements+* `trigger` actions in other views+* Javascript FFI+ * `window.Hyperbole` - API available from custom JS. `runAction` allows JS to trigger actions+ * `pushEvent` - send events to JS from the server+* Documents+ * Choose to configure with `View DocumentHead ()` instead of `ByteString` `->` `ByteString`+ * `quickStartDocument`+ * Live Reload+* Websocket - ping keepalive+* New form fields: `radio`, `select`+* `Web.Hyperbole.Effect.OAuth2` - Authentication +* `Web.Hyperbole.Effect.GenRandom` - Simple random effect used by OAuth2+* Error handling, custom errors+* Examples+ * Many additions and improvements+ * External Stylesheet TodoMVC+ * OAuth2 example++Breaking Changes / Improvements+* `Web.Atomic.CSS` overhauled, and is now opt-in. Use new `@` and `~` operators to apply attributes and styles+* `Web.Hyperbole.Data.Param` - unified param encoding for Forms, ViewId, ViewAction, Sessions, Queries+* `Web.Hyperbole.Data.Encoding` - encoding for ViewId, ViewAction+* `Web.Hyperbole.Data.URI` - Standardize on `Network.URI`, extra utilities to manage paths+* `trigger`: required refactor of `Page` type alias to support type-checking: `Eff es (Page '[])` is now `Page es '[]`++## 0.4.3 -- 2025-01-31+ * Bug fixes and improvements -## 0.4.2 -- 2024-01-21+## 0.4.2 -- 2025-01-21 * Cleaner HyperView class [(@cgeorgii)](https://github.com/cgeorgii) * data family Action
README.md view
@@ -1,11 +1,11 @@-+ -[](https://hackage.haskell.org/package/hyperbole)+[](https://hackage.haskell.org/package/hyperbole) -Create interactive HTML applications with type-safe serverside Haskell. Inspired by [HTMX](https://htmx.org/), [Elm](https://elm-lang.org/), and [Phoenix LiveView](https://www.phoenixframework.org/)+Create interactive HTML applications with type-safe serverside Haskell. Inspired by HTMX, Elm, and Phoenix LiveView. -[Learn more about Hyperbole on Hackage](https://hackage.haskell.org/package/hyperbole/docs/Web-Hyperbole.html) + ```haskell {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE OverloadedStrings #-}@@ -14,40 +14,52 @@ import Data.Text (Text) import Web.Hyperbole+import Web.Atomic.CSS + main :: IO () main = do run 3000 $ do- liveApp (basicDocument "Example") (runPage page)-+ liveApp quickStartDocument (runPage page) page :: (Hyperbole :> es) => Eff es (Page '[Message]) page = do- pure $ col id $ do+ pure $ do hyper Message1 $ messageView "Hello" hyper Message2 $ messageView "World!" - data Message = Message1 | Message2- deriving (Show, Read, ViewId)-+ deriving (Generic, ViewId) instance HyperView Message es where data Action Message = Louder Text- deriving (Show, Read, ViewAction)+ deriving (Generic, ViewAction) update (Louder msg) = do let new = msg <> "!" pure $ messageView new - messageView :: Text -> View Message () messageView msg = do- row id $ do- button (Louder msg) id "Louder"- el_ $ text msg+ button (Louder msg) ~ border 1 $ text msg ``` +++Examples+---------++The examples directory contains an app demonstrating many features. See them in action at [hyperbole.live](https://hyperbole.live)++<a href="https://hyperbole.live">+ <!--<img alt="Hyperbole Examples" src="https://github.com/seanhess/hyperbole/raw/main/examples/static/examples.png"/>-->+ <img alt="Hyperbole Examples" src="https://github.com/seanhess/hyperbole/raw/main/examples/static/examples.png"/>+</a>++<!-- out of date!+* [HaskRead](https://github.com/tusharad/Reddit-Clone-Haskell) - A Reddit Clone+-->+ Getting Started with Cabal -------------------------- @@ -57,7 +69,7 @@ $ cd myapp $ cabal init -Add hyperbole and text to your build-depends:+Add hyperbole and text as dependencies to the `.cabal` file: ``` build-depends:@@ -66,199 +78,38 @@ , text ``` -Paste the above example into Main.hs, and run+Paste the above example into Main.hs, and run: $ cabal run Visit http://localhost:3000 to view the application -Examples------------The example directory contains an app demonstrating various features. See it in action at https://docs.hyperbole.live--<a href="https://docs.hyperbole.live">- <img alt="Hyperbole Examples" src="example/static/examples.png"/>-</a>--### Try Example Project Locally--These will run the examples webserver--#### With Nix--`nix run github:seanhess/hyperbole`--#### With Docker--`docker run -it -p 3000:3000 ghcr.io/seanhess/hyperbole:latest`- Learn More ---------- -View Documentation on Hackage-* https://hackage.haskell.org/package/hyperbole/docs/Web-Hyperbole.html--View on Github-* https://github.com/seanhess/hyperbole+<a href="https://hackage.haskell.org/package/hyperbole/docs/Web-Hyperbole.html" target="_blank" style="border-radius: 20px; Background-color:#f8f8f8; gap: 20px; display: flex; flex-direction: row; align-items: center">+ <img src="https://github.com/seanhess/hyperbole/raw/main/docs/hackage.svg">+</a> +* [Using NIX](./docs/nix.md)+* [Local Development](./docs/dev.md)+* [Comparison with Similar Frameworks](./docs/comparison.md) -Full Production Example------------------------+In the Wild+--------------------- <a href="https://nso.edu"> <img alt="National Solar Observatory" src="https://nso1.b-cdn.net/wp-content/uploads/2020/03/NSO-logo-orange-text.png" width="400"/> </a> -The NSO uses Hyperbole for the Level 2 Data creation tool for the [DKIST telescope](https://nso.edu/telescopes/dki-solar-telescope/). It is completely [open source](https://github.com/DKISTDC/level2/). This production application contains complex interfaces, workers, databases, and more.+The NSO uses Hyperbole to manage Level 2 Data pipelines for the [DKIST telescope](https://nso.edu/telescopes/dki-solar-telescope/). It uses complex user interfaces, workers, databases, and more. [The entire codebase is open source](https://github.com/DKISTDC/level2/). -How to Import Flake------------- -You can import this flake's overlay to add `hyperbole` to all package sets and override ghc966 and ghc982 with the packages to satisfy `hyperbole`'s dependencies. -```nix-{- inputs = {- nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";- hyperbole.url = "github:seanhess/hyperbole"; # or "path:/path/to/cloned/hyperbole";- flake-utils.url = "github:numtide/flake-utils";- }; - outputs = { self, nixpkgs, hyperbole, flake-utils, ... }:- flake-utils.lib.eachDefaultSystem (- system:- let- pkgs = import nixpkgs {- inherit system;- overlays = [ hyperbole.overlays.default ];- };- haskellPackagesOverride = pkgs.overriddenHaskellPackages.ghc966.override (old: {- overrides = pkgs.lib.composeExtensions (old.overrides or (_: _: { })) (hfinal: hprev: {- # your overrides here- });- });- in- {- devShells.default = haskellPackagesOverride.shellFor {- packages = p: [ p.hyperbole ];- };- }- );-}-``` -Local Development--------------------### Recommended ghcid command--If you want to work on both the hyperbole library and example code, this `ghcid` command will run (and hot reload) the examples server as you change any non-testing code.--```-ghcid --setup=Main.update --command="cabal repl exe:examples lib:hyperbole" --run=Main.update --warnings --reload=./client/dist/hyperbole.js-```--If you want to work on the test suite, this will run the tests each time any library code is changed.--```-ghcid --command="cabal repl test lib:hyperbole" --run=Main.main --warnings --reload=./client/dist/hyperbole.js-```--### Nix--- `nix flake check` will build the library, example executable and devShell with ghc-9.8.2 and ghc-9.6.6- - This is what the CI on GitHub runs-- `nix run` or `nix run .#ghc982-example` to start the example project with GHC 9.8.2- - `nix run .#ghc966-example` to start the example project with GHC 9.6.6-- `nix develop` or `nix develop .#ghc982-shell` to get a shell with all dependencies installed for GHC 9.8.2. - - `nix develop .#ghc966-shell` to get a shell with all dependencies installed for GHC 9.6.6. -- `nix build`, `nix build .#ghc982-hyperbole` and `nix build .#ghc966-hyperbole` builds the library with the `overriddenHaskellPackages`- - If you want to import this flake, use the overlay-- `nix flake update web-view` will update the `web-view` input--Note: You can always run `cachix use hyperbole` to use the GitHub CI populated cache if you didn't allow adding 'extra-substituters' when first using this flake.--### Common Nix Issues--#### Not Allowed to Refer to GHC--If you get an error like:--```-error: output '/nix/store/64k8iw0ryz76qpijsnl9v87fb26v28z8-my-haskell-package-1.0.0.0' is not allowed to refer to the following paths:- /nix/store/5q5s4a07gaz50h04zpfbda8xjs8wrnhg-ghc-9.6.3-```--Follow these [instructions](https://nixos.org/manual/nixpkgs/unstable/#haskell-packaging-helpers)--#### Dependencies Incorrect--If you need to update `web-view` run `nix flake update web-view`, otherwise:--You will need to update the overlay, look for where it says `"${packageName}" = hfinal.callCabal2nix packageName src { };` and add a line like `Diff = hfinal.callHackage "Diff" "0.5" { };` with the package and version you need.--#### Missing Files--Check the `include` inside the `nix-filter.lib` to see if all files needed by cabal are there.--### Manual dependency installation---Download and install [NPM](https://nodejs.org/en/download). On a mac, can be installed via homebrew:--```-brew install npm-```--Install client dependencies--```-cd client-npm install-```--Recommended: Use `direnv` to automatically load environment from .env--```-brew install direnv-direnv allow-```---### Building--Build JavaScript client--```-cd client-npx webpack-```--Run examples--```-cd example-cabal run-```--### Tests--```-cabal test-```--### File watching--Run tests, then recompile everything on file change and restart examples--```-bin/dev-```-- Contributors ------------ @@ -267,3 +118,5 @@ * [Christian Georgii](https://github.com/cgeorgii) * [Pfalzgraf Martin](https://github.com/Skyfold) * [Tushar Adhatrao](https://github.com/tusharad)+* [Benjamin Thomas](https://github.com/benjamin-thomas)+* [Adithya Obilisetty](https://github.com/adithyaov)
client/dist/hyperbole.js view
@@ -1,3 +1,3 @@ /*! For license information please see hyperbole.js.LICENSE.txt */-(()=>{var e={296:e=>{function t(e,t=100,n={}){if("function"!=typeof e)throw new TypeError(`Expected the first parameter to be a function, got \`${typeof e}\`.`);if(t<0)throw new RangeError("`wait` must not be negative.");const{immediate:r}="boolean"==typeof n?{immediate:n}:n;let o,i,a,c,u;function s(){const t=o,n=i;return o=void 0,i=void 0,u=e.apply(t,n),u}function l(){const e=Date.now()-c;e<t&&e>=0?a=setTimeout(l,t-e):(a=void 0,r||(u=s()))}const d=function(...e){if(o&&this!==o&&Object.getPrototypeOf(this)===Object.getPrototypeOf(o))throw new Error("Debounced method called with different contexts of the same prototype.");o=this,i=e,c=Date.now();const n=r&&!a;return a||(a=setTimeout(l,t)),n&&(u=s()),u};return Object.defineProperty(d,"isPending",{get:()=>void 0!==a}),d.clear=()=>{a&&(clearTimeout(a),a=void 0)},d.flush=()=>{a&&d.trigger()},d.trigger=()=>{u=s(),d.clear()},d}e.exports.debounce=t,e.exports=t}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var i=t[r]={exports:{}};return e[r](i,i.exports,n),i.exports}(()=>{"use strict";var e=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t=function(e,t){var n=e.length,r=-1;if(n)for(;++r<n&&!1!==t(e[r],r););},r=function(e,t,n){return e.node.insertBefore(t.node,n)};function o(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,o,i,a,c=[],u=!0,s=!1;try{if(i=(n=n.call(e)).next,0===t){if(Object(n)!==n)return;u=!1}else for(;!(u=(r=i.call(n)).done)&&(c.push(r.value),c.length!==t);u=!0);}catch(e){s=!0,o=e}finally{try{if(!u&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(s)throw o}}return c}}(e,t)||function(e,t){if(e){if("string"==typeof e)return i(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?i(e,t):void 0}}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}var a="string",c="number",u="boolean",s={},l=function(e,t,n){return{attrName:e,propName:t,type:n}};t([["style","cssText"],["class","className"]],(function(e){var t=o(e,2),n=t[0],r=t[1];s[n]=l(n,r||n,a)})),t(["autofocus","draggable","hidden","checked","multiple","muted","selected"],(function(e){s[e]=l(e,e,u)})),t([["tabindex","tabIndex"]],(function(e){var t=o(e,2),n=t[0],r=t[1];s[n]=l(n,r,c)}));var d="xlink:",f="http://www.w3.org/1999/xlink",v="xml:",h="http://www.w3.org/XML/1998/namespace",p=function(e,t,n,r){switch(t){case a:n===s.style.propName?e.style[n]=null===r?"":r:e[n]=null===r?"":r;break;case c:if(null===r){var o=n.toLowerCase();e.removeAttribute(o)}else if("0"===r)e[n]=0;else if("-1"===r)e[n]=-1;else{var i=parseInt(r,10);isNaN(i)||(e[n]=i)}break;case u:["","true"].indexOf(r)<0?e[n]=!1:e[n]=!0}},y=function n(o,i,c){if(o&&i){c=c||i.node.parentNode;var u=o.content&&o.content!==i.content;if(o.type!==i.type||u)return c.replaceChild(o.node,i.node),function(e,t){for(var n in e)t[n]=e[n]}(o,i);(function(n,r){var o=[],i={};for(var c in r.attributes){var u=r.attributes[c],l=n.attributes[c];u!==l&&void 0===l&&o.push(c)}for(var y in n.attributes){var m=r.attributes[y],w=n.attributes[y];m!==w&&void 0!==w&&(i[y]=w)}!function(n,r){t(r,(function(t){if(e(s,t)){var r=s[t];p(n.node,r.type,r.propName,null)}else t in n.node&&p(n.node,a,t,null),n.node.removeAttribute(t);delete n.attributes[t]}))}(r,o),function(t,n){for(var r in n){var o=n[r];if(t.attributes[r]=o,e(s,r)){var i=s[r];p(t.node,i.type,i.propName,o)}else r.startsWith(d)?t.node.setAttributeNS(f,r,o):r.startsWith(v)?t.node.setAttributeNS(h,r,o):(r in t.node&&p(t.node,a,r,o),t.node.setAttribute(r,o||""))}}(r,i)})(o,i),function(n,o,i){var a=n.children.length,c=o.children.length;if(a||c){var u=function(n){var r={};for(var o in t(n,(function(t){var n=t.attributes["data-key"];(function(t,n){return!(!n||e(t,n)&&(console.warn("[OmDomDom]: Children with duplicate keys detected. Children with duplicate keys will be skipped, resulting in dropped node references. Keys must be unique and non-indexed."),1))})(r,n)&&(r[n]=t)})),r)return r}(o.children),s=Array(a);t(n.children,void 0!==u?function(e,t){var n=o.node.childNodes,a=e.attributes["data-key"];if(Object.prototype.hasOwnProperty.call(u,a)){var c=u[a];Array.prototype.indexOf.call(n,c.node)!==t&&r(o,c,n[t]),s[t]=c,delete u[a],i(e,s[t])}else r(o,e,n[t]),s[t]=e}:function(e,t){var n=o.children[t];void 0!==n?(i(e,n),s[t]=n):(o.node.appendChild(e.node),s[t]=e)}),o.children=s;var l=o.node.childNodes.length,d=l-a;if(d>0)for(;d>0;)o.node.removeChild(o.node.childNodes[l-1]),l--,d--}}(o,i,n)}},m=function n(r){var o,i=arguments.length>1&&void 0!==arguments[1]&&arguments[1];"string"==typeof r&&(o=r.trim().replace(/\s+</g,"<").replace(/>\s+/g,">"),r=(new DOMParser).parseFromString(o,"text/html").body);var a="BODY"===r.tagName,c=r.childNodes,u=c?c.length:0;if(a){if(u>1)throw new Error("[OmDomDom]: Your element should not have more than one root node.");if(0===u)throw new Error("[OmDomDom]: Your element should have at least one root node.");return n(c[0])}var l=3===r.nodeType?"text":8===r.nodeType?"comment":r.tagName.toLowerCase(),d=i||"svg"===l,f=1===r.nodeType?function(t){var n=function(t){return Array.prototype.reduce.call(t.attributes,(function(t,n){return e(s,n.name)||(t[n.name]=n.value),t}),{})}(t);return function(e,t){for(var n in s){var r=s[n].propName,o=e.getAttribute(n);n===s.style.attrName?t[n]=e.style[r]:"string"==typeof o&&(t[n]=o)}}(t,n),n}(r):{},v=u>0?null:r.textContent,h=Array(u);return t(c,(function(e,t){h[t]=n(e,d)})),{type:l,attributes:f,children:h,content:v,node:r,isSVGContext:d}};function w(e){if(e!=function(){var e=window.location.search;return e.startsWith("?")?e.substring(1):e}()){""!=e&&(e="?"+e);var t=location.pathname+e;console.log("history.replaceState(",t,")"),window.history.replaceState({},"",t)}}var b=function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{u(r.next(e))}catch(e){i(e)}}function c(e){try{u(r.throw(e))}catch(e){i(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,c)}u((r=r.apply(e,t||[])).next())}))},g=function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(c){return function(u){return function(c){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,c[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&c[0]?r.return:c[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,c[1])).done)return o;switch(r=0,o&&(c=[2&c[0],o.value]),c[0]){case 0:case 1:o=c;break;case 4:return a.label++,{value:c[1],done:!1};case 5:a.label++,r=c[1],c=[0];continue;case 7:c=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==c[0]&&2!==c[0])){a=0;continue}if(3===c[0]&&(!o||c[1]>o[0]&&c[1]<o[3])){a.label=c[1];break}if(6===c[0]&&a.label<o[1]){a.label=o[1],o=c;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(c);break}o[2]&&a.ops.pop(),a.trys.pop();continue}c=t.call(e,a)}catch(e){c=[6,e],r=0}finally{n=o=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}([c,u])}}},k="https:"===window.location.protocol?"wss:":"ws:",x="".concat(k,"//").concat(window.location.host).concat(window.location.pathname),E=function(){function e(){this.reconnectDelay=0}return e.prototype.connect=function(e){var t=this;void 0===e&&(e=x);var n=new WebSocket(e);function r(e){console.log("Connection Error",e)}this.socket=n,n.addEventListener("error",r),n.addEventListener("open",(function(e){console.log("Opened",e),t.isConnected=!0,t.hasEverConnected=!0,t.reconnectDelay=0,t.socket.removeEventListener("error",r)})),n.addEventListener("close",(function(n){t.isConnected=!1,console.log("Closed"),t.hasEverConnected&&(t.reconnectDelay+=1e3,console.log("Reconnecting in "+t.reconnectDelay/1e3+"s"),setTimeout((function(){return t.connect(e)}),t.reconnectDelay))}))},e.prototype.sendAction=function(e){return b(this,void 0,void 0,(function(){var t,n;return g(this,(function(r){switch(r.label){case 0:return t=[e.url.pathname+e.url.search,"Host: "+window.location.host,"Cookie: "+document.cookie,e.form].join("\n"),[4,this.fetch(e.id,t)];case 1:return(n=r.sent()).metadata,[2,n.rest]}}))}))},e.prototype.fetch=function(e,t){return b(this,void 0,void 0,(function(){return g(this,(function(n){switch(n.label){case 0:return this.sendMessage(t),[4,this.waitMessage(e)];case 1:return[2,n.sent()]}}))}))},e.prototype.sendMessage=function(e){this.socket.send(e)},e.prototype.waitMessage=function(e){return b(this,void 0,void 0,(function(){var t=this;return g(this,(function(n){return[2,new Promise((function(n,r){var o=function(r){var i,a,c=function(e){var t=e.split("\n"),n=function(e,t){for(var n=[],r=0,o=t;r<o.length;r++){var i=e(o[r]);if(!i)break;n.push(i)}return n}(o,t),r=function(e,t){for(var n=0;n<t.length&&e(t[n]);)n++;return t.slice(n)}(o,t).join("\n");return{metadata:C(n),rest:r};function o(e){var t=e.match(/^\|([A-Z\-]+)\|(.*)$/);if(t)return{key:t[1],value:t[2]}}}(r.data),u=c.metadata,s=c.rest;if(u.error)throw i=u.error,(a=new Error).name=i.substring(0,i.indexOf(" ")),a.message=i.substring(i.indexOf(" ")+1),a;u.cookies.forEach((function(e){document.cookie=e})),u.redirect?window.location.href=u.redirect:(void 0!==u.query&&w(u.query),u.viewId==e&&(n({metadata:u,rest:s}),t.socket.removeEventListener("message",o)))};t.socket.addEventListener("message",o),t.socket.addEventListener("error",r)}))]}))}))},e.prototype.disconnect=function(){this.socket.close()},e}();function C(e){var t,n,r,o;return{cookies:e.filter((function(e){return"COOKIE"==e.key})).map((function(e){return e.value})),redirect:null===(t=e.find((function(e){return"REDIRECT"==e.key})))||void 0===t?void 0:t.value,error:null===(n=e.find((function(e){return"ERROR"==e.key})))||void 0===n?void 0:n.value,viewId:null===(r=e.find((function(e){return"VIEW-ID"==e.key})))||void 0===r?void 0:r.value,query:null===(o=e.find((function(e){return"QUERY"==e.key})))||void 0===o?void 0:o.value}}var D=n(296);function O(e){if(e){var t=new URLSearchParams;return e.forEach((function(e,n){t.append(n,e)})),t}}function S(e,t,n){var r=function(e,t){var n=new URL(window.location.href);return n.searchParams.append("hyp-id",e),n.searchParams.append("hyp-action",t),n}(e,t);return{id:e,url:r,form:O(n)}}function L(e,t){document.addEventListener(e.toLowerCase(),(function(n){var r=n.target,o="on"+e+n.key,i=r.dataset[o];i&&(n.preventDefault(),t(A(r),i))}))}function T(e){e.querySelectorAll("[data-on-load]").forEach((function(e){var t=parseInt(e.dataset.delay)||0;setTimeout((function(){var t=new CustomEvent("hyp-load",{bubbles:!0,detail:{target:A(e)}});e.dispatchEvent(t)}),t)}))}function A(e){var t=function(e){var t,n=e.closest("[data-target]");return(null==n?void 0:n.dataset.target)||(null===(t=e.closest("[id]"))||void 0===t?void 0:t.id)}(e),n=document.getElementById(t);if(n)return n;console.error("Cannot find target: ",e)}var I,N=function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{u(r.next(e))}catch(e){i(e)}}function c(e){try{u(r.throw(e))}catch(e){i(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,c)}u((r=r.apply(e,t||[])).next())}))},P=function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(c){return function(u){return function(c){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,c[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&c[0]?r.return:c[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,c[1])).done)return o;switch(r=0,o&&(c=[2&c[0],o.value]),c[0]){case 0:case 1:o=c;break;case 4:return a.label++,{value:c[1],done:!1};case 5:a.label++,r=c[1],c=[0];continue;case 7:c=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==c[0]&&2!==c[0])){a=0;continue}if(3===c[0]&&(!o||c[1]>o[0]&&c[1]<o[3])){a.label=c[1];break}if(6===c[0]&&a.label<o[1]){a.label=o[1],o=c;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(c);break}o[2]&&a.ops.pop(),a.trys.pop();continue}c=t.call(e,a)}catch(e){c=[6,e],r=0}finally{n=o=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}([c,u])}}};console.log("Hyperbole 0.4.3c");var j=new Set;function M(e){return N(this,void 0,void 0,(function(){function t(e){return N(this,void 0,void 0,(function(){var t,n,r;return P(this,(function(o){switch(o.label){case 0:return[4,fetch(e.url,{method:"POST",headers:{Accept:"text/html","Content-Type":"application/x-www-form-urlencoded"},body:e.form,redirect:"manual"})];case 1:return(t=o.sent()).headers.get("location")?(console.log("Found Redirect",t.headers.get("location")),window.location.href=t.headers.get("location"),[2]):t.headers.get("location")?(window.location.href=t.headers.get("location"),[2]):(t.headers.has("set-query")&&w(t.headers.get("set-query")),t.ok?[3,3]:((n=new Error).name="Fetch Error "+t.status,[4,t.text()]));case 2:throw r=o.sent(),n.message=r,n;case 3:return[2,t.text()]}}))}))}return P(this,(function(n){return B.isConnected?[2,B.sendAction(e)]:[2,t(e)]}))}))}function R(e){return N(this,void 0,void 0,(function(){var t;return P(this,(function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,M(e)];case 1:return[2,n.sent()];case 2:throw t=n.sent(),document.body.innerHTML=(o=[".hyp-error {background-color:#DB3524; color:white; padding: 10px}",".hyp-details {padding: 10px}"],i="<div class='hyp-error'>".concat((r=t).name,"</div>"),a="<div class='hyp-details'>".concat(r.message,"</div>"),["<style>"+o.join("\n")+"</style>",i,a].join("\n")),t;case 3:return[2]}var r,o,i,a}))}))}function q(e,t,n){return N(this,void 0,void 0,(function(){var r,o,i,a,c;return P(this,(function(u){switch(u.label){case 0:return r=setTimeout((function(){e.classList.add("hyp-loading")}),100),[4,R(S(e.id,t,n))];case 1:return s=u.sent(),d=(l=(new DOMParser).parseFromString(s,"text/html")).querySelector("style"),(o={content:l.querySelector("div"),css:d}).css&&o.content?(function(e){for(var t=0,n=e.sheet.cssRules;t<n.length;t++){var r=n[t];0==j.has(r.cssText)&&(I.sheet.insertRule(r.cssText),j.add(r.cssText))}}(o.css),i=m(o.content),a=m(e),y(i,a),(c=document.getElementById(e.id))?T(c):console.warn("Target Missing: ",e.id),clearTimeout(r),e.classList.remove("hyp-loading"),[2]):(console.error("Empty Response",o),[2])}var s,l,d}))}))}document.addEventListener("DOMContentLoaded",(function(){var e;I=document.querySelector("style"),e=function(e,t){return N(this,void 0,void 0,(function(){return P(this,(function(n){return q(e,t),[2]}))}))},document.addEventListener("hyp-load",(function(t){var n=t.target.dataset.onLoad,r=t.detail.target;e(r,n)})),T(document.body),document.addEventListener("click",(function(e){var t=e.target.closest("[data-on-click]");t&&(e.preventDefault(),function(e,t){N(this,void 0,void 0,(function(){return P(this,(function(n){return q(e,t),[2]}))}))}(A(t),t.dataset.onClick))})),document.addEventListener("dblclick",(function(e){var t=e.target.closest("[data-on-dblclick]");t&&(e.preventDefault(),function(e,t){N(this,void 0,void 0,(function(){return P(this,(function(n){return q(e,t),[2]}))}))}(A(t),t.dataset.onDblclick))})),L("Keydown",(function(e,t){return N(this,void 0,void 0,(function(){return P(this,(function(n){return q(e,t),[2]}))}))})),L("Keyup",(function(e,t){return N(this,void 0,void 0,(function(){return P(this,(function(n){return q(e,t),[2]}))}))})),document.addEventListener("submit",(function(e){var t=e.target;if(null==t?void 0:t.dataset.onSubmit){e.preventDefault();var n=A(t),r=new FormData(t);!function(e,t,n){N(this,void 0,void 0,(function(){return P(this,(function(r){return q(e,t,n),[2]}))}))}(n,t.dataset.onSubmit,r)}else console.error("Missing onSubmit: ",t)})),document.addEventListener("change",(function(e){var t=e.target.closest("[data-on-change]");t&&(e.preventDefault(),t.value?function(e,t){N(this,void 0,void 0,(function(){return P(this,(function(n){return q(e,t),[2]}))}))}(A(t),t.value):console.error("Missing input value:",t))})),document.addEventListener("input",(function(e){var t=e.target.closest("[data-on-input]");if(t){var n=parseInt(t.dataset.delay)||250;if(n<250&&console.warn("Input delay < 100 can result in poor performance."),null==t?void 0:t.dataset.onInput){e.preventDefault();var r=A(t);t.debouncedCallback||(t.debouncedCallback=D((function(){var e=function(e,t){return e.replace(/%HYP-INP%/,t.replace(/\\/g,"\\\\").replace(/"/g,'\\"'))}(t.dataset.onInput,t.value);!function(e,t){N(this,void 0,void 0,(function(){return P(this,(function(n){return console.log("INPUT",e.id,t),q(e,t),[2]}))}))}(r,e)}),n)),t.debouncedCallback()}else console.error("Missing onInput: ",t)}}))}));var B=new E;B.connect()})()})();+(()=>{var e={296:e=>{function t(e,t=100,n={}){if("function"!=typeof e)throw new TypeError(`Expected the first parameter to be a function, got \`${typeof e}\`.`);if(t<0)throw new RangeError("`wait` must not be negative.");const{immediate:r}="boolean"==typeof n?{immediate:n}:n;let o,i,a,u,c;function s(){const t=o,n=i;return o=void 0,i=void 0,c=e.apply(t,n),c}function l(){const e=Date.now()-u;e<t&&e>=0?a=setTimeout(l,t-e):(a=void 0,r||(c=s()))}const d=function(...e){if(o&&this!==o&&Object.getPrototypeOf(this)===Object.getPrototypeOf(o))throw new Error("Debounced method called with different contexts of the same prototype.");o=this,i=e,u=Date.now();const n=r&&!a;return a||(a=setTimeout(l,t)),n&&(c=s()),c};return Object.defineProperty(d,"isPending",{get:()=>void 0!==a}),d.clear=()=>{a&&(clearTimeout(a),a=void 0)},d.flush=()=>{a&&d.trigger()},d.trigger=()=>{c=s(),d.clear()},d}e.exports.debounce=t,e.exports=t},147:e=>{"use strict";e.exports=JSON.parse('{"name":"web-ui","version":"0.5.0","description":"Development -----------","main":"index.js","directories":{"client":"client"},"scripts":{"build":"npx webpack"},"author":"","license":"ISC","devDependencies":{"ts-loader":"^9.4.1","typescript":"^4.8.3","uglify":"^0.1.5","webpack":"^5.88.2","webpack-cli":"^4.10.0"},"dependencies":{"omdomdom":"^0.3.2","debounce":"^2.2.0"}}')}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var i=t[r]={exports:{}};return e[r](i,i.exports,n),i.exports}(()=>{"use strict";var e=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t=function(e,t){var n=e.length,r=-1;if(n)for(;++r<n&&!1!==t(e[r],r););},r=function(e,t,n){return e.node.insertBefore(t.node,n)};function o(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,o,i,a,u=[],c=!0,s=!1;try{if(i=(n=n.call(e)).next,0===t){if(Object(n)!==n)return;c=!1}else for(;!(c=(r=i.call(n)).done)&&(u.push(r.value),u.length!==t);c=!0);}catch(e){s=!0,o=e}finally{try{if(!c&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(s)throw o}}return u}}(e,t)||function(e,t){if(e){if("string"==typeof e)return i(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?i(e,t):void 0}}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}var a="string",u="number",c="boolean",s={},l=function(e,t,n){return{attrName:e,propName:t,type:n}};t([["style","cssText"],["class","className"]],(function(e){var t=o(e,2),n=t[0],r=t[1];s[n]=l(n,r||n,a)})),t(["autofocus","draggable","hidden","checked","multiple","muted","selected"],(function(e){s[e]=l(e,e,c)})),t([["tabindex","tabIndex"]],(function(e){var t=o(e,2),n=t[0],r=t[1];s[n]=l(n,r,u)}));var d="xlink:",f="http://www.w3.org/1999/xlink",v="xml:",p="http://www.w3.org/XML/1998/namespace",h=function(e,t,n,r){switch(t){case a:n===s.style.propName?e.style[n]=null===r?"":r:e[n]=null===r?"":r;break;case u:if(null===r){var o=n.toLowerCase();e.removeAttribute(o)}else if("0"===r)e[n]=0;else if("-1"===r)e[n]=-1;else{var i=parseInt(r,10);isNaN(i)||(e[n]=i)}break;case c:["","true"].indexOf(r)<0?e[n]=!1:e[n]=!0}},y=function n(o,i,u){if(o&&i){u=u||i.node.parentNode;var c=o.content&&o.content!==i.content;if(o.type!==i.type||c)return u.replaceChild(o.node,i.node),function(e,t){for(var n in e)t[n]=e[n]}(o,i);(function(n,r){var o=[],i={};for(var u in r.attributes){var c=r.attributes[u],l=n.attributes[u];c!==l&&void 0===l&&o.push(u)}for(var y in n.attributes){var m=r.attributes[y],b=n.attributes[y];m!==b&&void 0!==b&&(i[y]=b)}!function(n,r){t(r,(function(t){if(e(s,t)){var r=s[t];h(n.node,r.type,r.propName,null)}else t in n.node&&h(n.node,a,t,null),n.node.removeAttribute(t);delete n.attributes[t]}))}(r,o),function(t,n){for(var r in n){var o=n[r];if(t.attributes[r]=o,e(s,r)){var i=s[r];h(t.node,i.type,i.propName,o)}else r.startsWith(d)?t.node.setAttributeNS(f,r,o):r.startsWith(v)?t.node.setAttributeNS(p,r,o):(r in t.node&&h(t.node,a,r,o),t.node.setAttribute(r,o||""))}}(r,i)})(o,i),function(n,o,i){var a=n.children.length,u=o.children.length;if(a||u){var c=function(n){var r={};for(var o in t(n,(function(t){var n=t.attributes["data-key"];(function(t,n){return!(!n||e(t,n)&&(console.warn("[OmDomDom]: Children with duplicate keys detected. Children with duplicate keys will be skipped, resulting in dropped node references. Keys must be unique and non-indexed."),1))})(r,n)&&(r[n]=t)})),r)return r}(o.children),s=Array(a);t(n.children,void 0!==c?function(e,t){var n=o.node.childNodes,a=e.attributes["data-key"];if(Object.prototype.hasOwnProperty.call(c,a)){var u=c[a];Array.prototype.indexOf.call(n,u.node)!==t&&r(o,u,n[t]),s[t]=u,delete c[a],i(e,s[t])}else r(o,e,n[t]),s[t]=e}:function(e,t){var n=o.children[t];void 0!==n?(i(e,n),s[t]=n):(o.node.appendChild(e.node),s[t]=e)}),o.children=s;var l=o.node.childNodes.length,d=l-a;if(d>0)for(;d>0;)o.node.removeChild(o.node.childNodes[l-1]),l--,d--}}(o,i,n)}},m=function n(r){var o,i=arguments.length>1&&void 0!==arguments[1]&&arguments[1];"string"==typeof r&&(o=r.trim().replace(/\s+</g,"<").replace(/>\s+/g,">"),r=(new DOMParser).parseFromString(o,"text/html").body);var a="BODY"===r.tagName,u=r.childNodes,c=u?u.length:0;if(a){if(c>1)throw new Error("[OmDomDom]: Your element should not have more than one root node.");if(0===c)throw new Error("[OmDomDom]: Your element should have at least one root node.");return n(u[0])}var l=3===r.nodeType?"text":8===r.nodeType?"comment":r.tagName.toLowerCase(),d=i||"svg"===l,f=1===r.nodeType?function(t){var n=function(t){return Array.prototype.reduce.call(t.attributes,(function(t,n){return e(s,n.name)||(t[n.name]=n.value),t}),{})}(t);return function(e,t){for(var n in s){var r=s[n].propName,o=e.getAttribute(n);n===s.style.attrName?t[n]=e.style[r]:"string"==typeof o&&(t[n]=o)}}(t,n),n}(r):{},v=c>0?null:r.textContent,p=Array(c);return t(u,(function(e,t){p[t]=n(e,d)})),{type:l,attributes:f,children:p,content:v,node:r,isSVGContext:d}};function b(e){if(e){var t=new URLSearchParams;return e.forEach((function(e,n){t.append(n,e)})),t}}function w(e){return g(e.trim().split("\n")).metadata}function g(e){var t,n,r,o,i,a,u,c=function(e,t){for(var n=[],r=0,o=t;r<o.length;r++){var i=e(o[r]);if(!i)break;n.push(i)}return n}(k,e),s=e.slice(c.length);return{metadata:(t=c,u=null===(n=t.find((function(e){return"RequestId"==e.key})))||void 0===n?void 0:n.value,{cookies:t.filter((function(e){return"Cookie"==e.key})).map((function(e){return e.value})),redirect:null===(r=t.find((function(e){return"Redirect"==e.key})))||void 0===r?void 0:r.value,error:null===(o=t.find((function(e){return"Error"==e.key})))||void 0===o?void 0:o.value,query:null===(i=t.find((function(e){return"Query"==e.key})))||void 0===i?void 0:i.value,events:t.filter((function(e){return"Event"==e.key})).map((function(e){return function(e){var t=E(e),n=t[0],r=t[1];return{name:n,detail:JSON.parse(r)}}(e.value)})),actions:t.filter((function(e){return"Trigger"==e.key})).map((function(e){return function(e){var t=E(e);return[t[0],t[1]]}(e.value)})),pageTitle:null===(a=t.find((function(e){return"PageTitle"==e.key})))||void 0===a?void 0:a.value,requestId:u}),rest:s}}function E(e){var t=e.indexOf("|");if(-1===t){var n=new Error("Bad Encoding, Expected Segment");throw n.message=e,n}return[e.slice(0,t),e.slice(t+1)]}function k(e){var t=e.match(/^(\w+)\: (.*)$/);if(t)return{key:t[1],value:t[2]}}function x(e,t){return e+" "+function(e){return""==e?"|":e.replace(/_/g,"\\_").replace(/\s+/g,"_")}(t)}var I,S=(I=function(e,t){return I=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},I(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}I(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),C=function(e){function t(t,n,r){var o=e.call(this,n)||this;return o.viewId=t,o.name="Fetch Error",o.body=r,o}return S(t,e),t}(Error),A=function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function u(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,u)}c((r=r.apply(e,t||[])).next())}))},T=function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(u){return function(c){return function(u){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,u[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&u[0]?r.return:u[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,u[1])).done)return o;switch(r=0,o&&(u=[2&u[0],o.value]),u[0]){case 0:case 1:o=u;break;case 4:return a.label++,{value:u[1],done:!1};case 5:a.label++,r=u[1],u=[0];continue;case 7:u=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==u[0]&&2!==u[0])){a=0;continue}if(3===u[0]&&(!o||u[1]>o[0]&&u[1]<o[3])){a.label=u[1];break}if(6===u[0]&&a.label<o[1]){a.label=o[1],o=u;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(u);break}o[2]&&a.ops.pop(),a.trys.pop();continue}u=t.call(e,a)}catch(e){u=[6,e],r=0}finally{n=o=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,c])}}},O="https:"===window.location.protocol?"wss:":"ws:",q="".concat(O,"//").concat(window.location.host).concat(window.location.pathname),L=function(){function e(){this.reconnectDelay=0}return e.prototype.connect=function(e){var t=this;void 0===e&&(e=q);var n=new WebSocket(e);function r(e){console.log("Connection Error",e)}this.socket=n,n.addEventListener("error",r),n.addEventListener("open",(function(e){console.log("Websocket Connected"),t.hasEverConnected&&document.dispatchEvent(new Event("hyp-socket-reconnect")),t.isConnected=!0,t.hasEverConnected=!0,t.reconnectDelay=1e3,t.socket.removeEventListener("error",r),document.dispatchEvent(new Event("hyp-socket-connect"))})),n.addEventListener("close",(function(n){t.isConnected&&document.dispatchEvent(new Event("hyp-socket-disconnect")),t.isConnected=!1,t.hasEverConnected&&(console.log("Reconnecting in "+t.reconnectDelay/1e3+"s"),setTimeout((function(){return t.connect(e)}),t.reconnectDelay))}))},e.prototype.sendAction=function(e){return A(this,void 0,void 0,(function(){var t,n,r,o;return T(this,(function(i){switch(i.label){case 0:return t=function(e){var t,n;return[["|ACTION|","ViewId: "+e.viewId,"Action: "+e.action,"RequestId: "+e.requestId].join("\n"),(n=e.meta,n.map((function(e){return e.key+": "+e.value})).join("\n"))].join("\n")+((t=e.form)?"\n\n"+t:"")}(e),[4,this.fetch(e.requestId,e.viewId,t)];case 1:return n=i.sent(),r=n.metadata,o=n.rest,[2,{meta:r,body:o.join("\n")}]}}))}))},e.prototype.fetch=function(e,t,n){return A(this,void 0,void 0,(function(){return T(this,(function(r){switch(r.label){case 0:return this.sendMessage(n),[4,this.waitMessage(e,t)];case 1:return[2,r.sent()]}}))}))},e.prototype.sendMessage=function(e){this.socket.send(e)},e.prototype.waitMessage=function(e,t){return A(this,void 0,void 0,(function(){var n=this;return T(this,(function(r){return[2,new Promise((function(r,o){var i=function(a){var u=g(a.data.split("\n").slice(1)),c=u.metadata;c.requestId?c.requestId==e&&(n.socket.removeEventListener("message",i),c.cookies.forEach((function(e){document.cookie=e})),c.error?o(new C(t,c.error,u.rest.join("\n"))):r(u)):console.error("Missing RequestId!",c,a.data)};n.socket.addEventListener("message",i),n.socket.addEventListener("error",o)}))]}))}))},e.prototype.disconnect=function(){this.socket.close()},e}(),D=n(296);function N(e,t){document.addEventListener(e.toLowerCase(),(function(n){var r=n.target,o="on"+e+n.key,i=r.dataset[o];i&&(n.preventDefault(),t(R(r),i))}))}function M(e,t){document.addEventListener(e,(function(n){var r=n.target.closest("[data-on"+e+"]");if(r){n.preventDefault();var o=R(r);t(o,r.dataset["on"+e])}}))}function j(e){e.querySelectorAll("[data-onload]").forEach((function(e){var t=parseInt(e.dataset.delay)||0,n=e.dataset.onload;setTimeout((function(){var t=R(e);if(e.dataset.onload==n){var r=new CustomEvent("hyp-load",{bubbles:!0,detail:{target:t,onLoad:n}});e.dispatchEvent(r)}}),t)}))}function P(e){e.querySelectorAll("[data-onmouseenter]").forEach((function(e){var t=e.dataset.onmouseenter,n=R(e);e.onmouseenter=function(){var r=new CustomEvent("hyp-mouseenter",{bubbles:!0,detail:{target:n,onMouseEnter:t}});e.dispatchEvent(r)}}))}function H(e){e.querySelectorAll("[data-onmouseleave]").forEach((function(e){var t=e.dataset.onmouseleave,n=R(e);e.onmouseleave=function(){var r=new CustomEvent("hyp-mouseleave",{bubbles:!0,detail:{target:n,onMouseLeave:t}});e.dispatchEvent(r)}}))}function R(e){var t=function(e){var t,n=e.closest("[data-target]");return(null==n?void 0:n.dataset.target)||(null===(t=e.closest("[id]"))||void 0===t?void 0:t.id)}(e),n=document.getElementById(t);if(n)return n;console.error("Cannot find target: ",t,e)}var _=function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function u(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,u)}c((r=r.apply(e,t||[])).next())}))},V=function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(u){return function(c){return function(u){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,u[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&u[0]?r.return:u[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,u[1])).done)return o;switch(r=0,o&&(u=[2&u[0],o.value]),u[0]){case 0:case 1:o=u;break;case 4:return a.label++,{value:u[1],done:!1};case 5:a.label++,r=u[1],u=[0];continue;case 7:u=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==u[0]&&2!==u[0])){a=0;continue}if(3===u[0]&&(!o||u[1]>o[0]&&u[1]<o[3])){a.label=u[1];break}if(6===u[0]&&a.label<o[1]){a.label=o[1],o=u;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(u);break}o[2]&&a.ops.pop(),a.trys.pop();continue}u=t.call(e,a)}catch(e){u=[6,e],r=0}finally{n=o=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,c])}}};function B(e){return _(this,void 0,void 0,(function(){var t,n,r,o,i,a;return V(this,(function(u){switch(u.label){case 0:return t=window.location.href,[4,fetch(t,{method:"POST",headers:{Accept:"text/html","Content-Type":"application/x-www-form-urlencoded","Hyp-RequestId":e.requestId,"Hyp-ViewId":e.viewId,"Hyp-Action":e.action},body:e.form,redirect:"manual"})];case 1:return[4,(n=u.sent()).text()];case 2:if(r=u.sent(),o=function(e){var t=g(e.split("\n").slice(1));return{metadata:t.metadata,rest:t.rest.slice(2)}}(r),i=o.metadata,a=o.rest,!n.ok)throw new C(e.viewId,r,r);return[2,{meta:i,body:a.join("\n")}]}}))}))}var G,U=function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function u(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,u)}c((r=r.apply(e,t||[])).next())}))},W=function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(u){return function(c){return function(u){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,u[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&u[0]?r.return:u[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,u[1])).done)return o;switch(r=0,o&&(u=[2&u[0],o.value]),u[0]){case 0:case 1:o=u;break;case 4:return a.label++,{value:u[1],done:!1};case 5:a.label++,r=u[1],u=[0];continue;case 7:u=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==u[0]&&2!==u[0])){a=0;continue}if(3===u[0]&&(!o||u[1]>o[0]&&u[1]<o[3])){a.label=u[1];break}if(6===u[0]&&a.label<o[1]){a.label=o[1],o=u;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(u);break}o[2]&&a.ops.pop(),a.trys.pop();continue}u=t.call(e,a)}catch(e){u=[6,e],r=0}finally{n=o=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,c])}}},F=n(147);console.log("Hyperbole "+F.version);var J=new Set;function K(e){return U(this,void 0,void 0,(function(){return W(this,(function(t){return Z.isConnected?[2,Z.sendAction(e)]:[2,B(e)]}))}))}function Y(e,t,n){return U(this,void 0,void 0,(function(){var r,o,i,a,u,c,s,l,d,f;return W(this,(function(v){switch(v.label){case 0:if(void 0===e)return console.error("Undefined HyperView!"),[2];if(void 0===t)return console.error("Undefined Action!",e,"this is a bug, please report: https://github.com/seanhess/hyperbole"),[2];if(r=setTimeout((function(){e.classList.add("hyp-loading")}),100),o=Math.random().toString(36).substring(2,8),i=function(e,t,n,r){return{viewId:e,action:t,requestId:n,meta:[{key:"Cookie",value:decodeURI(document.cookie)},{key:"Query",value:window.location.search}],form:b(r)}}(e.id,t,o,n),e.dataset.requestId)return console.warn("Action overlaps with active request ("+e.dataset.requestId+")",t),[2];e.dataset.requestId=o,v.label=1;case 1:return v.trys.push([1,3,,4]),[4,K(i)];case 2:if((a=v.sent()).meta.requestId!=e.dataset.requestId)throw(u=new Error).name="Concurrency Error",u.message="Stale Action ("+o+"):"+t,u;return delete e.dataset.requestId,$(a.meta)?[2]:(c=function(e){var t=(new DOMParser).parseFromString(e,"text/html"),n=t.querySelector("style");return{content:t.querySelector("div"),css:n}}(a.body),c.content?(function(e){if(e)for(var t=0,n=e.sheet.cssRules;t<n.length;t++){var r=n[t];0==J.has(r.cssText)&&(G.sheet.insertRule(r.cssText),J.add(r.cssText))}}(c.css),s=m(e),(l=m(c.content)).attributes=s.attributes,y(l,s),z(d=document.getElementById(e.id)),d?(Q(a.meta,d),j(d),P(d),H(d),function(e){var t=e.querySelector("[autofocus]");(null==t?void 0:t.focus)&&t.focus(),e.querySelectorAll("input[value]").forEach((function(e){var t=e.getAttribute("value");void 0!==t&&(e.value=t)})),e.querySelectorAll("input[type=checkbox]").forEach((function(e){var t="True"==e.dataset.checked;e.checked=t}))}(d),X(d)):console.warn("Target Missing: ",e.id),[3,4]):(console.error("Empty Response!",a.body),[2]));case 3:return f=v.sent(),console.error("Caught Error in HyperView ("+e.id+"):\n",f),e.innerHTML=f.body||"<div style='background:red;color:white;padding:10px'>Hyperbole Internal Error</div>",[3,4];case 4:return clearTimeout(r),e.classList.remove("hyp-loading"),[2]}}))}))}function $(e){if(e.redirect)return window.location.href=e.redirect,!0;null!=e.query&&function(e){if(e!=function(){var e=window.location.search;return e.startsWith("?")?e.substring(1):e}()){""!=e&&(e="?"+e);var t=location.pathname+e;window.history.replaceState({},"",t)}}(e.query),null!=e.pageTitle&&(document.title=e.pageTitle)}function Q(e,t){for(var n=0,r=e.events;n<r.length;n++){var o=r[n];setTimeout((function(){var e=new CustomEvent(o.name,{bubbles:!0,detail:o.detail});(t||document).dispatchEvent(e)}),10)}e.actions.forEach((function(e){var t=e[0],n=e[1];setTimeout((function(){var e=window.Hyperbole.hyperView(t);e&&Y(e,n)}),10)}))}function X(e){e.querySelectorAll("[id]").forEach((function(t){t.runAction=function(e){Y(this,e)}.bind(t),z(e)}))}function z(e){var t=new Event("hyp-content",{bubbles:!0});e.dispatchEvent(t)}document.addEventListener("DOMContentLoaded",(function(){var e,t=w(document.getElementById("hyp.metadata").innerText);$(t),Q(t),G=document.querySelector("style"),e=function(e,t){return U(this,void 0,void 0,(function(){return W(this,(function(n){return Y(e,t),[2]}))}))},document.addEventListener("hyp-load",(function(t){var n=t.detail.onLoad,r=t.detail.target;e(r,n)})),document.addEventListener("hyp-mouseenter",(function(t){var n=t.detail.onMouseEnter,r=t.detail.target;e(r,n)})),document.addEventListener("hyp-mouseleave",(function(t){var n=t.detail.onMouseLeave,r=t.detail.target;e(r,n)})),j(document.body),P(document.body),H(document.body),X(document.body),M("click",(function(e,t){return U(this,void 0,void 0,(function(){return W(this,(function(n){return Y(e,t),[2]}))}))})),M("dblclick",(function(e,t){return U(this,void 0,void 0,(function(){return W(this,(function(n){return Y(e,t),[2]}))}))})),N("Keydown",(function(e,t){return U(this,void 0,void 0,(function(){return W(this,(function(n){return Y(e,t),[2]}))}))})),N("Keyup",(function(e,t){return U(this,void 0,void 0,(function(){return W(this,(function(n){return Y(e,t),[2]}))}))})),document.addEventListener("submit",(function(e){var t=e.target;if(null==t?void 0:t.dataset.onsubmit){e.preventDefault();var n=R(t),r=new FormData(t);!function(e,t,n){U(this,void 0,void 0,(function(){return W(this,(function(r){return Y(e,t,n),[2]}))}))}(n,t.dataset.onsubmit,r)}else console.error("Missing onSubmit: ",t)})),document.addEventListener("change",(function(e){var t=e.target.closest("[data-onchange]");t&&(e.preventDefault(),null!=t.value?function(e,t){U(this,void 0,void 0,(function(){return W(this,(function(n){return console.log("CHANGE",e.id,"("+t+")"),Y(e,t),[2]}))}))}(R(t),x(t.dataset.onchange,t.value)):console.error("Missing input value:",t))})),document.addEventListener("input",(function(e){var t=e.target.closest("[data-oninput]");if(t){var n=parseInt(t.dataset.delay)||250;if(n<250&&console.warn("Input delay < 250 can result in poor performance."),null==t?void 0:t.dataset.oninput){e.preventDefault();var r=R(t);t.debouncedCallback||(t.debouncedCallback=D((function(){var e=x(t.dataset.oninput,t.value);!function(e,t){U(this,void 0,void 0,(function(){return W(this,(function(n){return Y(e,t),[2]}))}))}(r,e)}),n)),t.debouncedCallback()}else console.error("Missing onInput: ",t)}}))}));var Z=new L;Z.connect(),window.Hyperbole={runAction:Y,parseMetadata:w,action:function(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n];return e+t.reduce((function(e,t){return e+" "+JSON.stringify(t)}),"")},hyperView:function(e){var t=document.getElementById(e);if(null==t?void 0:t.runAction)return t;console.error("Element id="+e+" was not a HyperView")},socket:Z}})()})(); //# sourceMappingURL=hyperbole.js.map
+ client/dist/hyperbole.js.map view
@@ -0,0 +1,1 @@+{"version":3,"file":"hyperbole.js","mappings":";qBAAA,SAASA,EAASC,EAAWC,EAAO,IAAKC,EAAU,CAAC,GACnD,GAAyB,mBAAdF,EACV,MAAM,IAAIG,UAAU,+DAA+DH,QAGpF,GAAIC,EAAO,EACV,MAAM,IAAIG,WAAW,gCAItB,MAAM,UAACC,GAAgC,kBAAZH,EAAwB,CAACG,UAAWH,GAAWA,EAE1E,IAAII,EACAC,EACAC,EACAC,EACAC,EAEJ,SAASC,IACR,MAAMC,EAAcN,EACdO,EAAgBN,EAItB,OAHAD,OAAgBQ,EAChBP,OAAkBO,EAClBJ,EAASV,EAAUe,MAAMH,EAAaC,GAC/BH,CACR,CAEA,SAASM,IACR,MAAMC,EAAOC,KAAKC,MAAQV,EAEtBQ,EAAOhB,GAAQgB,GAAQ,EAC1BT,EAAYY,WAAWJ,EAAOf,EAAOgB,IAErCT,OAAYM,EAEPT,IACJK,EAASC,KAGZ,CAEA,MAAMU,EAAY,YAAaC,GAC9B,GACChB,GACGiB,OAASjB,GACTkB,OAAOC,eAAeF,QAAUC,OAAOC,eAAenB,GAEzD,MAAM,IAAIoB,MAAM,0EAGjBpB,EAAgBiB,KAChBhB,EAAkBe,EAClBb,EAAYS,KAAKC,MAEjB,MAAMQ,EAAUtB,IAAcG,EAU9B,OARKA,IACJA,EAAYY,WAAWJ,EAAOf,IAG3B0B,IACHjB,EAASC,KAGHD,CACR,EA+BA,OA7BAc,OAAOI,eAAeP,EAAW,YAAa,CAC7CQ,IAAG,SACmBf,IAAdN,IAITa,EAAUS,MAAQ,KACZtB,IAILuB,aAAavB,GACbA,OAAYM,EAAS,EAGtBO,EAAUW,MAAQ,KACZxB,GAILa,EAAUY,SAAS,EAGpBZ,EAAUY,QAAU,KACnBvB,EAASC,IAETU,EAAUS,OAAO,EAGXT,CACR,CAGAa,EAAOC,QAAQpC,SAAWA,EAE1BmC,EAAOC,QAAUpC,saCrGbqC,EAA2B,CAAC,EAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBxB,IAAjByB,EACH,OAAOA,EAAaJ,QAGrB,IAAID,EAASE,EAAyBE,GAAY,CAGjDH,QAAS,CAAC,GAOX,OAHAK,EAAoBF,GAAUJ,EAAQA,EAAOC,QAASE,GAG/CH,EAAOC,OACf,oBCjBA,IACIM,EAAc,SAAqBC,EAAKC,GAC1C,OAAOnB,OAAOoB,UAAUC,eAAeC,KAAKJ,EAAKC,EACnD,EASII,EAAU,SAAiBC,EAAOC,GACpC,IAAIC,EAASF,EAAME,OACfC,GAAO,EACX,GAAKD,EACL,OAASC,EAAMD,IACe,IAAxBD,EAAGD,EAAMG,GAAMA,KAEvB,EAaIC,EAAe,SAAsBC,EAAQC,EAAOC,GACtD,OAAOF,EAAOG,KAAKJ,aAAaE,EAAME,KAAMD,EAC9C,EAyCA,SAASE,EAAeC,EAAKC,GAC3B,OAEF,SAAyBD,GACvB,GAAIE,MAAMC,QAAQH,GAAM,OAAOA,CACjC,CAJSI,CAAgBJ,IA5BzB,SAA+BA,EAAKC,GAClC,IAAII,EAAK,MAAQL,EAAM,KAAO,oBAAsBM,QAAUN,EAAIM,OAAOC,WAAaP,EAAI,cAC1F,GAAI,MAAQK,EAAI,CACd,IAAIG,EACFC,EACAC,EACAC,EACAC,EAAO,GACPC,GAAK,EACLC,GAAK,EACP,IACE,GAAIJ,GAAML,EAAKA,EAAGjB,KAAKY,IAAMe,KAAM,IAAMd,EAAG,CAC1C,GAAInC,OAAOuC,KAAQA,EAAI,OACvBQ,GAAK,CACP,MAAO,OAASA,GAAML,EAAKE,EAAGtB,KAAKiB,IAAKW,QAAUJ,EAAKK,KAAKT,EAAGU,OAAQN,EAAKpB,SAAWS,GAAIY,GAAK,GAClG,CAAE,MAAOM,GACPL,GAAK,EAAIL,EAAKU,CAChB,CAAE,QACA,IACE,IAAKN,GAAM,MAAQR,EAAGe,SAAWT,EAAKN,EAAGe,SAAUtD,OAAO6C,KAAQA,GAAK,MACzE,CAAE,QACA,GAAIG,EAAI,MAAML,CAChB,CACF,CACA,OAAOG,CACT,CACF,CAEiCS,CAAsBrB,EAAKC,IAK5D,SAAqCqB,EAAGC,GACtC,GAAKD,EAAL,CACA,GAAiB,iBAANA,EAAgB,OAAOE,EAAkBF,EAAGC,GACvD,IAAIE,EAAI3D,OAAOoB,UAAUwC,SAAStC,KAAKkC,GAAGK,MAAM,GAAI,GAEpD,MADU,WAANF,GAAkBH,EAAEM,cAAaH,EAAIH,EAAEM,YAAYC,MAC7C,QAANJ,GAAqB,QAANA,EAAoBvB,MAAM4B,KAAKR,GACxC,cAANG,GAAqB,2CAA2CM,KAAKN,GAAWD,EAAkBF,EAAGC,QAAzG,CALc,CAMhB,CAZkES,CAA4BhC,EAAKC,IAkBnG,WACE,MAAM,IAAIxD,UAAU,4IACtB,CApByGwF,EACzG,CAYA,SAAST,EAAkBxB,EAAKkC,IACnB,MAAPA,GAAeA,EAAMlC,EAAIR,UAAQ0C,EAAMlC,EAAIR,QAC/C,IAAK,IAAIS,EAAI,EAAGkC,EAAO,IAAIjC,MAAMgC,GAAMjC,EAAIiC,EAAKjC,IAAKkC,EAAKlC,GAAKD,EAAIC,GACnE,OAAOkC,CACT,CAKA,IAAIC,EACM,SADNA,EAEG,SAFHA,EAGI,UAEJC,EAAgB,CAAC,EACjBC,EAAe,SAAsBC,EAAUC,EAAUC,GAC3D,MAAO,CACLF,SAAUA,EACVC,SAAUA,EACVC,KAAMA,EAEV,EAEApD,EADkB,CAAC,CAAC,QAAS,WAAY,CAAC,QAAS,eAC9B,SAAUqD,GAC7B,IAAIC,EAAQ5C,EAAe2C,EAAM,GAC/BE,EAAOD,EAAM,GACbE,EAAWF,EAAM,GACnBN,EAAcO,GAAQN,EAAaM,EAAMC,GAAYD,EAAMR,EAC7D,IAEA/C,EADmB,CAAC,YAAa,YAAa,SAAU,UAAW,WAAY,QAAS,aAClE,SAAUuD,GAC9BP,EAAcO,GAAQN,EAAaM,EAAMA,EAAMR,EACjD,IAEA/C,EADmB,CAAC,CAAC,WAAY,cACX,SAAUyD,GAC9B,IAAIC,EAAQhD,EAAe+C,EAAO,GAChCF,EAAOG,EAAM,GACbF,EAAWE,EAAM,GACnBV,EAAcO,GAAQN,EAAaM,EAAMC,EAAUT,EACrD,IACA,IAAIY,EAEQ,SAFRA,EAGU,+BAHVA,EAMQ,OANRA,EAOU,uCAIVC,EAAc,SAAqBC,EAAST,EAAMxD,EAAMiC,GAC1D,OAAQuB,GACN,KAAKL,EAEGnD,IAASoD,EAAcc,MAAMX,SAE7BU,EAAQC,MAAMlE,GADF,OAAViC,EACoB,GAEAA,EAGxBgC,EAAQjE,GADW,OAAViC,EACO,GAEAA,EAElB,MAEJ,KAAKkB,EAED,GAAc,OAAVlB,EAAgB,CAClB,IAAI0B,EAAO3D,EAAKmE,cAChBF,EAAQG,gBAAgBT,EAC1B,MAAO,GAAc,MAAV1B,EACTgC,EAAQjE,GAAQ,OACX,GAAc,OAAViC,EACTgC,EAAQjE,IAAS,MACZ,CACL,IAAIqE,EAASC,SAASrC,EAAO,IACxBsC,MAAMF,KACTJ,EAAQjE,GAAQqE,EAEpB,CACA,MAEJ,KAAKlB,EAEG,CAAC,GAAI,QAAQqB,QAAQvC,GAAS,EAChCgC,EAAQjE,IAAQ,EAEhBiE,EAAQjE,IAAQ,EAK1B,EAuIIyE,EAAQ,SAASA,EAAMC,EAAUC,EAAOC,GAC1C,GAAKF,GAAaC,EAAlB,CACAC,EAAWA,GAAYD,EAAM9D,KAAKgE,WAClC,IAAIC,EAAiBJ,EAASK,SAAWL,EAASK,UAAYJ,EAAMI,QACpE,GAAIL,EAASlB,OAASmB,EAAMnB,MAAQsB,EAElC,OADAF,EAASI,aAAaN,EAAS7D,KAAM8D,EAAM9D,MAjS7B,SAAqB6D,EAAUC,GAC/C,IAAK,IAAIf,KAAYc,EACnBC,EAAMf,GAAYc,EAASd,EAE/B,CA8RWqB,CAAYP,EAAUC,IA7EV,SAA0BD,EAAUC,GACzD,IAAIO,EAAoB,GACpBC,EAAoB,CAAC,EACzB,IAAK,IAAI7B,KAAYqB,EAAMS,WAAY,CACrC,IAAIC,EAAWV,EAAMS,WAAW9B,GAC5BgC,EAAYZ,EAASU,WAAW9B,GAChC+B,IAAaC,QACQ,IAAdA,GACTJ,EAAkBlD,KAAKsB,EAE3B,CACA,IAAK,IAAIiC,KAAab,EAASU,WAAY,CACzC,IAAII,EAAYb,EAAMS,WAAWG,GAC7BE,EAAaf,EAASU,WAAWG,GACjCC,IAAcC,QACQ,IAAfA,IACTN,EAAkBI,GAAaE,EAEnC,EAhFqB,SAA0Bd,EAAOS,GACtDhF,EAAQgF,GAAY,SAAU9B,GAC5B,GAAIxD,EAAYsD,EAAeE,GAAW,CACxC,IAAIoC,EAAiBtC,EAAcE,GACnCU,EAAYW,EAAM9D,KAAM6E,EAAelC,KAAMkC,EAAenC,SAAU,KACxE,MACMD,KAAYqB,EAAM9D,MACpBmD,EAAYW,EAAM9D,KAAMsC,EAAcG,EAAU,MAElDqB,EAAM9D,KAAKuD,gBAAgBd,UAEtBqB,EAAMS,WAAW9B,EAC1B,GACF,CAoEEqC,CAAiBhB,EAAOO,GAnEN,SAAuBP,EAAOS,GAChD,IAAK,IAAI9B,KAAY8B,EAAY,CAC/B,IAAInD,EAAQmD,EAAW9B,GAEvB,GADAqB,EAAMS,WAAW9B,GAAYrB,EACzBnC,EAAYsD,EAAeE,GAA/B,CACE,IAAIoC,EAAiBtC,EAAcE,GACnCU,EAAYW,EAAM9D,KAAM6E,EAAelC,KAAMkC,EAAenC,SAAUtB,EAExE,MACIqB,EAASsC,WAAW7B,GACtBY,EAAM9D,KAAKgF,eAAe9B,EAA0BT,EAAUrB,GAG5DqB,EAASsC,WAAW7B,GACtBY,EAAM9D,KAAKgF,eAAe9B,EAAwBT,EAAUrB,IAG1DqB,KAAYqB,EAAM9D,MACpBmD,EAAYW,EAAM9D,KAAMsC,EAAcG,EAAUrB,GAElD0C,EAAM9D,KAAKiF,aAAaxC,EAAUrB,GAAS,IAC7C,CACF,CA8CE8D,CAAcpB,EAAOQ,EACvB,EA0DEa,CAAiBtB,EAAUC,GAvD7B,SAAuBD,EAAUC,EAAOF,GACtC,IAAIwB,EAAyBvB,EAASwB,SAAS3F,OAC3C4F,EAAsBxB,EAAMuB,SAAS3F,OACzC,GAAK0F,GAA2BE,EAAhC,CACA,IAAIC,EAhQa,SAAsBF,GACvC,IAAIG,EAAM,CAAC,EAOX,IAAK,IAAIC,KANTlG,EAAQ8F,GAAU,SAAUvF,GAC1B,IAAI4F,EAAM5F,EAAMyE,WAvBO,aAIV,SAAoBiB,EAAKE,GACxC,SAAKA,GACDzG,EAAYuG,EAAKE,KACnBC,QAAQC,KAAK,+KACN,GAGX,EAaQC,CAAWL,EAAKE,KAClBF,EAAIE,GAAO5F,EAEf,IACc0F,EACZ,OAAOA,CAEX,CAqPoBM,CAAahC,EAAMuB,UACjCU,EAAe3F,MAAMgF,GAEvB7F,EAAQsE,EAASwB,cADC/H,IAAhBiI,EACyB,SAAUS,EAAerG,GAClD,IAAIsG,EAAanC,EAAM9D,KAAKiG,WACxBP,EAAMM,EAAczB,WAVL,YAWnB,GAAIvG,OAAOoB,UAAUC,eAAeC,KAAKiG,EAAaG,GAAM,CAC1D,IAAIQ,EAAaX,EAAYG,GACzBtF,MAAMhB,UAAUuE,QAAQrE,KAAK2G,EAAYC,EAAWlG,QAAUL,GAChEC,EAAakE,EAAOoC,EAAYD,EAAWtG,IAE7CoG,EAAapG,GAAOuG,SACbX,EAAYG,GACnB9B,EAAMoC,EAAeD,EAAapG,GACpC,MACEC,EAAakE,EAAOkC,EAAeC,EAAWtG,IAC9CoG,EAAapG,GAAOqG,CAExB,EAE2B,SAAUA,EAAerG,GAClD,IAAIwG,EAAarC,EAAMuB,SAAS1F,QACN,IAAfwG,GACTvC,EAAMoC,EAAeG,GACrBJ,EAAapG,GAAOwG,IAEpBrC,EAAM9D,KAAKoG,YAAYJ,EAAchG,MACrC+F,EAAapG,GAAOqG,EAExB,GAEFlC,EAAMuB,SAAWU,EACjB,IAAIM,EAAmBvC,EAAM9D,KAAKiG,WAAWvG,OACzC4G,EAAQD,EAAmBjB,EAC/B,GAAIkB,EAAQ,EACV,KAAOA,EAAQ,GACbxC,EAAM9D,KAAKuG,YAAYzC,EAAM9D,KAAKiG,WAAWI,EAAmB,IAChEA,IACAC,GAvCuD,CA0C7D,CAWEE,CAAc3C,EAAUC,EAAOF,EARA,CASjC,EAII6C,EAAS,SAASA,EAAOzG,GAC3B,IApSI0G,EAoSAC,EAAeC,UAAUlH,OAAS,QAAsBpC,IAAjBsJ,UAAU,IAAmBA,UAAU,GAC9D,iBAAT5G,IArSP0G,EAsSY1G,EAtSoB6G,OAAOC,QAAQ,QAAS,KAAKA,QAAQ,QAAS,KAsShF9G,GArSW,IAAI+G,WACIC,gBAAgBN,EAAoB,aAC1CO,MAqSf,IAAIC,EAA0B,SAAjBlH,EAAKmH,QACdlB,EAAajG,EAAKiG,WAClBmB,EAAgBnB,EAAaA,EAAWvG,OAAS,EACrD,GAAIwH,EAAQ,CACV,GAAIE,EAAgB,EAClB,MAAM,IAAIlJ,MAAM,qEACX,GAAsB,IAAlBkJ,EACT,MAAM,IAAIlJ,MAAM,gEAEhB,OAAOuI,EAAOR,EAAW,GAE7B,CACA,IAAItD,EAAyB,IAAlB3C,EAAKqH,SAAiB,OAA2B,IAAlBrH,EAAKqH,SAAiB,UAAYrH,EAAKmH,QAAQ7D,cACrFgE,EAAQX,GAAyB,QAAThE,EACxB4B,EAA+B,IAAlBvE,EAAKqH,SA7GJ,SAAuBjE,GACzC,IAAImB,EATkB,SAA2BnB,GACjD,OAAOhD,MAAMhB,UAAUmI,OAAOjI,KAAK8D,EAAQmB,YAAY,SAAUA,EAAY9B,GAI3E,OAHKxD,EAAYsD,EAAeE,EAASV,QACvCwC,EAAW9B,EAASV,MAAQU,EAASrB,OAEhCmD,CACT,GAAG,CAAC,EACN,CAEmBiD,CAAkBpE,GAEnC,OAvBsB,SAA2BA,EAASmB,GAC1D,IAAK,IAAI9B,KAAYF,EAAe,CAClC,IACIG,EADiBH,EAAcE,GACLC,SAC1B+E,EAAYrE,EAAQsE,aAAajF,GACjCA,IAAaF,EAAcc,MAAMZ,SACnC8B,EAAW9B,GAAYW,EAAQC,MAAMX,GACP,iBAAd+E,IAChBlD,EAAW9B,GAAYgF,EAE3B,CACF,CAWEE,CAAkBvE,EAASmB,GACpBA,CACT,CAyGyCqD,CAAc5H,GAAQ,CAAC,EAC1DkE,EAAUkD,EAAgB,EAAI,KAAOpH,EAAK6H,YAC1CxC,EAAWjF,MAAMgH,GAIrB,OAHA7H,EAAQ0G,GAAY,SAAUnG,EAAOH,GACnC0F,EAAS1F,GAAO8G,EAAO3G,EAAOwH,EAChC,IACO,CACL3E,KAAMA,EACN4B,WAAYA,EACZc,SAAUA,EACVnB,QAASA,EACTlE,KAAMA,EACN2G,aAAcW,EAElB,ECvVO,SAASQ,EAASC,GACvB,GAAKA,EAAL,CAEA,IAAMC,EAAS,IAAIC,gBAMnB,OAJAF,EAAKxI,SAAQ,SAAC6B,EAAOsE,GACnBsC,EAAOE,OAAOxC,EAAKtE,EACrB,IAEO4G,CARoB,CAS7B,CA0EO,SAASG,EAAcC,GAC5B,OAAOC,EAAcD,EAAMvB,OAAOyB,MAAM,OAAOC,QAEjD,CAEO,SAASF,EAAcG,GAC5B,IAxByBC,YAErBC,EAsBAC,ECpHC,SAA4BC,EAAiCJ,GAElE,IADA,IAAIK,EAAS,GACI,MAAAL,EAAA,eAAO,CAAnB,IACCM,EAAIF,EADG,MAEX,IAAIE,EAGF,MAFAD,EAAO1H,KAAK2H,GAKhB,OAAOD,CACT,CDyGsBE,CAAaC,EAAWR,GAIxCS,EAAOT,EAAM3G,MAAM8G,EAAMjJ,QAE7B,MAAO,CACL6I,UA/BuBE,EA+BFE,EA7BnBD,EAAgD,QAApC,EAAAD,EAAKS,MAAK,SAAAC,GAAK,MAAS,aAATA,EAAEzD,GAAF,WAAqB,eAAEtE,MAE/C,CACLgI,QAASX,EAAKY,QAAO,SAAAF,GAAK,MAAS,UAATA,EAAEzD,GAAF,IAAmBF,KAAI,SAAA2D,GAAK,OAAAA,EAAE/H,KAAF,IACtDkI,SAA6C,QAAnC,EAAAb,EAAKS,MAAK,SAAAC,GAAK,MAAS,YAATA,EAAEzD,GAAF,WAAoB,eAAEtE,MAC/CmI,MAAuC,QAAhC,EAAAd,EAAKS,MAAK,SAAAC,GAAK,MAAS,SAATA,EAAEzD,GAAF,WAAiB,eAAEtE,MACzCoI,MAAuC,QAAhC,EAAAf,EAAKS,MAAK,SAAAC,GAAK,MAAS,SAATA,EAAEzD,GAAF,WAAiB,eAAEtE,MACzCqI,OAAQhB,EAAKY,QAAO,SAAAF,GAAK,MAAS,SAATA,EAAEzD,GAAF,IAAkBF,KAAI,SAAC2D,GAAM,OA6BnD,SAA0Bf,GAC3B,MAAesB,EAAiBtB,GAA/BrG,EAAI,KAAE4H,EAAI,KACf,MAAO,CACL5H,KAAI,EACJ6H,OAAQC,KAAKC,MAAMH,GAEvB,CAnC0DI,CAAiBZ,EAAE/H,MAAnB,IACtD4I,QAASvB,EAAKY,QAAO,SAAAF,GAAK,MAAS,WAATA,EAAEzD,GAAF,IAAoBF,KAAI,SAAC2D,GAAM,OAoCtD,SAAqBf,GACtB,MAAmBsB,EAAiBtB,GACxC,MAAO,CADI,KAAQ,KAErB,CAvC6D6B,CAAYd,EAAE/H,MAAd,IACzD8I,UAA+C,QAApC,EAAAzB,EAAKS,MAAK,SAAAC,GAAK,MAAS,aAATA,EAAEzD,GAAF,WAAqB,eAAEtE,MACjDsH,UAAS,IAoBTO,KAAMA,EAGV,CAgBA,SAASS,EAAiBtB,GACxB,IAAI+B,EAAK/B,EAAMzE,QAAQ,KACvB,IAAY,IAARwG,EAAW,CACb,IAAI9I,EAAM,IAAInD,MAAM,kCAEpB,MADAmD,EAAI+I,QAAUhC,EACR/G,EAER,MAAO,CAAC+G,EAAMvG,MAAM,EAAGsI,GAAK/B,EAAMvG,MAAMsI,EAAK,GAC/C,CAEO,SAASnB,EAAUqB,GACxB,IAAIC,EAAQD,EAAKC,MAAM,kBACvB,GAAIA,EACF,MAAO,CACL5E,IAAK4E,EAAM,GACXlJ,MAAOkJ,EAAM,GAGnB,CAIO,SAASC,EAAaC,EAAgBC,GAC3C,OAAOD,EAAS,IAGlB,SAAuBC,GACrB,MAAa,IAATA,EACK,IAGFA,EAAM3D,QAAQ,KAAM,OAAOA,QAAQ,OAAQ,IACpD,CATwB4D,CAAcD,EACtC,0cE1IA,cAGE,WAAYE,EAAgBC,EAAa3D,GAAzC,MACE,YAAM2D,IAAI,YACV,EAAKD,OAASA,EACd,EAAK5I,KAAO,cACZ,EAAKkF,KAAOA,GACd,CACF,OATgC,OAShC,EATA,CAAgC/I,y2CC3B1B2M,EAAwC,WAA7BC,OAAOC,SAASF,SAAwB,OAAS,MAC5DG,EAAiB,UAAGH,EAAQ,aAAKC,OAAOC,SAASE,MAAI,OAAGH,OAAOC,SAASG,UAI9E,aAOE,aAFA,KAAAC,eAAyB,CAGzB,CA4GF,OAzGE,YAAAC,QAAA,SAAQC,GAAR,gBAAQ,IAAAA,IAAAA,EAAA,GACN,IAAMC,EAAO,IAAIC,UAAUF,GAG3B,SAASG,EAAeC,GACtB9F,QAAQ+F,IAAI,mBAAoBD,EAClC,CAJA1N,KAAK4N,OAASL,EAMdA,EAAKM,iBAAiB,QAASJ,GAE/BF,EAAKM,iBAAiB,QAAQ,SAACC,GAC7BlG,QAAQ+F,IAAI,uBAER,EAAKI,kBACPC,SAASC,cAAc,IAAIC,MAAM,yBAGnC,EAAKC,aAAc,EACnB,EAAKJ,kBAAmB,EACxB,EAAKX,eAAiB,IACtB,EAAKQ,OAAOQ,oBAAoB,QAASX,GACzCO,SAASC,cAAc,IAAIC,MAAM,sBACnC,IAEAX,EAAKM,iBAAiB,SAAS,SAAAnG,GACzB,EAAKyG,aACPH,SAASC,cAAc,IAAIC,MAAM,0BAGnC,EAAKC,aAAc,EAGf,EAAKJ,mBACPnG,QAAQ+F,IAAI,mBAAsB,EAAKP,eAAiB,IAAQ,KAChEvN,YAAW,WAAM,SAAKwN,QAAQC,EAAb,GAAoB,EAAKF,gBAG9C,GACF,EAEM,YAAAiB,WAAN,SAAiB5B,yGAEU,OADrBI,EHrBD,SAA6BA,GAClC,IAeyB7C,EAkCIU,EAzC7B,MAAO,CARM,CACX,WACA,WAAamC,EAAID,OACjB,WAAaC,EAAIJ,OACjB,cAAgBI,EAAIlC,WAKb2D,KAAK,OAwCe5D,EAvCZmC,EAAInC,KAwCdA,EAAKjD,KAAI,SAAA2D,GAAK,OAAAA,EAAEzD,IAAM,KAAOyD,EAAE/H,KAAjB,IAAwBiL,KAAK,QAvChDA,KAAK,QAIkBtE,EAJC6C,EAAI7C,MAMvB,OAASA,EADE,GAJpB,CGQcuE,CAAoB9B,GACL,GAAMzM,KAAKwO,MAAM/B,EAAO9B,UAAW8B,EAAOG,OAAQC,WAE3E,OAFI,EAAqB,SAAnBrC,EAAQ,WAAEU,EAAI,OAEb,CAAP,EAAO,CACLR,KAAMF,EACNtB,KAAMgC,EAAKoD,KAAK,gBAKd,YAAAE,MAAN,SAAYC,EAAkBC,EAAY7B,6FAE9B,OADV7M,KAAK2O,YAAY9B,GACP,GAAM7M,KAAK4O,YAAYH,EAAOC,WACxC,MAAO,CAAP,EADU,kBAIJ,YAAAC,YAAR,SAAoB9B,GAClB7M,KAAK4N,OAAOiB,KAAKhC,EACnB,EAEc,YAAA+B,YAAd,SAA0BH,EAAkBC,iFAC1C,MAAO,CAAP,EAAO,IAAII,SAAQ,SAACC,EAASC,GAC3B,IAAMC,EAAY,SAACC,GACjB,IAGIzJ,EAAS6E,EAHM4E,EAAMtD,KACRrB,MAAM,MAAMzG,MAAM,IAG/B0G,EAAqB/E,EAAO+E,SAE3BA,EAASG,UAKVH,EAASG,WAAa8D,IAO1B,EAAKb,OAAOQ,oBAAoB,UAAWa,GAG3CzE,EAASa,QAAQ7J,SAAQ,SAAC2N,GACxBnB,SAASmB,OAASA,CACpB,IAEI3E,EAASgB,MACXwD,EAAO,IAAII,EAAWV,EAAIlE,EAASgB,MAAO/F,EAAOyF,KAAKoD,KAAK,QAI7DS,EAAQtJ,IAvBNmC,QAAQ4D,MAAM,qBAAsBhB,EAAU0E,EAAMtD,KAwBxD,EAEA,EAAKgC,OAAOC,iBAAiB,UAAWoB,GACxC,EAAKrB,OAAOC,iBAAiB,QAASmB,EACxC,YAGF,YAAAK,WAAA,WACErP,KAAK4N,OAAO0B,OACd,EACF,EApHA,YCKO,SAASC,EAAeL,EAAeM,GAC5CxB,SAASH,iBAAiBqB,EAAM3J,eAAe,SAASkK,GACtD,IAAIC,EAASD,EAAEE,OAEXC,EAAa,KAAOV,EAAQO,EAAE9H,IAC9B8E,EAASiD,EAAOG,QAAQD,GACvBnD,IAELgD,EAAEK,iBACFN,EAAGO,EAAcL,GAASjD,GAC5B,GACF,CAEO,SAASuD,EAAoBd,EAAeM,GACjDxB,SAASH,iBAAiBqB,GAAO,SAASO,GACxC,IAGIC,EAHKD,EAAEE,OAGKM,QAAQ,WAAaf,EAAQ,KAC7C,GAAKQ,EAAL,CAEAD,EAAEK,iBACF,IAAIH,EAASI,EAAcL,GAC3BF,EAAGG,EAAQD,EAAOG,QAAQ,KAAOX,GAJd,CAKrB,GACF,CAgCO,SAASgB,EAAWjO,GAGzBA,EAAKkO,iBAAiB,iBAAiB3O,SAAQ,SAAC4O,GAC9C,IAAIC,EAAQ3K,SAAS0K,EAAKP,QAAQQ,QAAU,EACxCC,EAASF,EAAKP,QAAQU,OAK1B1Q,YAAW,WACT,IAAI8P,EAASI,EAAcK,GAG3B,GAAIA,EAAKP,QAAQU,QAAUD,EAA3B,CAKA,IAAMpB,EAAQ,IAAIsB,YAAY,WAAY,CAAEC,SAAS,EAAM5E,OAAQ,CAAE8D,OAAM,EAAEW,OAAM,KACnFF,EAAKnC,cAAciB,GACrB,GAAGmB,EACL,GACF,CAEO,SAASK,EAAiBzO,GAC/BA,EAAKkO,iBAAiB,uBAAuB3O,SAAQ,SAACS,GACpD,IAAI0O,EAAe1O,EAAK4N,QAAQe,aAE5BjB,EAASI,EAAc9N,GAE3BA,EAAK2O,aAAe,WAClB,IAAM1B,EAAQ,IAAIsB,YAAY,iBAAkB,CAAEC,SAAS,EAAM5E,OAAQ,CAAE8D,OAAM,EAAEgB,aAAY,KAC/F1O,EAAKgM,cAAciB,EACrB,CACF,GACF,CAEO,SAAS2B,EAAiB5O,GAC/BA,EAAKkO,iBAAiB,uBAAuB3O,SAAQ,SAACS,GACpD,IAAI6O,EAAe7O,EAAK4N,QAAQkB,aAE5BpB,EAASI,EAAc9N,GAE3BA,EAAK8O,aAAe,WAClB,IAAM7B,EAAQ,IAAIsB,YAAY,iBAAkB,CAAEC,SAAS,EAAM5E,OAAQ,CAAE8D,OAAM,EAAEmB,aAAY,KAC/F7O,EAAKgM,cAAciB,EACrB,CACF,GACF,CAmFA,SAASa,EAAc9N,GACrB,IAAI+O,EANN,SAAyB/O,SACnBgP,EAAahP,EAAKgO,QAAQ,iBAC9B,OAAOgB,aAAU,EAAVA,EAAYpB,QAAQF,UAA8B,QAApB,EAAA1N,EAAKgO,QAAQ,eAAO,eAAEvB,GAC7D,CAGiBwC,CAAgBjP,GAC3B0N,EAAS3B,SAASmD,eAAeH,GAErC,GAAKrB,EAKL,OAAOA,EAJL/H,QAAQ4D,MAAM,uBAAwBwF,EAAU/O,EAKpD,u2CClNO,SAAemP,EAAevE,6GAGzB,OADNwE,EAAMtE,OAAOC,SAASsE,KAChB,GAAM9C,MAAM6C,EAAK,CACzBE,OAAQ,OACRC,QACA,CACE,OAAU,YACV,eAAgB,oCAChB,gBAAiB3E,EAAIlC,UACrB,aAAckC,EAAID,OAClB,aAAcC,EAAIJ,QAEpBvD,KAAM2D,EAAI7C,KAEVuB,SAAU,mBAGD,UAfPkG,EAAM,UAeWC,eAGrB,GAHIxI,EAAO,SACP,EAeC,SAA2ByI,GAChC,IAEI,EAAqBrH,EAFbqH,EAAIpH,MAAM,MAEuBzG,MAAM,IAEnD,MAAO,CAAE0G,SAFK,WAEKU,KAFC,OAEUpH,MAAM,GACtC,CArB2B8N,CAAkB1I,GAArCsB,EAAQ,WAAEU,EAAI,QAEfuG,EAAII,GACP,MAAM,IAAIzC,EAAWvC,EAAID,OAAQ1D,EAAMA,GAQzC,MAAO,CAAP,EALyB,CACvBwB,KAAMF,EACNtB,KAAMgC,EAAKoD,KAAK,mBCfhBwD,o2CAPAC,EAAU,EAAQ,KAItBnK,QAAQ+F,IAAI,aAAeoE,EAAQC,SAInC,IAAIC,EAAkB,IAAIC,IAG1B,SAAe7D,EAAWxB,sEACxB,OAAIU,EAAKY,YACA,CAAP,EAAOZ,EAAKc,WAAWxB,IAGhB,CAAP,EAAOuE,EAAevE,UAM1B,SAAesF,EAAUxC,EAAmBlD,EAAgBzC,qHAG1D,QAAezK,IAAXoQ,EAEF,OADA/H,QAAQ4D,MAAM,wBACd,IAGF,QAAejM,IAAXkN,EAEF,OADA7E,QAAQ4D,MAAM,oBAAqBmE,EAAQ,uEAC3C,IAaF,GAVIyC,EAAUvS,YAAW,WAGvB8P,EAAO0C,UAAUC,IAAI,cACvB,GAAG,KAEC7D,ENcG8D,KAAKC,SAAS3O,SAAS,IAAI4O,UAAU,EAAG,GMb3C5F,EN/BC,SAAuB6B,EAAYjC,EAAuBgC,EAAkBzE,GAMjF,MAAO,CAAE4C,OAAQ8B,EAAIjC,OAAM,EAAE9B,UAAW8D,EAAO/D,KAL5B,CACjB,CAAE/C,IAAK,SAAUtE,MAAOqP,UAAU1E,SAASmB,SAC3C,CAAExH,IAAK,QAAStE,MAAO0J,OAAOC,SAAS2F,SAGY3I,KAAMD,EAASC,GACtE,CMwBY4I,CAAcjD,EAAOjB,GAAIjC,EAAQgC,EAAOzE,GAG9C2F,EAAOE,QAAQlF,UAEjB,OADA/C,QAAQC,KAAK,wCAA0C8H,EAAOE,QAAQlF,UAAY,IAAK8B,GACvF,IAIFkD,EAAOE,QAAQlF,UAAY8D,mBAGL,gCAAMJ,EAAWxB,WAErC,IAFI4E,EAAgB,UAEZ/G,KAAKC,WAAagF,EAAOE,QAAQlF,UAIvC,MAHIrH,EAAM,IAAInD,OACV6D,KAAO,oBACXV,EAAI+I,QAAU,iBAAmBoC,EAAQ,KAAOhC,EAC1CnJ,EAQR,cALSqM,EAAOE,QAAQlF,UAGPkI,EAAqBpB,EAAI/G,MAGxC,KAGEoI,EJnED,SAAuBrB,GAC5B,IACMsB,GADS,IAAI/J,WACAC,gBAAgBwI,EAAK,aAClCuB,EAAMD,EAAIE,cAAc,SAG9B,MAAO,CACL9M,QAHc4M,EAAIE,cAAc,OAIhCD,IAAKA,EAET,CIyD6BE,CAAczB,EAAIvI,MAEtC4J,EAAO3M,SA0GhB,SAAgBgN,GACd,GAAKA,EAEL,IADA,IACmB,MADAA,EAAIC,MAAMC,SACV,eAAO,CAArB,IAAMC,EAAI,KAC4B,GAArCrB,EAAgBsB,IAAID,EAAKE,WAC3B1B,EAAWsB,MAAMK,WAAWH,EAAKE,SACjCvB,EAAgBK,IAAIgB,EAAKE,UAG/B,CA7GIE,CAAOZ,EAAOE,KAIRW,EAAajL,EAAOiH,IACtBzM,EAAcwF,EAAOoK,EAAO3M,UAC3BK,WAAamN,EAAInN,WACtBX,EAAM3C,EAAMyQ,GAKZC,EADIC,EAAY7F,SAASmD,eAAexB,EAAOjB,KAG3CmF,GAEFC,EAAerC,EAAI/G,KAAMmJ,GAGzB3D,EAAW2D,GACXnD,EAAiBmD,GACjBhD,EAAiBgD,GA4DvB,SAAmBlE,GACjB,IAAIoE,EAAUpE,EAAOsD,cAAc,gBAC/Bc,aAAO,EAAPA,EAASC,QACXD,EAAQC,QAGVrE,EAAOQ,iBAAiB,gBAAgB3O,SAAQ,SAAC6I,GAC/C,IAAI4J,EAAM5J,EAAMV,aAAa,cACjBpK,IAAR0U,IACF5J,EAAMhH,MAAQ4Q,EAElB,IAEAtE,EAAOQ,iBAAiB,wBAAwB3O,SAAQ,SAAC0S,GACvD,IAAIC,EAAsC,QAA5BD,EAASrE,QAAQsE,QAC/BD,EAASC,QAAUA,CACrB,GACF,CA5EMC,CAAUP,GACVQ,EAAiBR,IAGjBjM,QAAQC,KAAK,mBAAoB8H,EAAOjB,YA/BxC9G,QAAQ4D,MAAM,kBAAmBiG,EAAIvI,MACrC,+BAmCFtB,QAAQ4D,MAAM,8BAAgCmE,EAAOjB,GAAK,OAAQ,GAIlEiB,EAAO2E,UAAY,EAAIpL,MAAQ,0GAKjC1I,aAAa4R,GACbzC,EAAO0C,UAAUkC,OAAO,0BAG1B,SAAS1B,EAAqBnI,GAC5B,GAAIA,EAAKa,SAGP,OADAwB,OAAOC,SAASsE,KAAO5G,EAAKa,UACrB,EAGS,MAAdb,EAAKe,OC1IJ,SAAkBA,GACvB,GAAIA,GAQN,WACE,IAAMA,EAAQsB,OAAOC,SAAS2F,OAC9B,OAAOlH,EAAMzE,WAAW,KAAOyE,EAAMgH,UAAU,GAAKhH,CACtD,CAXe+I,GAAgB,CACd,IAAT/I,IAAaA,EAAQ,IAAMA,GAC/B,IAAI4F,EAAMrE,SAASG,SAAW1B,EAE9BsB,OAAO0H,QAAQC,aAAa,CAAC,EAAG,GAAIrD,GAExC,CDoIIsD,CAASjK,EAAKe,OAGM,MAAlBf,EAAKyB,YACP6B,SAAS4G,MAAQlK,EAAKyB,UAE1B,CAEA,SAAS2H,EAAepJ,EAAgBiF,GACtC,IAAwB,UAAAjF,EAAKgB,OAAL,eAAa,CAAhC,IAAImJ,EAAW,KAClBhV,YAAW,WAET,IAAIqP,EAAQ,IAAIsB,YAAYqE,EAAY7Q,KAAM,CAAEyM,SAAS,EAAM5E,OAAQgJ,EAAYhJ,UACjE8D,GAAU3B,UAChBC,cAAciB,EAC5B,GAAG,IAGLxE,EAAKuB,QAAQzK,SAAQ,SAAC,OAACoL,EAAM,KAAEH,EAAM,KACnC5M,YAAW,WACT,IAAIiV,EAAO/H,OAAOgI,UAAUC,UAAUpI,GAClCkI,GACF3C,EAAU2C,EAAMrI,EAEpB,GAAG,GACL,GACF,CA2FA,SAAS4H,EAAiBpS,GAExBA,EAAKkO,iBAAiB,QAAQ3O,SAAQ,SAAC6D,GACrCA,EAAQ8M,UAAY,SAAS1F,GAC3B0F,EAAUnS,KAAMyM,EAClB,EAAEwI,KAAK5P,GAEPuO,EAAgB3R,EAClB,GACF,CAEA,SAAS2R,EAAgB3R,GACvB,IAAIiN,EAAQ,IAAIhB,MAAM,cAAe,CAAEuC,SAAS,IAChDxO,EAAKgM,cAAciB,EACrB,CAEAlB,SAASH,iBAAiB,oBAvE1B,WAEE,IF1J6B2B,EE0JzB9E,EAAON,EAAc4D,SAASmD,eAAe,gBAAgB+D,WACjErC,EAAqBnI,GACrBoJ,EAAepJ,GAEfoH,EAAa9D,SAASiF,cAAc,SF9JPzD,EEgKd,SAAeG,EAAmBlD,6EAC/C0F,EAAUxC,EAAQlD,cFhKpBuB,SAASH,iBAAiB,YAAY,SAAS4B,GAC7C,IAAIhD,EAASgD,EAAE5D,OAAOyE,OAClBX,EAASF,EAAE5D,OAAO8D,OACtBH,EAAGG,EAAQlD,EACb,IAEAuB,SAASH,iBAAiB,kBAAkB,SAAS4B,GACnD,IAAIhD,EAASgD,EAAE5D,OAAO8E,aAClBhB,EAASF,EAAE5D,OAAO8D,OACtBH,EAAGG,EAAQlD,EACb,IAEAuB,SAASH,iBAAiB,kBAAkB,SAAS4B,GACnD,IAAIhD,EAASgD,EAAE5D,OAAOiF,aAClBnB,EAASF,EAAE5D,OAAO8D,OACtBH,EAAGG,EAAQlD,EACb,IEmJAyD,EAAWlC,SAAS9E,MACpBwH,EAAiB1C,SAAS9E,MAC1B2H,EAAiB7C,SAAS9E,MAC1BmL,EAAiBrG,SAAS9E,MF/K1B8G,EAAoB,SEkLR,SAAeL,EAAmBlD,6EAE5C0F,EAAUxC,EAAQlD,gBFhLpBuD,EAAoB,YEmLL,SAAeL,EAAmBlD,6EAE/C0F,EAAUxC,EAAQlD,gBF5NpB8C,EAAe,WE+ND,SAAeI,EAAmBlD,6EAE9C0F,EAAUxC,EAAQlD,gBF7NpB8C,EAAe,SEgOH,SAAeI,EAAmBlD,6EAE5C0F,EAAUxC,EAAQlD,gBFvDpBuB,SAASH,iBAAiB,UAAU,SAAS4B,GAC3C,IAAIzF,EAAOyF,EAAEE,OAEb,GAAK3F,aAAI,EAAJA,EAAM6F,QAAQsF,SAAnB,CAKA1F,EAAEK,iBAEF,IAAIH,EAASI,EAAc/F,GACrBoL,EAAW,IAAIC,SAASrL,IE+Cf,SAAe2F,EAAmBlD,EAAgBzC,sEAEjEmI,EAAUxC,EAAQlD,EAAQzC,aFhD1BwF,CAAGG,EAAQ3F,EAAK6F,QAAQsF,SAAUC,QARhCxN,QAAQ4D,MAAM,qBAAsBxB,EASxC,IAvEAgE,SAASH,iBAAiB,UAAU,SAAS4B,GAC3C,IAEIC,EAFKD,EAAEE,OAEKM,QAAQ,mBAEnBP,IACLD,EAAEK,iBAEkB,MAAhBJ,EAAOrM,MEiHA,SAAesM,EAAmBlD,sEAC7C7E,QAAQ+F,IAAI,SAAUgC,EAAOjB,GAAI,IAAMjC,EAAS,KAChD0F,EAAUxC,EAAQlD,aF5GlB+C,CAFaO,EAAcL,GACdlD,EAAakD,EAAOG,QAAQyF,SAAU5F,EAAOrM,QALxDuE,QAAQ4D,MAAM,uBAAwBkE,GAO1C,IAQA1B,SAASH,iBAAiB,SAAS,SAAS4B,GAC1C,IACIC,EADKD,EAAEE,OACKM,QAAQ,kBAExB,GAAKP,EAAL,CAEA,IAAIW,EAAQ3K,SAASgK,EAAOG,QAAQQ,QAAU,IAK9C,GAJIA,EAAQ,KACVzI,QAAQC,KAAK,qDAGV6H,aAAM,EAANA,EAAQG,QAAQ0F,QAArB,CAKA9F,EAAEK,iBAEF,IAAIH,EAASI,EAAcL,GAEtBA,EAAO8F,oBACV9F,EAAO8F,kBAAoBhX,GAAS,WAClC,IAAIiO,EAASD,EAAakD,EAAOG,QAAQ0F,QAAS7F,EAAOrM,QEgFnD,SAAesM,EAAmBlD,sEAC5C0F,EAAUxC,EAAQlD,aFhFd+C,CAAGG,EAAQlD,EACb,GAAG4D,IAGLX,EAAO8F,yBAfL5N,QAAQ4D,MAAM,oBAAqBkE,EARlB,CAwBrB,GE6EF,IA0BA,IAAMnC,EAAO,IAAIkI,EACjBlI,EAAKF,UA2DLN,OAAOgI,UACP,CACE5C,UAAWA,EACX/H,cAAeA,EACfqC,OAAQ,SAASiJ,OAAK,wDAEpB,OAAOA,EADEzL,EAAOT,QAAO,SAACmM,EAAKjJ,GAAU,OAAAiJ,EAAM,IAAM7J,KAAK8J,UAAUlJ,EAA3B,GAAmC,GAE5E,EACAsI,UAAW,SAASpI,GAClB,IAAIvH,EAAU2I,SAASmD,eAAevE,GACtC,GAAKvH,aAAO,EAAPA,EAAS8M,UAId,OAAO9M,EAHLuC,QAAQ4D,MAAM,cAAgBoB,EAAS,uBAI3C,EACAgB,OAAQL","sources":["webpack://web-ui/./node_modules/debounce/index.js","webpack://web-ui/webpack/bootstrap","webpack://web-ui/./node_modules/omdomdom/lib/omdomdom.es.js","webpack://web-ui/./src/action.ts","webpack://web-ui/./src/lib.ts","webpack://web-ui/./src/response.ts","webpack://web-ui/./src/sockets.ts","webpack://web-ui/./src/events.ts","webpack://web-ui/./src/http.ts","webpack://web-ui/./src/index.ts","webpack://web-ui/./src/browser.ts"],"sourcesContent":["function debounce(function_, wait = 100, options = {}) {\n\tif (typeof function_ !== 'function') {\n\t\tthrow new TypeError(`Expected the first parameter to be a function, got \\`${typeof function_}\\`.`);\n\t}\n\n\tif (wait < 0) {\n\t\tthrow new RangeError('`wait` must not be negative.');\n\t}\n\n\t// TODO: Deprecate the boolean parameter at some point.\n\tconst {immediate} = typeof options === 'boolean' ? {immediate: options} : options;\n\n\tlet storedContext;\n\tlet storedArguments;\n\tlet timeoutId;\n\tlet timestamp;\n\tlet result;\n\n\tfunction run() {\n\t\tconst callContext = storedContext;\n\t\tconst callArguments = storedArguments;\n\t\tstoredContext = undefined;\n\t\tstoredArguments = undefined;\n\t\tresult = function_.apply(callContext, callArguments);\n\t\treturn result;\n\t}\n\n\tfunction later() {\n\t\tconst last = Date.now() - timestamp;\n\n\t\tif (last < wait && last >= 0) {\n\t\t\ttimeoutId = setTimeout(later, wait - last);\n\t\t} else {\n\t\t\ttimeoutId = undefined;\n\n\t\t\tif (!immediate) {\n\t\t\t\tresult = run();\n\t\t\t}\n\t\t}\n\t}\n\n\tconst debounced = function (...arguments_) {\n\t\tif (\n\t\t\tstoredContext\n\t\t\t&& this !== storedContext\n\t\t\t&& Object.getPrototypeOf(this) === Object.getPrototypeOf(storedContext)\n\t\t) {\n\t\t\tthrow new Error('Debounced method called with different contexts of the same prototype.');\n\t\t}\n\n\t\tstoredContext = this; // eslint-disable-line unicorn/no-this-assignment\n\t\tstoredArguments = arguments_;\n\t\ttimestamp = Date.now();\n\n\t\tconst callNow = immediate && !timeoutId;\n\n\t\tif (!timeoutId) {\n\t\t\ttimeoutId = setTimeout(later, wait);\n\t\t}\n\n\t\tif (callNow) {\n\t\t\tresult = run();\n\t\t}\n\n\t\treturn result;\n\t};\n\n\tObject.defineProperty(debounced, 'isPending', {\n\t\tget() {\n\t\t\treturn timeoutId !== undefined;\n\t\t},\n\t});\n\n\tdebounced.clear = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tclearTimeout(timeoutId);\n\t\ttimeoutId = undefined;\n\t};\n\n\tdebounced.flush = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tdebounced.trigger();\n\t};\n\n\tdebounced.trigger = () => {\n\t\tresult = run();\n\n\t\tdebounced.clear();\n\t};\n\n\treturn debounced;\n}\n\n// Adds compatibility for ES modules\nmodule.exports.debounce = debounce;\n\nmodule.exports = debounce;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","/*!\n * @license MIT (https://github.com/geotrev/omdomdom/blob/master/LICENSE)\n * Omdomdom v0.3.2 (https://github.com/geotrev/omdomdom)\n * Copyright 2023 George Treviranus <geowtrev@gmail.com>\n */\nvar DATA_KEY_ATTRIBUTE$1 = \"data-key\";\nvar hasProperty = function hasProperty(obj, prop) {\n return Object.prototype.hasOwnProperty.call(obj, prop);\n};\nvar keyIsValid = function keyIsValid(map, key) {\n if (!key) return false;\n if (hasProperty(map, key)) {\n console.warn(\"[OmDomDom]: Children with duplicate keys detected. Children with duplicate keys will be skipped, resulting in dropped node references. Keys must be unique and non-indexed.\");\n return false;\n }\n return true;\n};\nvar forEach = function forEach(items, fn) {\n var length = items.length;\n var idx = -1;\n if (!length) return;\n while (++idx < length) {\n if (fn(items[idx], idx) === false) break;\n }\n};\nvar createKeyMap = function createKeyMap(children) {\n var map = {};\n forEach(children, function (child) {\n var key = child.attributes[DATA_KEY_ATTRIBUTE$1];\n if (keyIsValid(map, key)) {\n map[key] = child;\n }\n });\n for (var _ in map) {\n return map;\n }\n};\nvar insertBefore = function insertBefore(parent, child, refNode) {\n return parent.node.insertBefore(child.node, refNode);\n};\nvar assignVNode = function assignVNode(template, vNode) {\n for (var property in template) {\n vNode[property] = template[property];\n }\n};\n\nvar toHTML = function toHTML(htmlString) {\n var processedDOMString = htmlString.trim().replace(/\\s+</g, \"<\").replace(/>\\s+/g, \">\");\n var parser = new DOMParser();\n var context = parser.parseFromString(processedDOMString, \"text/html\");\n return context.body;\n};\n\nfunction _iterableToArrayLimit(arr, i) {\n var _i = null == arr ? null : \"undefined\" != typeof Symbol && arr[Symbol.iterator] || arr[\"@@iterator\"];\n if (null != _i) {\n var _s,\n _e,\n _x,\n _r,\n _arr = [],\n _n = !0,\n _d = !1;\n try {\n if (_x = (_i = _i.call(arr)).next, 0 === i) {\n if (Object(_i) !== _i) return;\n _n = !1;\n } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0);\n } catch (err) {\n _d = !0, _e = err;\n } finally {\n try {\n if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return;\n } finally {\n if (_d) throw _e;\n }\n }\n return _arr;\n }\n}\nfunction _slicedToArray(arr, i) {\n return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();\n}\nfunction _arrayWithHoles(arr) {\n if (Array.isArray(arr)) return arr;\n}\nfunction _unsupportedIterableToArray(o, minLen) {\n if (!o) return;\n if (typeof o === \"string\") return _arrayLikeToArray(o, minLen);\n var n = Object.prototype.toString.call(o).slice(8, -1);\n if (n === \"Object\" && o.constructor) n = o.constructor.name;\n if (n === \"Map\" || n === \"Set\") return Array.from(o);\n if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);\n}\nfunction _arrayLikeToArray(arr, len) {\n if (len == null || len > arr.length) len = arr.length;\n for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];\n return arr2;\n}\nfunction _nonIterableRest() {\n throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}\n\nvar Types = {\n STRING: \"string\",\n INT: \"number\",\n BOOL: \"boolean\"\n};\nvar DomProperties = {};\nvar createRecord = function createRecord(attrName, propName, type) {\n return {\n attrName: attrName,\n propName: propName,\n type: type\n };\n};\nvar stringProps = [[\"style\", \"cssText\"], [\"class\", \"className\"]];\nforEach(stringProps, function (_ref) {\n var _ref2 = _slicedToArray(_ref, 2),\n attr = _ref2[0],\n property = _ref2[1];\n DomProperties[attr] = createRecord(attr, property || attr, Types.STRING);\n});\nvar booleanProps = [\"autofocus\", \"draggable\", \"hidden\", \"checked\", \"multiple\", \"muted\", \"selected\"];\nforEach(booleanProps, function (attr) {\n DomProperties[attr] = createRecord(attr, attr, Types.BOOL);\n});\nvar integerProps = [[\"tabindex\", \"tabIndex\"]];\nforEach(integerProps, function (_ref3) {\n var _ref4 = _slicedToArray(_ref3, 2),\n attr = _ref4[0],\n property = _ref4[1];\n DomProperties[attr] = createRecord(attr, property, Types.INT);\n});\nvar Namespace = {\n xlink: {\n prefix: \"xlink:\",\n resource: \"http://www.w3.org/1999/xlink\"\n },\n xml: {\n prefix: \"xml:\",\n resource: \"http://www.w3.org/XML/1998/namespace\"\n }\n};\n\nvar setProperty = function setProperty(element, type, prop, value) {\n switch (type) {\n case Types.STRING:\n {\n if (prop === DomProperties.style.propName) {\n if (value === null) {\n element.style[prop] = \"\";\n } else {\n element.style[prop] = value;\n }\n } else if (value === null) {\n element[prop] = \"\";\n } else {\n element[prop] = value;\n }\n break;\n }\n case Types.INT:\n {\n if (value === null) {\n var attr = prop.toLowerCase();\n element.removeAttribute(attr);\n } else if (value === \"0\") {\n element[prop] = 0;\n } else if (value === \"-1\") {\n element[prop] = -1;\n } else {\n var parsed = parseInt(value, 10);\n if (!isNaN(parsed)) {\n element[prop] = parsed;\n }\n }\n break;\n }\n case Types.BOOL:\n {\n if ([\"\", \"true\"].indexOf(value) < 0) {\n element[prop] = false;\n } else {\n element[prop] = true;\n }\n break;\n }\n }\n};\n\nvar removeAttributes = function removeAttributes(vNode, attributes) {\n forEach(attributes, function (attrName) {\n if (hasProperty(DomProperties, attrName)) {\n var propertyRecord = DomProperties[attrName];\n setProperty(vNode.node, propertyRecord.type, propertyRecord.propName, null);\n } else {\n if (attrName in vNode.node) {\n setProperty(vNode.node, Types.STRING, attrName, null);\n }\n vNode.node.removeAttribute(attrName);\n }\n delete vNode.attributes[attrName];\n });\n};\nvar setAttributes = function setAttributes(vNode, attributes) {\n for (var attrName in attributes) {\n var value = attributes[attrName];\n vNode.attributes[attrName] = value;\n if (hasProperty(DomProperties, attrName)) {\n var propertyRecord = DomProperties[attrName];\n setProperty(vNode.node, propertyRecord.type, propertyRecord.propName, value);\n continue;\n }\n if (attrName.startsWith(Namespace.xlink.prefix)) {\n vNode.node.setAttributeNS(Namespace.xlink.resource, attrName, value);\n continue;\n }\n if (attrName.startsWith(Namespace.xml.prefix)) {\n vNode.node.setAttributeNS(Namespace.xml.resource, attrName, value);\n continue;\n }\n if (attrName in vNode.node) {\n setProperty(vNode.node, Types.STRING, attrName, value);\n }\n vNode.node.setAttribute(attrName, value || \"\");\n }\n};\nvar getPropertyValues = function getPropertyValues(element, attributes) {\n for (var attrName in DomProperties) {\n var propertyRecord = DomProperties[attrName];\n var propName = propertyRecord.propName;\n var attrValue = element.getAttribute(attrName);\n if (attrName === DomProperties.style.attrName) {\n attributes[attrName] = element.style[propName];\n } else if (typeof attrValue === \"string\") {\n attributes[attrName] = attrValue;\n }\n }\n};\nvar getBaseAttributes = function getBaseAttributes(element) {\n return Array.prototype.reduce.call(element.attributes, function (attributes, attrName) {\n if (!hasProperty(DomProperties, attrName.name)) {\n attributes[attrName.name] = attrName.value;\n }\n return attributes;\n }, {});\n};\nvar getAttributes = function getAttributes(element) {\n var attributes = getBaseAttributes(element);\n getPropertyValues(element, attributes);\n return attributes;\n};\nvar updateAttributes = function updateAttributes(template, vNode) {\n var removedAttributes = [];\n var changedAttributes = {};\n for (var attrName in vNode.attributes) {\n var oldValue = vNode.attributes[attrName];\n var nextValue = template.attributes[attrName];\n if (oldValue === nextValue) continue;\n if (typeof nextValue === \"undefined\") {\n removedAttributes.push(attrName);\n }\n }\n for (var _attrName in template.attributes) {\n var _oldValue = vNode.attributes[_attrName];\n var _nextValue = template.attributes[_attrName];\n if (_oldValue === _nextValue) continue;\n if (typeof _nextValue !== \"undefined\") {\n changedAttributes[_attrName] = _nextValue;\n }\n }\n removeAttributes(vNode, removedAttributes);\n setAttributes(vNode, changedAttributes);\n};\n\nvar DATA_KEY_ATTRIBUTE = \"data-key\";\nfunction patchChildren(template, vNode, patch) {\n var templateChildrenLength = template.children.length;\n var vNodeChildrenLength = vNode.children.length;\n if (!templateChildrenLength && !vNodeChildrenLength) return;\n var vNodeKeyMap = createKeyMap(vNode.children);\n var nextChildren = Array(templateChildrenLength);\n if (vNodeKeyMap !== undefined) {\n forEach(template.children, function (templateChild, idx) {\n var childNodes = vNode.node.childNodes;\n var key = templateChild.attributes[DATA_KEY_ATTRIBUTE];\n if (Object.prototype.hasOwnProperty.call(vNodeKeyMap, key)) {\n var keyedChild = vNodeKeyMap[key];\n if (Array.prototype.indexOf.call(childNodes, keyedChild.node) !== idx) {\n insertBefore(vNode, keyedChild, childNodes[idx]);\n }\n nextChildren[idx] = keyedChild;\n delete vNodeKeyMap[key];\n patch(templateChild, nextChildren[idx]);\n } else {\n insertBefore(vNode, templateChild, childNodes[idx]);\n nextChildren[idx] = templateChild;\n }\n });\n } else {\n forEach(template.children, function (templateChild, idx) {\n var vNodeChild = vNode.children[idx];\n if (typeof vNodeChild !== \"undefined\") {\n patch(templateChild, vNodeChild);\n nextChildren[idx] = vNodeChild;\n } else {\n vNode.node.appendChild(templateChild.node);\n nextChildren[idx] = templateChild;\n }\n });\n }\n vNode.children = nextChildren;\n var childNodesLength = vNode.node.childNodes.length;\n var delta = childNodesLength - templateChildrenLength;\n if (delta > 0) {\n while (delta > 0) {\n vNode.node.removeChild(vNode.node.childNodes[childNodesLength - 1]);\n childNodesLength--;\n delta--;\n }\n }\n}\n\nvar patch = function patch(template, vNode, rootNode) {\n if (!template || !vNode) return;\n rootNode = rootNode || vNode.node.parentNode;\n var contentChanged = template.content && template.content !== vNode.content;\n if (template.type !== vNode.type || contentChanged) {\n rootNode.replaceChild(template.node, vNode.node);\n return assignVNode(template, vNode);\n }\n updateAttributes(template, vNode);\n patchChildren(template, vNode, patch);\n};\nvar render = function render(vNode, root) {\n root.appendChild(vNode.node);\n};\nvar create = function create(node) {\n var isSVGContext = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n if (typeof node === \"string\") {\n node = toHTML(node);\n }\n var isRoot = node.tagName === \"BODY\";\n var childNodes = node.childNodes;\n var numChildNodes = childNodes ? childNodes.length : 0;\n if (isRoot) {\n if (numChildNodes > 1) {\n throw new Error(\"[OmDomDom]: Your element should not have more than one root node.\");\n } else if (numChildNodes === 0) {\n throw new Error(\"[OmDomDom]: Your element should have at least one root node.\");\n } else {\n return create(childNodes[0]);\n }\n }\n var type = node.nodeType === 3 ? \"text\" : node.nodeType === 8 ? \"comment\" : node.tagName.toLowerCase();\n var isSVG = isSVGContext || type === \"svg\";\n var attributes = node.nodeType === 1 ? getAttributes(node) : {};\n var content = numChildNodes > 0 ? null : node.textContent;\n var children = Array(numChildNodes);\n forEach(childNodes, function (child, idx) {\n children[idx] = create(child, isSVG);\n });\n return {\n type: type,\n attributes: attributes,\n children: children,\n content: content,\n node: node,\n isSVGContext: isSVG\n };\n};\n\nexport { create, patch, render };\n//# sourceMappingURL=omdomdom.es.js.map\n","\nimport { takeWhileMap } from \"./lib\"\n\n\ntype EncodedAction = string\n\nexport type ActionMessage = {\n viewId: ViewId\n action: EncodedAction\n requestId: RequestId\n meta: Meta[]\n form: URLSearchParams | undefined\n}\n\nexport type ViewId = string\nexport type RequestId = string\n\n\n\nexport function actionMessage(id: ViewId, action: EncodedAction, reqId: RequestId, form?: FormData): ActionMessage {\n let meta: Meta[] = [\n { key: \"Cookie\", value: decodeURI(document.cookie) },\n { key: \"Query\", value: window.location.search }\n ]\n\n return { viewId: id, action, requestId: reqId, meta, form: toSearch(form) }\n}\n\nexport function toSearch(form?: FormData): URLSearchParams | undefined {\n if (!form) return undefined\n\n const params = new URLSearchParams()\n\n form.forEach((value, key) => {\n params.append(key, value as string)\n })\n\n return params\n}\n\nexport function renderActionMessage(msg: ActionMessage): string {\n let header = [\n \"|ACTION|\",\n \"ViewId: \" + msg.viewId,\n \"Action: \" + msg.action,\n \"RequestId: \" + msg.requestId\n ]\n\n\n return [\n header.join('\\n'),\n renderMetadata(msg.meta),\n ].join('\\n') + renderForm(msg.form)\n}\n\n\nexport function renderForm(form: URLSearchParams | undefined): string {\n if (!form) return \"\"\n return \"\\n\\n\" + form\n}\n\n\nexport function requestId(): RequestId {\n return Math.random().toString(36).substring(2, 8)\n}\n\n\n// Metadata -------------------------------------\n\n\ntype Meta = { key: string, value: string }\ntype RemoteEvent = { name: string, detail: any }\n\nexport type Metadata = {\n requestId: string\n cookies: string[]\n redirect?: string\n error?: string\n query?: string\n events?: RemoteEvent[]\n actions?: [ViewId, string][],\n pageTitle?: string\n}\n\n\nexport type ParsedResponse = {\n metadata: Metadata,\n rest: string[]\n}\n\nexport function renderMetadata(meta: Meta[]): string {\n return meta.map(m => m.key + \": \" + m.value).join('\\n')\n}\n\nexport function parseMetas(meta: Meta[]): Metadata {\n\n let requestId = meta.find(m => m.key == \"RequestId\")?.value\n\n return {\n cookies: meta.filter(m => m.key == \"Cookie\").map(m => m.value),\n redirect: meta.find(m => m.key == \"Redirect\")?.value,\n error: meta.find(m => m.key == \"Error\")?.value,\n query: meta.find(m => m.key == \"Query\")?.value,\n events: meta.filter(m => m.key == \"Event\").map((m) => parseRemoteEvent(m.value)),\n actions: meta.filter(m => m.key == \"Trigger\").map((m) => parseAction(m.value)),\n pageTitle: meta.find(m => m.key == \"PageTitle\")?.value,\n requestId\n }\n}\n// viewId: meta.find(m => m.key == \"VIEW-ID\")?.value,\n\n// decode entities\nexport function parseMetadata(input: string): Metadata {\n return splitMetadata(input.trim().split(\"\\n\")).metadata\n\n}\n\nexport function splitMetadata(lines: string[]): ParsedResponse {\n let metas: Meta[] = takeWhileMap(parseMeta, lines)\n // console.log(\"Split Metadata\", lines.length)\n // console.log(\" [0]\", lines[0])\n // console.log(\" [1]\", lines[1])\n let rest = lines.slice(metas.length)\n\n return {\n metadata: parseMetas(metas),\n rest: rest\n }\n\n}\n\n\nexport function parseRemoteEvent(input: string): RemoteEvent {\n let [name, data] = breakNextSegment(input)\n return {\n name,\n detail: JSON.parse(data)\n }\n}\n\nexport function parseAction(input: string): [ViewId, string] {\n let [viewId, action] = breakNextSegment(input)\n return [viewId, action]\n}\n\nfunction breakNextSegment(input: string): [string, string] | undefined {\n let ix = input.indexOf('|')\n if (ix === -1) {\n let err = new Error(\"Bad Encoding, Expected Segment\")\n err.message = input\n throw err\n }\n return [input.slice(0, ix), input.slice(ix + 1)]\n}\n\nexport function parseMeta(line: string): Meta | undefined {\n let match = line.match(/^(\\w+)\\: (.*)$/)\n if (match) {\n return {\n key: match[1],\n value: match[2]\n }\n }\n}\n\n// Sanitized Encoding ------------------------------------\n\nexport function encodedParam(action: string, param: string): string {\n return action + ' ' + sanitizeParam(param)\n}\n\nfunction sanitizeParam(param: string): string {\n if (param == \"\") {\n return \"|\"\n }\n\n return param.replace(/_/g, \"\\\\_\").replace(/\\s+/g, \"_\")\n}\n","\n\nexport function takeWhileMap<T, A>(pred: (val: T) => A | undefined, lines: T[]): A[] {\n var output = []\n for (var line of lines) {\n let a = pred(line)\n if (a)\n output.push(a)\n else\n break;\n }\n\n return output\n}\n\n// export function dropWhile<T, A>(pred: (val: T) => A | undefined, lines: T[]): T[] {\n// let index = 0;\n// while (index < lines.length && pred(lines[index])) {\n// index++;\n// }\n// return lines.slice(index);\n// }\n//\n","\nimport { ViewId } from './action'\nimport { Metadata } from \"./action\"\n\n\n\nexport type Response = {\n meta: Metadata\n body: ResponseBody\n}\n\nexport type ResponseBody = string\n\nexport function parseResponse(res: ResponseBody): LiveUpdate {\n const parser = new DOMParser()\n const doc = parser.parseFromString(res, 'text/html')\n const css = doc.querySelector(\"style\") as HTMLStyleElement\n const content = doc.querySelector(\"div\") as HTMLElement\n\n return {\n content: content,\n css: css\n }\n}\n\nexport type LiveUpdate = {\n content: HTMLElement\n css: HTMLStyleElement | null\n}\n\n\nexport class FetchError extends Error {\n viewId: ViewId\n body: string\n constructor(viewId: ViewId, msg: string, body: string) {\n super(msg)\n this.viewId = viewId\n this.name = \"Fetch Error\"\n this.body = body\n }\n}\n","import { ActionMessage, ViewId, RequestId, renderActionMessage } from './action'\nimport { Metadata, ParsedResponse, splitMetadata } from \"./action\"\nimport { Response, FetchError } from \"./response\"\n\nconst protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\nconst defaultAddress = `${protocol}//${window.location.host}${window.location.pathname}`\n\n\n\nexport class SocketConnection {\n socket: WebSocket\n\n hasEverConnected: Boolean\n isConnected: Boolean\n reconnectDelay: number = 0\n\n constructor() {\n }\n\n // we need to faithfully transmit the \n connect(addr = defaultAddress) {\n const sock = new WebSocket(addr)\n this.socket = sock\n\n function onConnectError(ev: Event) {\n console.log(\"Connection Error\", ev)\n }\n\n sock.addEventListener('error', onConnectError)\n\n sock.addEventListener('open', (_event) => {\n console.log(\"Websocket Connected\")\n\n if (this.hasEverConnected) {\n document.dispatchEvent(new Event(\"hyp-socket-reconnect\"))\n }\n\n this.isConnected = true\n this.hasEverConnected = true\n this.reconnectDelay = 1000\n this.socket.removeEventListener('error', onConnectError)\n document.dispatchEvent(new Event(\"hyp-socket-connect\"))\n })\n\n sock.addEventListener('close', _ => {\n if (this.isConnected) {\n document.dispatchEvent(new Event(\"hyp-socket-disconnect\"))\n }\n\n this.isConnected = false\n\n // attempt to reconnect in 1s\n if (this.hasEverConnected) {\n console.log(\"Reconnecting in \" + (this.reconnectDelay / 1000) + \"s\")\n setTimeout(() => this.connect(addr), this.reconnectDelay)\n }\n\n })\n }\n\n async sendAction(action: ActionMessage): Promise<Response> {\n let msg = renderActionMessage(action)\n let { metadata, rest } = await this.fetch(action.requestId, action.viewId, msg)\n\n return {\n meta: metadata,\n body: rest.join('\\n')\n }\n\n }\n\n async fetch(reqId: RequestId, id: ViewId, msg: string): Promise<ParsedResponse> {\n this.sendMessage(msg)\n let res = await this.waitMessage(reqId, id)\n return res\n }\n\n private sendMessage(msg: string) {\n this.socket.send(msg)\n }\n\n private async waitMessage(reqId: RequestId, id: ViewId): Promise<ParsedResponse> {\n return new Promise((resolve, reject) => {\n const onMessage = (event: MessageEvent) => {\n let data: string = event.data\n let lines = data.split(\"\\n\").slice(1) // drop the command line\n\n let parsed = splitMetadata(lines)\n let metadata: Metadata = parsed.metadata\n\n if (!metadata.requestId) {\n console.error(\"Missing RequestId!\", metadata, event.data)\n return\n }\n\n if (metadata.requestId != reqId) {\n // skip, it's not us!\n return\n }\n\n\n // We have found our message. Remove the listener\n this.socket.removeEventListener('message', onMessage)\n\n // set the cookies. These happen automatically in http\n metadata.cookies.forEach((cookie: string) => {\n document.cookie = cookie\n })\n\n if (metadata.error) {\n reject(new FetchError(id, metadata.error, parsed.rest.join('\\n')))\n return\n }\n\n resolve(parsed)\n }\n\n this.socket.addEventListener('message', onMessage)\n this.socket.addEventListener('error', reject)\n })\n }\n\n disconnect() {\n this.socket.close()\n }\n}\n\n\n\n\n\n\n","\nimport * as debounce from 'debounce'\nimport { encodedParam } from './action'\n\nexport type UrlFragment = string\n\nexport function listenKeydown(cb: (target: HTMLElement, action: string) => void): void {\n listenKeyEvent(\"Keydown\", cb)\n}\n\nexport function listenKeyup(cb: (target: HTMLElement, action: string) => void): void {\n listenKeyEvent(\"Keyup\", cb)\n}\n\nexport function listenKeyEvent(event: string, cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(event.toLowerCase(), function(e: KeyboardEvent) {\n let source = e.target as HTMLInputElement\n\n let datasetKey = \"on\" + event + e.key\n let action = source.dataset[datasetKey]\n if (!action) return\n\n e.preventDefault()\n cb(nearestTarget(source), action)\n })\n}\n\nexport function listenBubblingEvent(event: string, cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(event, function(e) {\n let el = e.target as HTMLInputElement\n\n // clicks can fire on internal elements. Find the parent with a click handler\n let source = el.closest(\"[data-on\" + event + \"]\") as HTMLElement\n if (!source) return\n\n e.preventDefault()\n let target = nearestTarget(source)\n cb(target, source.dataset[\"on\" + event])\n })\n}\n\nexport function listenClick(cb: (target: HTMLElement, action: string) => void): void {\n listenBubblingEvent(\"click\", cb)\n}\n\nexport function listenDblClick(cb: (target: HTMLElement, action: string) => void): void {\n listenBubblingEvent(\"dblclick\", cb)\n}\n\n\nexport function listenTopLevel(cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"hyp-load\", function(e: CustomEvent) {\n let action = e.detail.onLoad\n let target = e.detail.target\n cb(target, action)\n })\n\n document.addEventListener(\"hyp-mouseenter\", function(e: CustomEvent) {\n let action = e.detail.onMouseEnter\n let target = e.detail.target\n cb(target, action)\n })\n\n document.addEventListener(\"hyp-mouseleave\", function(e: CustomEvent) {\n let action = e.detail.onMouseLeave\n let target = e.detail.target\n cb(target, action)\n })\n}\n\n\nexport function listenLoad(node: HTMLElement): void {\n\n // it doesn't really matter WHO runs this except that it should have target\n node.querySelectorAll(\"[data-onload]\").forEach((load: HTMLElement) => {\n let delay = parseInt(load.dataset.delay) || 0\n let onLoad = load.dataset.onload\n // console.log(\"load start\", load.dataset.onLoad)\n\n // load no longer exists!\n // we should clear the timeout or back out if the dom is replaced in the interem\n setTimeout(() => {\n let target = nearestTarget(load)\n // console.log(\"load go\", load.dataset.onLoad)\n\n if (load.dataset.onload != onLoad) {\n // the onLoad no longer exists\n return\n }\n\n const event = new CustomEvent(\"hyp-load\", { bubbles: true, detail: { target, onLoad } })\n load.dispatchEvent(event)\n }, delay)\n })\n}\n\nexport function listenMouseEnter(node: HTMLElement): void {\n node.querySelectorAll(\"[data-onmouseenter]\").forEach((node: HTMLElement) => {\n let onMouseEnter = node.dataset.onmouseenter\n\n let target = nearestTarget(node)\n\n node.onmouseenter = () => {\n const event = new CustomEvent(\"hyp-mouseenter\", { bubbles: true, detail: { target, onMouseEnter } })\n node.dispatchEvent(event)\n }\n })\n}\n\nexport function listenMouseLeave(node: HTMLElement): void {\n node.querySelectorAll(\"[data-onmouseleave]\").forEach((node: HTMLElement) => {\n let onMouseLeave = node.dataset.onmouseleave\n\n let target = nearestTarget(node)\n\n node.onmouseleave = () => {\n const event = new CustomEvent(\"hyp-mouseleave\", { bubbles: true, detail: { target, onMouseLeave } })\n node.dispatchEvent(event)\n }\n })\n}\n\n\nexport function listenChange(cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"change\", function(e) {\n let el = e.target as HTMLElement\n\n let source = el.closest(\"[data-onchange]\") as HTMLInputElement\n\n if (!source) return\n e.preventDefault()\n\n if (source.value == null) {\n console.error(\"Missing input value:\", source)\n return\n }\n\n let target = nearestTarget(source)\n let action = encodedParam(source.dataset.onchange, source.value)\n cb(target, action)\n })\n}\n\ninterface LiveInputElement extends HTMLInputElement {\n debouncedCallback?: Function;\n}\n\nexport function listenInput(cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"input\", function(e) {\n let el = e.target as HTMLElement\n let source = el.closest(\"[data-oninput]\") as LiveInputElement\n\n if (!source) return\n\n let delay = parseInt(source.dataset.delay) || 250\n if (delay < 250) {\n console.warn(\"Input delay < 250 can result in poor performance.\")\n }\n\n if (!source?.dataset.oninput) {\n console.error(\"Missing onInput: \", source)\n return\n }\n\n e.preventDefault()\n\n let target = nearestTarget(source)\n\n if (!source.debouncedCallback) {\n source.debouncedCallback = debounce(() => {\n let action = encodedParam(source.dataset.oninput, source.value)\n cb(target, action)\n }, delay)\n }\n\n source.debouncedCallback()\n })\n}\n\n\n\nexport function listenFormSubmit(cb: (target: HTMLElement, action: string, form: FormData) => void): void {\n document.addEventListener(\"submit\", function(e) {\n let form = e.target as HTMLFormElement\n\n if (!form?.dataset.onsubmit) {\n console.error(\"Missing onSubmit: \", form)\n return\n }\n\n e.preventDefault()\n\n let target = nearestTarget(form)\n const formData = new FormData(form)\n cb(target, form.dataset.onsubmit, formData)\n })\n}\n\nfunction nearestTargetId(node: HTMLElement): string | undefined {\n let targetData = node.closest(\"[data-target]\") as HTMLElement | undefined\n return targetData?.dataset.target || node.closest(\"[id]\")?.id\n}\n\nfunction nearestTarget(node: HTMLElement): HTMLElement {\n let targetId = nearestTargetId(node)\n let target = document.getElementById(targetId)\n\n if (!target) {\n console.error(\"Cannot find target: \", targetId, node)\n return\n }\n\n return target\n}\n","import { ActionMessage, splitMetadata, ParsedResponse } from './action'\nimport { Response, FetchError } from \"./response\"\n\nexport async function sendActionHttp(msg: ActionMessage): Promise<Response> {\n // console.log(\"HTTP sendAction\", msg.url.toString())\n let url = window.location.href\n let res = await fetch(url, {\n method: \"POST\",\n headers:\n {\n 'Accept': 'text/html',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'Hyp-RequestId': msg.requestId,\n 'Hyp-ViewId': msg.viewId,\n 'Hyp-Action': msg.action\n },\n body: msg.form,\n // we never want this to be redirected\n redirect: \"manual\"\n })\n\n let body = await res.text()\n let { metadata, rest } = parseMetadataHttp(body)\n\n if (!res.ok) {\n throw new FetchError(msg.viewId, body, body)\n }\n\n let response: Response = {\n meta: metadata,\n body: rest.join('\\n')\n }\n\n return response\n}\n\n\nexport function parseMetadataHttp(inp: string): ParsedResponse {\n let lines = inp.split(\"\\n\")\n // drop the <script> start line\n let { metadata, rest } = splitMetadata(lines.slice(1))\n // drop the </script> end line and 2x whitespace\n return { metadata, rest: rest.slice(2) }\n}\n\n\n","import { patch, create } from \"omdomdom/lib/omdomdom.es.js\"\nimport { SocketConnection } from './sockets'\nimport { listenChange, listenClick, listenDblClick, listenFormSubmit, listenLoad, listenTopLevel, listenInput, listenKeydown, listenKeyup, listenMouseEnter, listenMouseLeave } from './events'\nimport { actionMessage, ActionMessage, requestId, ViewId, parseMetadata, Metadata } from './action'\nimport { sendActionHttp } from './http'\nimport { setQuery } from \"./browser\"\nimport { parseResponse, Response, LiveUpdate } from './response'\n\nlet PACKAGE = require('../package.json');\n\n\n// console.log(\"VERSION 2\", INIT_PAGE, INIT_STATE)\nconsole.log(\"Hyperbole \" + PACKAGE.version)\n\n\nlet rootStyles: HTMLStyleElement;\nlet addedRulesIndex = new Set();\n\n\nasync function sendAction(msg: ActionMessage): Promise<Response> {\n if (sock.isConnected) {\n return sock.sendAction(msg)\n }\n else {\n return sendActionHttp(msg)\n }\n}\n\n\n\nasync function runAction(target: HyperView, action: string, form?: FormData) {\n // console.log(\"runAction\", target.id, action)\n\n if (target === undefined) {\n console.error(\"Undefined HyperView!\")\n return\n }\n\n if (action === undefined) {\n console.error(\"Undefined Action!\", target, \"this is a bug, please report: https://github.com/seanhess/hyperbole\")\n return\n }\n\n let timeout = setTimeout(() => {\n // add loading after 100ms, not right away\n // if it runs shorter than that we probably don't want to add loading effects\n target.classList.add(\"hyp-loading\")\n }, 100)\n\n let reqId = requestId()\n let msg = actionMessage(target.id, action, reqId, form)\n\n // Ignore any request if a requestId is active\n if (target.dataset.requestId) {\n console.warn(\"Action overlaps with active request (\" + target.dataset.requestId + \")\", action)\n return\n }\n\n // Set the requestId\n target.dataset.requestId = reqId\n\n try {\n let res: Response = await sendAction(msg)\n\n if (res.meta.requestId != target.dataset.requestId) {\n let err = new Error()\n err.name = \"Concurrency Error\"\n err.message = \"Stale Action (\" + reqId + \"):\" + action\n throw err\n }\n else {\n delete target.dataset.requestId\n }\n\n let shouldStop = runMetadataImmediate(res.meta)\n\n if (shouldStop) {\n return\n }\n\n let update: LiveUpdate = parseResponse(res.body)\n\n if (!update.content) {\n console.error(\"Empty Response!\", res.body)\n return\n }\n\n // First, update the stylesheet\n addCSS(update.css)\n\n\n // Patch the node\n const old: VNode = create(target)\n let next: VNode = create(update.content)\n next.attributes = old.attributes\n patch(next, old)\n\n\n // Emit relevant events\n let newTarget = document.getElementById(target.id)\n dispatchContent(newTarget)\n\n if (newTarget) {\n // execute the metadata, anything that doesn't interrupt the dom update\n runMetadataDOM(res.meta, newTarget)\n\n // now way for these to bubble)\n listenLoad(newTarget)\n listenMouseEnter(newTarget)\n listenMouseLeave(newTarget)\n fixInputs(newTarget)\n enrichHyperViews(newTarget)\n }\n else {\n console.warn(\"Target Missing: \", target.id)\n }\n\n }\n catch (err) {\n console.error(\"Caught Error in HyperView (\" + target.id + \"):\\n\", err)\n\n // Hyperbole catches handler errors, and the server controls what to display to the user on an error\n // but if you manage to crash your parent server process somehow, the response may be empty\n target.innerHTML = err.body || \"<div style='background:red;color:white;padding:10px'>Hyperbole Internal Error</div>\"\n }\n\n\n // Remove loading and clear add timeout\n clearTimeout(timeout)\n target.classList.remove(\"hyp-loading\")\n}\n\nfunction runMetadataImmediate(meta: Metadata): boolean {\n if (meta.redirect) {\n // perform a redirect immediately\n window.location.href = meta.redirect\n return true\n }\n\n if (meta.query != null) {\n setQuery(meta.query)\n }\n\n if (meta.pageTitle != null) {\n document.title = meta.pageTitle\n }\n}\n\nfunction runMetadataDOM(meta: Metadata, target?: HTMLElement) {\n for (var remoteEvent of meta.events) {\n setTimeout(() => {\n // console.log(\"dipsatching custom event\", remoteEvent)\n let event = new CustomEvent(remoteEvent.name, { bubbles: true, detail: remoteEvent.detail })\n let eventTarget = target || document\n eventTarget.dispatchEvent(event)\n }, 10)\n }\n\n meta.actions.forEach(([viewId, action]) => {\n setTimeout(() => {\n let view = window.Hyperbole.hyperView(viewId)\n if (view) {\n runAction(view, action)\n }\n }, 10)\n })\n}\n\n\nfunction fixInputs(target: HTMLElement) {\n let focused = target.querySelector(\"[autofocus]\") as HTMLInputElement\n if (focused?.focus) {\n focused.focus()\n }\n\n target.querySelectorAll(\"input[value]\").forEach((input: HTMLInputElement) => {\n let val = input.getAttribute(\"value\")\n if (val !== undefined) {\n input.value = val\n }\n })\n\n target.querySelectorAll(\"input[type=checkbox]\").forEach((checkbox: HTMLInputElement) => {\n let checked = checkbox.dataset.checked == \"True\"\n checkbox.checked = checked\n })\n}\n\nfunction addCSS(src: HTMLStyleElement | null) {\n if (!src) return;\n const rules: any = src.sheet.cssRules\n for (const rule of rules) {\n if (addedRulesIndex.has(rule.cssText) == false) {\n rootStyles.sheet.insertRule(rule.cssText);\n addedRulesIndex.add(rule.cssText);\n }\n }\n}\n\n\n\n\nfunction init() {\n // metadata attached to initial page loads need to be executed\n let meta = parseMetadata(document.getElementById(\"hyp.metadata\").innerText)\n runMetadataImmediate(meta)\n runMetadataDOM(meta)\n\n rootStyles = document.querySelector('style')\n\n listenTopLevel(async function(target: HyperView, action: string) {\n runAction(target, action)\n })\n\n listenLoad(document.body)\n listenMouseEnter(document.body)\n listenMouseLeave(document.body)\n enrichHyperViews(document.body)\n\n\n listenClick(async function(target: HyperView, action: string) {\n // console.log(\"CLICK\", target.id, action)\n runAction(target, action)\n })\n\n listenDblClick(async function(target: HyperView, action: string) {\n // console.log(\"DBLCLICK\", target.id, action)\n runAction(target, action)\n })\n\n listenKeydown(async function(target: HyperView, action: string) {\n // console.log(\"KEYDOWN\", target.id, action)\n runAction(target, action)\n })\n\n listenKeyup(async function(target: HyperView, action: string) {\n // console.log(\"KEYUP\", target.id, action)\n runAction(target, action)\n })\n\n listenFormSubmit(async function(target: HyperView, action: string, form: FormData) {\n // console.log(\"FORM\", target.id, action, form)\n runAction(target, action, form)\n })\n\n listenChange(async function(target: HyperView, action: string) {\n console.log(\"CHANGE\", target.id, \"(\" + action + \")\")\n runAction(target, action)\n })\n\n listenInput(async function(target: HyperView, action: string) {\n runAction(target, action)\n })\n}\n\n\n\nfunction enrichHyperViews(node: HTMLElement): void {\n // enrich all the hyperviews\n node.querySelectorAll(\"[id]\").forEach((element: HyperView) => {\n element.runAction = function(action: string) {\n runAction(this, action)\n }.bind(element)\n\n dispatchContent(node)\n })\n}\n\nfunction dispatchContent(node: HTMLElement): void {\n let event = new Event(\"hyp-content\", { bubbles: true })\n node.dispatchEvent(event)\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", init)\n\n\n\n\n// Should we connect to the socket or not?\nconst sock = new SocketConnection()\nsock.connect()\n\n\n\n\n\n\ntype VNode = {\n // One of three value types are used:\n // - The tag name of the element\n // - \"text\" if text node\n // - \"comment\" if comment node\n type: string\n\n // An object whose key/value pairs are the attribute\n // name and value, respectively\n attributes: [string: string]\n\n // Is set to `true` if a node is an `svg`, which tells\n // Omdomdom to treat it, and its children, as such\n isSVGContext: Boolean\n\n // The content of a \"text\" or \"comment\" node\n content: string\n\n // An array of virtual node children\n children: Array<VNode>\n\n // The real DOM node\n node: Node\n}\n\n\n\n\n\ndeclare global {\n interface Window {\n Hyperbole?: HyperboleAPI;\n }\n}\n\nexport interface HyperboleAPI {\n runAction(target: HTMLElement, action: string, form?: FormData): Promise<void>\n action(con: string, ...params: any[]): string\n hyperView(viewId: ViewId): HyperView | undefined\n parseMetadata(input: string): Metadata\n socket: SocketConnection\n}\n\n\n\n\nexport interface HyperView extends HTMLElement {\n runAction(target: HTMLElement, action: string, form?: FormData): Promise<void>\n}\n\n\n\nwindow.Hyperbole =\n{\n runAction: runAction,\n parseMetadata: parseMetadata,\n action: function(con, ...params: any[]) {\n let ps = params.reduce((str, param) => str + \" \" + JSON.stringify(param), \"\")\n return con + ps\n },\n hyperView: function(viewId) {\n let element = document.getElementById(viewId) as any\n if (!element?.runAction) {\n console.error(\"Element id=\" + viewId + \" was not a HyperView\")\n return undefined\n }\n return element\n },\n socket: sock\n}\n","\nexport function setQuery(query: string) {\n if (query != currentQuery()) {\n if (query != \"\") query = \"?\" + query\n let url = location.pathname + query\n // console.log(\"history.replaceState(\", url, \")\")\n window.history.replaceState({}, \"\", url)\n }\n}\n\nfunction currentQuery(): string {\n const query = window.location.search;\n return query.startsWith('?') ? query.substring(1) : query;\n}\n"],"names":["debounce","function_","wait","options","TypeError","RangeError","immediate","storedContext","storedArguments","timeoutId","timestamp","result","run","callContext","callArguments","undefined","apply","later","last","Date","now","setTimeout","debounced","arguments_","this","Object","getPrototypeOf","Error","callNow","defineProperty","get","clear","clearTimeout","flush","trigger","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__","hasProperty","obj","prop","prototype","hasOwnProperty","call","forEach","items","fn","length","idx","insertBefore","parent","child","refNode","node","_slicedToArray","arr","i","Array","isArray","_arrayWithHoles","_i","Symbol","iterator","_s","_e","_x","_r","_arr","_n","_d","next","done","push","value","err","return","_iterableToArrayLimit","o","minLen","_arrayLikeToArray","n","toString","slice","constructor","name","from","test","_unsupportedIterableToArray","_nonIterableRest","len","arr2","Types","DomProperties","createRecord","attrName","propName","type","_ref","_ref2","attr","property","_ref3","_ref4","Namespace","setProperty","element","style","toLowerCase","removeAttribute","parsed","parseInt","isNaN","indexOf","patch","template","vNode","rootNode","parentNode","contentChanged","content","replaceChild","assignVNode","removedAttributes","changedAttributes","attributes","oldValue","nextValue","_attrName","_oldValue","_nextValue","propertyRecord","removeAttributes","startsWith","setAttributeNS","setAttribute","setAttributes","updateAttributes","templateChildrenLength","children","vNodeChildrenLength","vNodeKeyMap","map","_","key","console","warn","keyIsValid","createKeyMap","nextChildren","templateChild","childNodes","keyedChild","vNodeChild","appendChild","childNodesLength","delta","removeChild","patchChildren","create","processedDOMString","isSVGContext","arguments","trim","replace","DOMParser","parseFromString","body","isRoot","tagName","numChildNodes","nodeType","isSVG","reduce","getBaseAttributes","attrValue","getAttribute","getPropertyValues","getAttributes","textContent","toSearch","form","params","URLSearchParams","append","parseMetadata","input","splitMetadata","split","metadata","lines","meta","requestId","metas","pred","output","a","takeWhileMap","parseMeta","rest","find","m","cookies","filter","redirect","error","query","events","breakNextSegment","data","detail","JSON","parse","parseRemoteEvent","actions","parseAction","pageTitle","ix","message","line","match","encodedParam","action","param","sanitizeParam","viewId","msg","protocol","window","location","defaultAddress","host","pathname","reconnectDelay","connect","addr","sock","WebSocket","onConnectError","ev","log","socket","addEventListener","_event","hasEverConnected","document","dispatchEvent","Event","isConnected","removeEventListener","sendAction","join","renderActionMessage","fetch","reqId","id","sendMessage","waitMessage","send","Promise","resolve","reject","onMessage","event","cookie","FetchError","disconnect","close","listenKeyEvent","cb","e","source","target","datasetKey","dataset","preventDefault","nearestTarget","listenBubblingEvent","closest","listenLoad","querySelectorAll","load","delay","onLoad","onload","CustomEvent","bubbles","listenMouseEnter","onMouseEnter","onmouseenter","listenMouseLeave","onMouseLeave","onmouseleave","targetId","targetData","nearestTargetId","getElementById","sendActionHttp","url","href","method","headers","res","text","inp","parseMetadataHttp","ok","rootStyles","PACKAGE","version","addedRulesIndex","Set","runAction","timeout","classList","add","Math","random","substring","decodeURI","search","actionMessage","runMetadataImmediate","update","doc","css","querySelector","parseResponse","src","sheet","cssRules","rule","has","cssText","insertRule","addCSS","old","dispatchContent","newTarget","runMetadataDOM","focused","focus","val","checkbox","checked","fixInputs","enrichHyperViews","innerHTML","remove","currentQuery","history","replaceState","setQuery","title","remoteEvent","view","Hyperbole","hyperView","bind","innerText","onsubmit","formData","FormData","onchange","oninput","debouncedCallback","SocketConnection","con","str","stringify"],"sourceRoot":""}
+ client/util/live-reload.js view
@@ -0,0 +1,48 @@+// This isn't magic. If you want custom behavior, copy and modify this however you like. +//+// As with any custom js, add to a single page via the `script` combinator+// page = do+// pure $ do+// el "This is my page"+// script "custom.js"+//+// or to the entire app by adding a script tag to your document function. See Example.App.toDocument+//+// Consider conditionally adding it based on ENV+console.log("Live Reload enabled")++function showNotification(message) {+ const notification = document.createElement('div');+ notification.classList.add("live-reload")+ notification.innerHTML = message;+ jackIn(notification.style)+ notification.addEventListener('click', function() {+ notification.remove()+ })+ document.body.appendChild(notification);+}++document.addEventListener("hyp-socket-disconnect", () => {+ showNotification("DISCONNECTED - will reload on reconnect")+})++document.addEventListener("hyp-socket-reconnect", () => {+ setTimeout(() => {+ location.reload()+ }, 0)+})+++// duplicate cyber style stuff here so the default live reload is fun+function jackIn(style) {+ style.position = 'fixed';+ style.bottom = '15px';+ style.left = '15px';+ style.right = '15px';+ style.backgroundColor = 'rgba(160, 63, 56, 1.0)';+ style.color = '#fff';+ style.borderTop = 'solid #EC6458 4px';+ style.padding = '15px';+ style.zIndex = '1000';+ style.clipPath = 'polygon(0 0, 100% 0, 100% calc(100% - 16px), calc(100% - 16px) 100%, 0 100%)';+}
hyperbole.cabal view
@@ -5,9 +5,9 @@ -- see: https://github.com/sol/hpack name: hyperbole-version: 0.4.3+version: 0.5.0 synopsis: Interactive HTML apps using type-safe serverside Haskell-description: Interactive serverside web framework Inspired by HTMX, Elm, and Phoenix LiveView+description: Interactive HTML applications with type-safe serverside Haskell. Inspired by HTMX, Elm, and Phoenix LiveView. category: Web, Network homepage: https://github.com/seanhess/hyperbole bug-reports: https://github.com/seanhess/hyperbole/issues@@ -21,6 +21,8 @@ , GHC == 9.6.6 extra-source-files: client/dist/hyperbole.js+ client/dist/hyperbole.js.map+ client/util/live-reload.js extra-doc-files: README.md CHANGELOG.md@@ -33,25 +35,47 @@ exposed-modules: Web.Hyperbole Web.Hyperbole.Application+ Web.Hyperbole.Data.Cookie+ Web.Hyperbole.Data.Encoded+ Web.Hyperbole.Data.JSON+ Web.Hyperbole.Data.Param Web.Hyperbole.Data.QueryData- Web.Hyperbole.Data.Session- Web.Hyperbole.Effect.Event- Web.Hyperbole.Effect.Handler+ Web.Hyperbole.Data.URI+ Web.Hyperbole.Document+ Web.Hyperbole.Effect.Client+ Web.Hyperbole.Effect.GenRandom Web.Hyperbole.Effect.Hyperbole+ Web.Hyperbole.Effect.OAuth2 Web.Hyperbole.Effect.Query Web.Hyperbole.Effect.Request Web.Hyperbole.Effect.Response- Web.Hyperbole.Effect.Server Web.Hyperbole.Effect.Session Web.Hyperbole.HyperView+ Web.Hyperbole.HyperView.Event+ Web.Hyperbole.HyperView.Forms+ Web.Hyperbole.HyperView.Handled+ Web.Hyperbole.HyperView.Input+ Web.Hyperbole.HyperView.Types+ Web.Hyperbole.HyperView.ViewAction+ Web.Hyperbole.HyperView.ViewId Web.Hyperbole.Page Web.Hyperbole.Route+ Web.Hyperbole.Server.Handler+ Web.Hyperbole.Server.Message+ Web.Hyperbole.Server.Options+ Web.Hyperbole.Server.Socket+ Web.Hyperbole.Server.Wai Web.Hyperbole.TypeList+ Web.Hyperbole.Types.Client+ Web.Hyperbole.Types.Event+ Web.Hyperbole.Types.Request+ Web.Hyperbole.Types.Response Web.Hyperbole.View- Web.Hyperbole.View.Element+ Web.Hyperbole.View.CSS Web.Hyperbole.View.Embed- Web.Hyperbole.View.Event- Web.Hyperbole.View.Forms+ Web.Hyperbole.View.Render+ Web.Hyperbole.View.Tag+ Web.Hyperbole.View.Types other-modules: Paths_hyperbole autogen-modules:@@ -69,7 +93,11 @@ DeriveAnyClass ghc-options: -Wall -fdefer-typed-holes build-depends:- base >=4.16 && <5+ aeson >=2.1.2.1 && <2.3+ , atomic-css ==0.2.*+ , attoparsec ==0.14.*+ , attoparsec-aeson >=2.1 && <2.3+ , base >=4.16 && <5 , bytestring >=0.11 && <0.13 , casing >0.1 && <0.2 , containers >=0.6 && <1@@ -77,9 +105,14 @@ , data-default >0.8 && <0.9 , effectful >=2.4 && <3 , file-embed >=0.0.10 && <0.1+ , filepath >=1.4 && <2 , http-api-data ==0.6.*+ , http-client ==0.7.*+ , http-client-tls ==0.3.* , http-types ==0.12.* , network >=3.1 && <4+ , network-uri >=2.6.4.1 && <2.7+ , random >=1.2 && <2 , string-conversions ==0.4.* , string-interpolate ==0.3.* , text >=1.2 && <3@@ -87,7 +120,6 @@ , wai >=3.2 && <4 , wai-websockets >=3.0 && <4 , warp >=3.3 && <4- , web-view >=0.7 && <=1.0 , websockets >=0.12 && <0.14 default-language: GHC2021 @@ -95,9 +127,13 @@ type: exitcode-stdio-1.0 main-is: Spec.hs other-modules:+ Test.EncodedSpec Test.FormSpec+ Test.ParamSpec Test.QuerySpec Test.RouteSpec+ Test.SessionSpec+ Test.URISpec Test.ViewActionSpec Test.ViewIdSpec Paths_hyperbole@@ -115,8 +151,14 @@ DerivingStrategies DeriveAnyClass ghc-options: -Wall -fdefer-typed-holes -threaded -rtsopts -with-rtsopts=-N -F -pgmF=skeletest-preprocessor+ build-tool-depends:+ skeletest:skeletest-preprocessor build-depends:- base >=4.16 && <5+ aeson >=2.1.2.1 && <2.3+ , atomic-css ==0.2.*+ , attoparsec ==0.14.*+ , attoparsec-aeson >=2.1 && <2.3+ , base >=4.16 && <5 , bytestring >=0.11 && <0.13 , casing >0.1 && <0.2 , containers >=0.6 && <1@@ -124,10 +166,15 @@ , data-default >0.8 && <0.9 , effectful >=2.4 && <3 , file-embed >=0.0.10 && <0.1+ , filepath >=1.4 && <2 , http-api-data ==0.6.*+ , http-client ==0.7.*+ , http-client-tls ==0.3.* , http-types ==0.12.* , hyperbole , network >=3.1 && <4+ , network-uri >=2.6.4.1 && <2.7+ , random >=1.2 && <2 , skeletest , string-conversions ==0.4.* , string-interpolate ==0.3.*@@ -136,6 +183,5 @@ , wai >=3.2 && <4 , wai-websockets >=3.0 && <4 , warp >=3.3 && <4- , web-view >=0.7 && <=1.0 , websockets >=0.12 && <0.14 default-language: GHC2021
src/Web/Hyperbole.hs view
@@ -9,13 +9,13 @@ Create fully interactive HTML applications with type-safe serverside Haskell. Inspired by [HTMX](https://htmx.org/), [Elm](https://elm-lang.org/), and [Phoenix LiveView](https://www.phoenixframework.org/) -} module Web.Hyperbole- ( -- * Introduction+ ( -- * Introduction #intro# -- $use - -- * Getting started+ -- * Getting started #start# -- $hello - -- ** HTML Views+ -- ** HTML Views #views# -- $view-functions-intro -- ** Interactive HyperViews #hyperviews#@@ -24,59 +24,74 @@ -- ** View Functions #viewfunctions# -- $view-functions - -- * Managing State+ -- * Managing State #state# -- $state-parameters - -- ** Side Effects+ -- ** Side Effects #side-effects# -- $state-effects - -- ** Databases and Custom Effects+ -- ** Databases and Custom Effects #databases# -- $state-databases - -- * Multiple HyperViews+ -- * Multiple HyperViews #hyperview-multi# -- $practices-multi - -- ** Copies+ -- ** Same HyperView, Unique ViewId #hyperview-same# -- $practices-same - -- ** Nesting+ -- ** Different HyperViews+ -- $practices-diff++ -- ** Nesting HyperViews #hyperview-nested# -- $practices-nested -- * Functions, not Components #reusable# -- $reusable - -- * Pages #pages#+ -- * Pages and Routes #pages# -- $practices-pages - -- * Examples+ -- * Examples #examples# -- $examples - -- * Application+ -- * Application #application# liveApp , Warp.run- , basicDocument++ -- ** Page , Page , runPage - -- ** Type-Safe Routes- , routeRequest -- maybe belongs in an application section+ -- ** Document+ , document+ , quickStartDocument+ , DocumentHead+ , quickStart+ , mobileFriendly++ -- ** Type-Safe Routes #routes# , Route (..)- , routeUrl+ , routeRequest -- maybe belongs in an application section+ , routeUri , route - -- * Hyperbole Effect+ -- * Hyperbole Effect #hyperbole-effect# , Hyperbole - -- ** Response- , respondEarly- , notFound- , redirect-- -- ** Request+ -- ** Request #request# , request , Request (..) - -- ** Query Params #query#+ -- ** Response #response#+ , respondError+ , respondErrorView+ , notFound+ , redirect++ -- ** Query #query#+ -- $query+ , ToQuery (..)+ , FromQuery (..) , query , setQuery , param@@ -86,20 +101,28 @@ , queryParams -- ** Sessions #sessions#+ -- $sessions+ , Session (..) , session , saveSession , lookupSession , modifySession , modifySession_ , deleteSession- , Session (..) - -- * HyperView+ -- ** Control Client #client#+ , pageTitle+ , trigger+ , pushEvent++ -- * HyperView #hyperview# , HyperView (..)+ , ViewId+ , ViewAction , hyper , HasViewId (..) - -- * Interactive Elements+ -- * Interactive Elements #interactive# , button , search , dropdown@@ -109,22 +132,25 @@ -- * Events , onClick , onDblClick+ , onMouseEnter+ , onMouseLeave , onInput+ , onLoad+ , DelayMs , onKeyDown , onKeyUp- , onLoad- , onRequest , Key (..)- , DelayMs - -- * Type-Safe Forms-- -- | Painless forms with type-checked field names, and support for validation. See [Example.Forms](https://docs.hyperbole.live/formsimple)+ -- * Type-Safe Forms #forms#+ -- $forms+ , FromForm (..)+ , FromFormF (..) , formData- , Form (..)- , formFields- , formFieldsWith- , FormField+ , GenFields (..)+ , fieldNames+ , FieldName (..)+ , FormFields+ -- , FormField (..) , Field , Identity @@ -133,44 +159,43 @@ , field , label , input+ , checkbox+ , radioGroup+ , radio+ , select+ , checked , textarea , submit- , placeholder+ , View.placeholder , InputType (..) -- ** Validation , Validated (..)+ , isInvalid , validate- , fieldValid , invalidText- , anyInvalid - -- * Query Param Encoding- , ToQuery (..)- , FromQuery (..)+ -- * Query Param Encoding #query-param#+ , QueryData , ToParam (..) , FromParam (..)- , QueryData- , DefaultParam (..)+ , ToEncoded+ , FromEncoded - -- * Advanced+ -- * Advanced #advanced# , target- , view , Response- , ViewId- , ViewAction , Root - -- * Exports-- -- ** Web.View+ -- * Exports #exports# - -- | Hyperbole is tightly integrated with [Web.View](https://hackage.haskell.org/package/web-view/docs/Web-View.html) for HTML generation- , module Web.View+ -- ** View+ , View (..)+ , module View -- ** Embeds - -- | Embedded CSS and Javascript to include in your document function. See 'basicDocument'+ -- | Embedded CSS and Javascript to include in your document function. See 'quickStartDocument' , module Web.Hyperbole.View.Embed -- ** Effectful@@ -178,33 +203,45 @@ , module Effectful -- ** Other+ , URI (..)+ , uri , Application- , Generic+ , module GHC.Generics+ , Default (..)+ , ToJSON+ , FromJSON ) where +import Data.Aeson (FromJSON, ToJSON)+import Data.Default import Effectful (Eff, (:>))+import GHC.Generics (Generic, Rep) import Network.Wai (Application) import Network.Wai.Handler.Warp as Warp (run)+import Web.Atomic.CSS ()+import Web.Atomic.Types () import Web.Hyperbole.Application+import Web.Hyperbole.Data.Encoded (FromEncoded, ToEncoded)+import Web.Hyperbole.Data.Param import Web.Hyperbole.Data.QueryData-import Web.Hyperbole.Data.Session+import Web.Hyperbole.Document+import Web.Hyperbole.Effect.Client import Web.Hyperbole.Effect.Hyperbole import Web.Hyperbole.Effect.Query import Web.Hyperbole.Effect.Request-import Web.Hyperbole.Effect.Response (notFound, redirect, respondEarly, view)-import Web.Hyperbole.Effect.Server+import Web.Hyperbole.Effect.Response import Web.Hyperbole.Effect.Session import Web.Hyperbole.HyperView+import Web.Hyperbole.HyperView.Forms import Web.Hyperbole.Page (Page, runPage) import Web.Hyperbole.Route-import Web.Hyperbole.View+import Web.Hyperbole.Types.Request+import Web.Hyperbole.Types.Response+import Web.Hyperbole.View hiding (placeholder)+import Web.Hyperbole.View qualified as View hiding (Attributable, Attributes, View) import Web.Hyperbole.View.Embed-import Web.Hyperbole.View.Forms-import Web.View hiding (Query, Segment, button, cssResetEmbed, form, input, label) --- import Web.View.Types.Url (Segment)- {- $use Single Page Applications (SPAs) require the programmer to write two programs: a Javascript client and a Server, which both must conform to a common API@@ -228,21 +265,20 @@ Hyperbole depends heavily on the following frameworks * [Effectful](https://hackage.haskell.org/package/effectful-core)-* [Web View](https://hackage.haskell.org/package/web-view)+* [Atomic CSS](https://hackage.haskell.org/package/atomic-css) -} {- $hello +#EXAMPLE /intro+ Hyperbole applications run via [Warp](https://hackage.haskell.org/package/warp) and [WAI](https://hackage.haskell.org/package/wai) They are divided into top-level 'Page's, which run side effects (such as loading data from a database), then respond with an HTML 'View'. The following application has a single 'Page' that displays a static "Hello World" @-{\-# LANGUAGE DeriveAnyClass #-\} {\-# LANGUAGE OverloadedStrings #-\}-{\-# LANGUAGE TypeFamilies #-\}-{\-# LANGUAGE DataKinds #-\} module Main where @@ -257,13 +293,15 @@ {- $interactive +#EXAMPLE /simple+ We can embed one or more 'HyperView's to add type-safe interactivity to live subsections of our 'Page'. To start, first define a data type (a 'ViewId') that uniquely identifies that subsection of the page: @ #EMBED Example/Docs/Interactive.hs data Message @ -Make our 'ViewId' an instance of 'HyperView' by:+Make our 'ViewId' an instance of 'HyperView': * Create an 'Action' type with a constructor for every possible way that the user can interact with it * Write an 'update' for each 'Action'@@ -272,15 +310,15 @@ #EMBED Example/Docs/Interactive.hs instance HyperView Message @ -If an 'Action' occurs, the contents of our 'HyperView' will be replaced with 'update'.+If an 'Action' occurs, the contents of our 'HyperView' will be replaced with the result of 'update'. -To embed our new 'HyperView', add the 'ViewId' to the type-level list of 'Page', and then wrap the view in 'hyper'.+To use our new 'HyperView', add the 'ViewId' to the type-level list of 'Page', and then place it in the page view with 'hyper'. @ #EMBED Example/Docs/Interactive.hs page @ -Now let's add a button to trigger the 'Action'. Note that we must update the 'View'\'s 'context' to match our 'ViewId'. The compiler will tell us if we try to trigger actions that don't belong to our 'HyperView'+Now let's add a button to trigger the 'Action'. Note that we must now update the 'View'\'s 'context' to match our 'ViewId'. The compiler will tell us if we try to trigger actions that don't belong to our 'HyperView' @ #EMBED Example/Docs/Interactive.hs messageView@@ -292,27 +330,32 @@ {- $view-functions-intro -'View's are HTML fragments with embedded CSS+'View's are HTML fragments with a 'context' @ #EMBED Example/Docs/BasicPage.hs helloWorld @ ->>> Web.View.renderText $ el bold "Hello World"-<style type='text/css'>.bold { font-weight:bold }</style>-<div class='bold'>Hello World</div>+>>> renderText helloWorld+<div>Hello World</div> We can factor 'View's into reusable functions: +#EXAMPLE /simple+ @ #EMBED Example/Docs/BasicPage.hs messageView #EMBED Example/Docs/BasicPage.hs page' @ -We use plain functions to maintain a consistent look and feel rather than stylesheets:+Using [atomic-css](https://hackage.haskell.org/package/atomic-css) we can use functions to factor styles as well +#EXAMPLE /css+ @+import Web.Atomic.CSS+ header = bold h1 = header . fontSize 32 h2 = header . fontSize 24@@ -321,18 +364,16 @@ example = col page $ do el h1 "My Page" @--See [Web.View](https://hackage.haskell.org/package/web-view) for more details -} {- $view-functions -We showed above how we can factor 'View's into functions. It's best-practice to have a __main__ 'View' for each 'HyperView'. These take the form:+We showed above how we can factor 'View's into functions. It's best-practice to have a main 'View' function for each 'HyperView'. These take the form: > state -> View viewId () -There's nothing special about `state` or 'View' functions. They're just functions that take parameters and return a view.+There's nothing special about `state` or 'View' functions. They're just functions that take input data and return a view. We can write multiple view functions with our 'HyperView' as the 'context', and factor them however is most convenient: @@ -340,13 +381,13 @@ #EMBED Example/Docs/ViewFunctions.hs messageButton @ -We can also create 'View' functions that work in any 'context':+Some 'View' functions can be used in any 'context': @ #EMBED Example/Docs/ViewFunctions.hs header @ -Then we can refactor our main 'View' to use view functions to avoid repeating ourselves+With those two functions defined, we can refactor our main 'View' to use them and avoid repeating ourselves @ #EMBED Example/Docs/ViewFunctions.hs messageView@@ -367,21 +408,13 @@ {- $practices-multi -We can add as many 'HyperView's to a page as we want. Let's create another 'HyperView' for a simple counter--From [Example.Docs.MultiView](https://github.com/seanhess/hyperbole/blob/0.4/example/Example/Docs/MultiView.hs)--@-#EMBED Example/Docs/MultiView.hs data Count---#EMBED Example/Docs/MultiView.hs instance HyperView Count+We can add as many 'HyperView's to a page as we want. These can be muliple copies of the same 'HyperView' with unique 'ViewId' values, or completely different 'HyperView's.+-} -#EMBED Example/Docs/MultiView.hs countView-@+{- $practices-diff -We can use both 'Message' and 'Count' 'HyperView's in our page, and they will update independently:+Let's add both 'Count' and 'Message' 'HyperView's to the same page. Each will update independently: @ #EMBED Example/Docs/MultiView.hs page@@ -391,9 +424,9 @@ {- $practices-same -We can embed multiple copies of the same 'HyperView' as long as the value of 'ViewId' is unique. Let's update `Message` to allow for more than one value:+We can embed more than one of the same 'HyperView' as long as the value of 'ViewId' is unique. Let's update `Message` to allow for more than one value: -See [Example.Page.Simple](https://docs.hyperbole.live/simple)+▶️ #EXAMPLE /simple @ #EMBED Example/Docs/MultiCopies.hs data Message@@ -406,33 +439,33 @@ @ -This is especially useful if we put identifying information in our 'ViewId', such as a database id. The 'viewId' function gives us access to that info. See the [Contacts Example](https://docs.hyperbole.live/contacts) and the [Lazy Loading Example](https://docs.hyperbole.live/lazyloading)+This is especially useful if we put identifying information in our 'ViewId', such as a database id. The 'viewId' function can give us access to that info: -From [Example.Page.LazyLoading](https://docs.hyperbole.live/lazyloading)+#EXAMPLE /data/loadmore @-#EMBED Example/Page/LazyLoading.hs data LazyData+#EMBED Example/Page/DataLists/LoadMore.hs data Languages -#EMBED Example/Page/LazyLoading.hs instance (Debug :> es, GenRandom :> es) => HyperView+#EMBED Example/Page/DataLists/LoadMore.hs instance HyperView Languages @ -} {- $practices-pages -An app has multiple 'Page's with different 'Route's that each map to a unique url path:+An app will usually have multiple 'Page's with different 'Route's that each map to a unique url path: @ #EMBED Example/Docs/MultiPage.hs data AppRoute @ -When we define our app, we define a function that maps a 'Route' to a 'Page'+When we create our app, we can add a router function which maps a 'Route' to a 'Page' with 'routeRequest'. The web page is completely reloaded each time you switch routes. Each 'Page' is completely isolated. @ #EMBED Example/Docs/MultiPage.hs main @ -Each 'Page' is completely independent. The web page is freshly reloaded each time you switch routes. We can add type-safe links to other pages using 'route'+We can add type-safe links to other pages using 'route' @ #EMBED Example/Docs/MultiPage.hs menu@@ -452,11 +485,11 @@ {- $practices-nested -We can nest smaller, specific 'HyperView's inside of a larger parent. You might need this technique to display a list of items which need to update themselves+We can nest smaller, more specific 'HyperView's inside of a larger parent. You might need this technique to display a list of items which might also need to update themselves individually -Let's imagine we want to display a list of Todos. The user can mark individual todos complete, and have them update independently. The specific 'HyperView' might look like this:+Let's imagine we want to display a list of Todos. The user can mark individual todos complete, and have them update independently. The more specific 'HyperView' for each item might look like this: -From [Example.Docs.Nested](https://github.com/seanhess/hyperbole/blob/0.4/example/Example/Docs/Nested.hs)+#EXAMPLE /examples/todos @ #EMBED Example/Docs/Nested.hs data TodoItem@@ -466,7 +499,7 @@ But we also want the entire list to refresh when a user adds a new todo. We need to create a parent 'HyperView' for the whole list. -List all allowed nested views by adding them to 'Require'+Add any nested 'HyperView's to 'Require' to make sure they are handled. The compiler will let you know if you forget @ #EMBED Example/Docs/Nested.hs data AllTodos@@ -474,12 +507,11 @@ #EMBED Example/Docs/Nested.hs instance HyperView AllTodos @ -Then we can embed the child 'HyperView' into the parent with 'hyper'+Then we can embed the child 'HyperView' into the parent 'View' just like we do on a 'Page', by using 'hyper' @ #EMBED Example/Docs/Nested.hs todosView @-See this technique used in the [TodoMVC Example](https://docs.hyperbole.live/todos) -} @@ -487,52 +519,54 @@ You may be tempted to use 'HyperView's to create reusable \"Components\". This leads to object-oriented designs that don't compose well. We are using a functional language, so our main unit of reuse should be functions! -We showed earlier that we can write a [View Function](#g:view-functions) with a generic 'context' that we can reuse in any view. A function like this might help us reuse styles:+We showed earlier that we can write a [View Function](#g:view-functions) with a generic 'context' that we can reuse in any view. A function like this might help us reuse styles or layout: @ #EMBED Example/Docs/ViewFunctions.hs header @ -What if we want to reuse functionality too? We can pass an 'Action' into the view function as a parameter:+But what if we want to reuse interactivity? We can pass an 'Action' into the view function as a parameter: @ #EMBED Example/Docs/Component.hs styledButton @ -We can create more complex view functions by passing state in as a parameter. Here's a button that toggles between a checked and unchecked state:+We can create more complex view functions by passing state in as a parameter. Here's a button that toggles between a checked and unchecked state for any 'HyperView': @-#EMBED Example/View/Inputs.hs toggleCheckBtn+#EMBED Example/View/Inputs.hs toggleCheckbox @ -View functions can wrap other Views:+View functions can be containers which wrap other Views: @ #EMBED Example/View/Inputs.hs progressBar @ -Don't leverage 'HyperView's for code reuse. Think about which subsections of a page ought to update independently. Those are 'HyperView's. If you need reusable functionality, use [view functions](#g:viewfunctions) instead.+Don't use 'HyperView's to keep your code DRY. Think about which subsections of a page ought to update independently. Those are 'HyperView's. If you need reusable interactivity, use [view functions](#g:viewfunctions) whenever possible. See the following example for a more complicated example. -* See [Example.View.DataTable](https://docs.hyperbole.live/datatable) for a more complex example+#EXAMPLE /data/sortabletable -} {- $examples-https://docs.hyperbole.live is full of live examples demonstrating different features. Each example includes a link to the source code. Some highlights:+[hyperbole.live](https://hyperbole.live) is full of live examples demonstrating different features. Each example includes a link to the source code. Some highlights: -* [Simple](https://docs.hyperbole.live/simple)-* [Counter](https://docs.hyperbole.live/counter)-* [CSS Transitions](https://docs.hyperbole.live/transitions)-* [Lazy Loading](https://docs.hyperbole.live/lazyloading)-* [Forms](https://docs.hyperbole.live/formsimple)-* [Data Table](https://docs.hyperbole.live/datatable)-* [Sessions](https://docs.hyperbole.live/sessions)-* [Filter Items](https://docs.hyperbole.live/filter)-* [Autocomplete](https://docs.hyperbole.live/autocomplete)-* [Todo MVC](https://docs.hyperbole.live/todos)+* ▶️ #EXAMPLE /simple+* ▶️ #EXAMPLE /counter+* ▶️ #EXAMPLE /concurrency+* ▶️ #EXAMPLE /state+* ▶️ #EXAMPLE /requests+* ▶️ #EXAMPLE /data+* ▶️ #EXAMPLE /forms+* ▶️ #EXAMPLE /interactivity+* ▶️ #EXAMPLE /errors+* ▶️ #EXAMPLE /oauth2+* ▶️ #EXAMPLE /javascript+* ▶️ #EXAMPLE /advanced -The [National Solar Observatory](https://nso.edu) uses Hyperbole for the Level 2 Data creation tool for the [DKIST telescope](https://nso.edu/telescopes/dki-solar-telescope/). It is completely [open source](https://github.com/DKISTDC/level2/). This production application contains complex interfaces, workers, databases, and more.+The [National Solar Observatory](https://nso.edu) uses Hyperbole to manage Level 2 Data pipelines for the [DKIST telescope](https://nso.edu/telescopes/dki-solar-telescope/). It uses complex user interfaces, workers, databases, and more. [The entire codebase is open source](https://github.com/DKISTDC/level2/). -} @@ -540,12 +574,12 @@ 'HyperView's are stateless. They 'update' based entirely on the 'Action'. However, we can track simple state by passing it back and forth between the 'Action' and the 'View' -From [Example.Page.Simple](https://docs.hyperbole.live/simple)+#EXAMPLE /counter @-#EMBED Example/Docs/State.hs instance HyperView Message+#EMBED Example/Page/Counter.hs instance HyperView Counter -#EMBED Example/Docs/State.hs messageView+#EMBED Example/Page/Counter.hs viewCount @ -} @@ -554,9 +588,9 @@ For any real application with more complex state and data persistence, we need side effects. -Hyperbole relies on [Effectful](https://hackage.haskell.org/package/effectful) to compose side effects. We can use effects in a page or an 'update'. The 'Hyperbole' effect gives us access to the 'request' and 'Client' state, including 'session's and the 'query' 'param's. In this example the page keeps the message in the 'query' 'param's+Hyperbole relies on [Effectful](https://hackage.haskell.org/package/effectful) to compose side effects. We can use effects in a 'Page' or an 'update'. The 'Hyperbole' effect gives us access to the 'request' and client state, including 'session's and the 'query' 'param's. In this example the page keeps the message in the 'query' 'param's -From [Example.Docs.SideEffects](https://github.com/seanhess/hyperbole/blob/0.4/example/Example/Docs/SideEffects.hs)+#EXAMPLE /state/query @ #EMBED Example/Docs/SideEffects.hs page@@ -567,29 +601,29 @@ To use an 'Effect' other than 'Hyperbole', add it as a constraint to the 'Page' and any 'HyperView' instances that need it. -From [Example.Page.Counter](https://docs.hyperbole.live/counter)+#EXAMPLE /state/effects @ {\-# LANGUAGE UndecidableInstances #-\} -#EMBED Example/Page/Counter.hs instance (Reader+#EMBED Example/Page/State/Effects.hs instance (Reader @ Then run the effect in your application @-#EMBED Example/Page/Counter.hs app+#EMBED Example/Page/State/Effects.hs app @ -* Read more about [Effectful](https://hackage.haskell.org/package/effectful)+See [Effectful](https://hackage.haskell.org/package/effectful) to read more about Effects -} {- $state-databases -A database is no different from any other 'Effect'. We recommend you create a custom effect to describe high-level data operations.+A database is no different from any other 'Effect'. It is recommended to create a custom effect to describe high-level data operations. -From [Example.Effects.Todos](https://github.com/seanhess/hyperbole/blob/0.4/example/Example/Effects/Todos.hs)+#EXAMPLE /examples/todos @ #EMBED Example/Effects/Todos.hs data Todos@@ -597,27 +631,41 @@ #EMBED Example/Effects/Todos.hs loadAll @ -Once you've created an 'Effect', you add it to any 'HyperView' or 'Page' as a constraint.--From [Example.Page.Todo](https://docs.hyperbole.live/todos):+Just like any effect, to use our custom 'Effect', we add it to any 'HyperView' or 'Page' as a constraint. @ {\-# LANGUAGE UndecidableInstances #-\} -#EMBED Example/Page/Todo.hs simplePage+#EMBED Example/Page/Todos/Todo.hs simplePage @ -We run a custom effect in our Application just like any other. Here we implementing our custom effect using 'Hyperbole' 'sessions', but you could write a different runner that connects to a database instead.+We run a custom effect in our Application just like any other. The TodoMVC example implements the Todos 'Effect' using 'Hyperbole' 'sessions', but you could write a different runner that connects to a database instead. @-#EMBED Example/Page/Todo.hs main+#EMBED Example/Page/Todos/Todo.hs main @ -See [example/Main](https://github.com/seanhess/hyperbole/blob/0.4/example/Main.hs) for a full example application with multiple effects- Implementing a database runner for a custom 'Effect' is beyond the scope of this documentation, but see the following: * [Effectful.Dynamic.Dispatch](https://hackage.haskell.org/package/effectful-core/docs/Effectful-Dispatch-Dynamic.html) - Introduction to Effects * [NSO.Data.Datasets](https://github.com/DKISTDC/level2/blob/main/src/NSO/Data/Datasets.hs) - Production Data Effect with a database runner * [Effectful.Rel8](https://github.com/DKISTDC/level2/blob/main/types/src/Effectful/Rel8.hs) - Effect for the [Rel8](https://hackage.haskell.org/package/rel8) Postgres Library+-}+++{- $query+#EXAMPLE /state/query+-}+++{- $sessions+#EXAMPLE /state/sessions+-}+++{- $forms++Painless forms with type-checked field names, and support for validation.++#EXAMPLE /forms -}
src/Web/Hyperbole/Application.hs view
@@ -1,138 +1,86 @@-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE QuasiQuotes #-}- module Web.Hyperbole.Application ( waiApp , websocketsOr , defaultConnectionOptions , liveApp+ , liveAppWith+ , ServerOptions (..)+ , defaultErrorMessage+ , defaultError , socketApp- , basicDocument+ , quickStartDocument , routeRequest ) where +import Control.Exception import Control.Monad (forever) import Data.ByteString.Lazy qualified as BL-import Data.Maybe (fromMaybe)-import Data.String.Conversions (cs)-import Data.String.Interpolate (i)-import Data.Text (Text)-import Data.Text qualified as T import Effectful import Effectful.Concurrent.Async-import Effectful.Dispatch.Dynamic-import Effectful.Error.Static-import Network.HTTP.Types as HTTP (parseQuery) import Network.Wai qualified as Wai import Network.Wai.Handler.WebSockets (websocketsOr)-import Network.WebSockets (Connection, PendingConnection, defaultConnectionOptions)+import Network.WebSockets (ConnectionException (..), PendingConnection, defaultConnectionOptions, withPingThread) import Network.WebSockets qualified as WS-import Web.Cookie (parseCookies)+import Web.Hyperbole.Document import Web.Hyperbole.Effect.Hyperbole import Web.Hyperbole.Effect.Request (reqPath)-import Web.Hyperbole.Effect.Server (Host (..), Request (..), Response (..), Server, SocketError (..), cookiesFromHeader, runServerSockets, runServerWai)+import Web.Hyperbole.Effect.Response (notFound) import Web.Hyperbole.Route-import Web.Hyperbole.View.Embed (cssResetEmbed, scriptEmbed)+import Web.Hyperbole.Server.Options+import Web.Hyperbole.Server.Socket (handleRequestSocket)+import Web.Hyperbole.Server.Wai (handleRequestWai)+import Web.Hyperbole.Types.Response {- | Turn one or more 'Page's into a Wai Application. Respond using both HTTP and WebSockets > #EMBED Example/Docs/BasicPage.hs main -}-liveApp :: (BL.ByteString -> BL.ByteString) -> Eff '[Hyperbole, Server, Concurrent, IOE] Response -> Wai.Application-liveApp toDoc app =+liveApp :: (BL.ByteString -> BL.ByteString) -> Eff '[Hyperbole, Concurrent, IOE] Response -> Wai.Application+liveApp doc =+ liveAppWith $+ ServerOptions+ { toDocument = doc+ , serverError = defaultError+ }+++-- | Run a Hyperbole application, customizing both the document and the format of server errors+liveAppWith :: ServerOptions -> Eff '[Hyperbole, Concurrent, IOE] Response -> Wai.Application+liveAppWith opts app req = websocketsOr defaultConnectionOptions- (runEff . runConcurrent . socketApp app)- (waiApp toDoc app)+ (\pend -> socketApp opts req app pend `catch` suppressMessages)+ (waiApp opts app)+ req -waiApp :: (BL.ByteString -> BL.ByteString) -> Eff '[Hyperbole, Server, Concurrent, IOE] Response -> Wai.Application-waiApp toDoc actions req res = do- rr <- runEff $ runConcurrent $ runServerWai toDoc req res $ runHyperbole actions- case rr of- Nothing -> error "Missing required response in handler"- Just r -> pure r+waiApp :: ServerOptions -> Eff '[Hyperbole, Concurrent, IOE] Response -> Wai.Application+waiApp opts actions req res = do+ runEff $ runConcurrent $ handleRequestWai opts req res actions -socketApp :: (IOE :> es, Concurrent :> es) => Eff (Hyperbole : Server : es) Response -> PendingConnection -> Eff es ()-socketApp actions pend = do+socketApp :: ServerOptions -> Wai.Request -> Eff '[Hyperbole, Concurrent, IOE] Response -> PendingConnection -> IO ()+socketApp opts req actions pend = do conn <- liftIO $ WS.acceptRequest pend- forever $ do- ereq <- runErrorNoCallStack @SocketError $ receiveRequest conn- case ereq of- Left e -> liftIO $ putStrLn $ "SOCKET ERROR " <> show e- Right r -> do- a <- async (runServerSockets conn r $ runHyperbole actions)- -- throw exceptions in this thread- link a- pure ()- where- receiveRequest :: (IOE :> es, Error SocketError :> es) => Connection -> Eff es Request- receiveRequest conn = do- t <- receiveText conn- case parseMessage t of- Left e -> throwError e- Right r -> pure r-- receiveText :: (IOE :> es) => Connection -> Eff es Text- receiveText conn = do- -- c <- ask @Connection- liftIO $ WS.receiveData conn-- parseMessage :: Text -> Either SocketError Request- parseMessage t = do- case T.splitOn "\n" t of- [url, host, cook, body] -> parse url cook host (Just body)- [url, host, cook] -> parse url cook host Nothing- _ -> Left $ InvalidMessage t- where- parseUrl :: Text -> Either SocketError (Text, Text)- parseUrl u =- case T.splitOn "?" u of- [url, query] -> pure (url, query)- _ -> Left $ InvalidMessage u-- parse :: Text -> Text -> Text -> Maybe Text -> Either SocketError Request- parse url cook hst mbody = do- (u, q) <- parseUrl url- let path = paths u- query = HTTP.parseQuery (cs q)- cookies = cookiesFromHeader $ parseCookies $ cs $ header cook- host = Host $ cs $ header hst- method = "POST"- body = cs $ fromMaybe "" mbody- pure $ Request{path, host, query, body, method, cookies}-- paths p = filter (/= "") $ T.splitOn "/" p-- -- drop up to the colon, then ': '- header = T.drop 2 . T.dropWhile (/= ':')+ -- ping to keep the socket alive+ withPingThread conn 25 (pure ()) $ do+ forever $ do+ runEff $ runConcurrent $ handleRequestSocket opts req conn actions -{- | wrap HTML fragments in a simple document with a custom title and include required embeds--@-'liveApp' (basicDocument "App Title") ('routeRequest' router)-@--You may want to specify a custom document function to import custom javascript, css, or add other information to the \<head\>--> import Data.String.Interpolate (i)-> import Web.Hyperbole (scriptEmbed, cssResetEmbed)->-> #EMBED Example/Docs/App.hs customDocument--}-basicDocument :: Text -> BL.ByteString -> BL.ByteString-basicDocument title cnt =- [i|<html>- <head>- <title>#{title}</title>- <script type="text/javascript">#{scriptEmbed}</script>- <style type="text/css">#{cssResetEmbed}</style>- </head>- <body>#{cnt}</body>- </html>|]+suppressMessages :: ConnectionException -> IO a+suppressMessages ex = do+ -- The default version of Network.Websockets prints out CloseRequest and ConnectionClosed errors+ -- it's like they're using these as events instead of exceptions+ case ex of+ ConnectionClosed -> do+ -- putStrLn "CAUGHT ConnectionClosed"+ pure undefined+ CloseRequest _cd _msg -> do+ -- putStrLn "CAUGHT CloseRequest"+ pure undefined+ other -> throwIO other {- | Route URL patterns to different pages@@ -152,7 +100,5 @@ -} routeRequest :: (Hyperbole :> es, Route route) => (route -> Eff es Response) -> Eff es Response routeRequest actions = do- path <- reqPath- case findRoute path of- Nothing -> send $ RespondEarly NotFound- Just rt -> actions rt+ pth <- reqPath+ maybe notFound actions $ matchRoute pth
+ src/Web/Hyperbole/Data/Cookie.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE AllowAmbiguousTypes #-}++module Web.Hyperbole.Data.Cookie where++import Data.ByteString (ByteString)+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as M+import Data.Maybe (fromMaybe)+import Data.String.Conversions (cs)+import Data.Text (Text)+import Network.HTTP.Types (urlDecode, urlEncode)+import Web.Hyperbole.Data.URI+++type Key = Text+++data Cookie = Cookie+ { key :: Key+ , path :: Maybe Path+ , value :: Maybe CookieValue+ }+ deriving (Show, Eq)+++newtype Cookies = Cookies (Map Key Cookie)+ deriving newtype (Monoid, Semigroup, Show, Eq)+++newtype CookieValue = CookieValue ByteString+ deriving newtype (Show, Eq)+++insert :: Cookie -> Cookies -> Cookies+insert cookie (Cookies m) =+ Cookies $ M.insert cookie.key cookie m+++delete :: Key -> Cookies -> Cookies+delete key (Cookies m) =+ Cookies $ M.delete key m+++lookup :: Key -> Cookies -> Maybe CookieValue+lookup key (Cookies m) = do+ cook <- M.lookup key m+ cook.value+++fromList :: [Cookie] -> Cookies+fromList cks = Cookies $ M.fromList (fmap keyValue cks)+ where+ keyValue c = (c.key, c)+++toList :: Cookies -> [Cookie]+toList (Cookies m) = M.elems m+++render :: Path -> Cookie -> ByteString+render requestPath cookie =+ let p = fromMaybe requestPath cookie.path+ in cs cookie.key <> "=" <> value cookie.value <> "; SameSite=None; secure; path=" <> cs (uriToText (pathUri p))+ where+ value Nothing = "; expires=Thu, 01 Jan 1970 00:00:00 GMT"+ value (Just (CookieValue val)) = urlEncode True $ cs val+++parse :: [(ByteString, ByteString)] -> Either String Cookies+parse kvs = do+ cks <- mapM (uncurry parseValue) kvs+ pure $ fromList cks+++parseValue :: ByteString -> ByteString -> Either String Cookie+parseValue k val = do+ let cval = CookieValue $ cs $ urlDecode True val+ pure $ Cookie (cs k) Nothing (Just $ cval)
+ src/Web/Hyperbole/Data/Encoded.hs view
@@ -0,0 +1,289 @@+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedLists #-}++module Web.Hyperbole.Data.Encoded where++import Data.Aeson (FromJSON (..), ToJSON (..), Value (..))+import Data.Aeson qualified as A+import Data.Attoparsec.ByteString qualified as AB+import Data.Attoparsec.ByteString qualified as Atto+import Data.Attoparsec.ByteString.Char8 (isSpace, sepBy, takeWhile1)+import Data.Attoparsec.ByteString.Char8 qualified as AC+import Data.Bifunctor (first)+import Data.String (IsString)+import Data.String.Conversions (cs)+import Data.Text (Text)+import Data.Text qualified as T+import GHC.Generics+import Web.Hyperbole.Data.Param+++newtype ConName = ConName {text :: Text}+ deriving newtype (Eq, Show, IsString)+instance Semigroup ConName where+ -- Ignore the second constructor name+ c1 <> _ = c1+instance Monoid ConName where+ mempty = ConName ""+++{- | Pretty Human Readable top-levelencoding for ViewAction and ViewId+For simple Sum and Product types it is equivalent to the Show/Read instance++MyConstructor 1 2 3+-}+data Encoded = Encoded ConName [ParamValue]+ deriving (Show, Eq)+++instance Semigroup Encoded where+ Encoded c1 es1 <> Encoded c2 es2 =+ Encoded (c1 <> c2) (es1 <> es2)+instance Monoid Encoded where+ mempty = Encoded mempty mempty+instance ToJSON Encoded where+ toJSON e = toJSON $ encode e+instance FromJSON Encoded where+ parseJSON (String t) =+ case decodeEither t of+ Left e -> fail $ "Encoded " <> cs e+ Right a -> pure a+ parseJSON val = fail $ "Expected Encoded but got: " <> show val+++encode :: (ToEncoded a) => a -> Text+encode a = encodedToText $ toEncoded a+++decode :: (FromEncoded a) => Text -> Maybe a+decode t = either (const Nothing) Just $ decodeEither t+++decodeEither :: (FromEncoded a) => Text -> Either String a+decodeEither t = do+ enc <- encodedParseText t+ parseEncoded enc+++-- | Basic Encoding+encodedToText :: Encoded -> Text+encodedToText (Encoded con values) =+ let params = T.intercalate " " $ fmap encodeParam values+ in case params of+ "" -> con.text+ _ -> con.text <> " " <> params+++encodedParseText :: Text -> Either String Encoded+encodedParseText inp =+ first cs $ AB.parseOnly encodedParser (cs inp)+ where+ encodedParser :: AB.Parser Encoded+ encodedParser = do+ con <- AC.takeTill AC.isSpace+ AC.skipSpace+ params <- paramParser `sepBy` AC.char ' '+ pure $ Encoded (ConName (cs con)) params+++genericToEncoded :: (Generic a, GToEncoded (Rep a)) => a -> Encoded+genericToEncoded a = gToEncoded (from a)+++genericParseEncoded :: (Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a+genericParseEncoded enc = do+ (gen, _) <- gParseEncoded enc+ pure $ to gen+++genericDecode :: (Generic a, GFromEncoded (Rep a)) => Text -> Maybe a+genericDecode t = either (const Nothing) Just $ do+ enc <- encodedParseText t+ genericParseEncoded enc+++-- | Custom Encoding for embedding into web documents. Noteably used for 'ViewId' and 'ViewAction'+class ToEncoded a where+ toEncoded :: a -> Encoded+ default toEncoded :: (Generic a, GToEncoded (Rep a)) => a -> Encoded+ toEncoded = genericToEncoded+++instance ToEncoded Encoded where+ toEncoded = id+++-- | Custom Encoding for embedding into web documents. Noteably used for 'ViewId' and 'ViewAction'+class FromEncoded a where+ parseEncoded :: Encoded -> Either String a+ default parseEncoded :: (Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a+ parseEncoded = genericParseEncoded+++instance FromEncoded Encoded where+ parseEncoded = pure+++fromResult :: A.Result a -> Either String a+fromResult (A.Success a) = pure a+fromResult (A.Error e) = Left (cs e)+++-------------------------------------------------------------------------------+-- PARAM ENCODING+-------------------------------------------------------------------------------+-- Params need to be sanitized and escaped, because we want to use spaces to separate our params+-- Data.Param by default does not sanitize spaces++paramParser :: Atto.Parser ParamValue+paramParser = do+ t <- takeWhile1 (not . isSpace)+ pure $ decodeParam $ cs t+++decodeParam :: Text -> ParamValue+decodeParam = \case+ "|" -> ParamValue ""+ t -> ParamValue $ desanitizeParamText t+++-- replace all underscores that are NOT "\\_" with spaces++desanitizeParamText :: Text -> Text+desanitizeParamText =+ T.replace "\\ " "_" . T.replace "_" " "+++-- | T.isSuffixOf "\\" seg = T.dropEnd 1 seg <> "_" <> txt+-- | otherwise = seg <> " " <> txt++-- foldr join "" $+-- where+--+-- join "" "" = " "+-- join "" " " = " "+-- join seg "" = seg+-- join seg txt+-- | T.isSuffixOf "\\" seg = T.dropEnd 1 seg <> "_" <> txt+-- | otherwise = seg <> " " <> txt++encodeParam :: ParamValue -> Text+encodeParam (ParamValue t) =+ case t of+ "" -> "|"+ _ -> sanitizeParamText t+ where+ sanitizeParamText :: Text -> Text+ sanitizeParamText = T.replace " " "_" . T.replace "_" "\\_"+++-- decodeParamValue :: (FromParam a) => Text -> Either String a+-- decodeParamValue = parseParam . decodeParam++-- decodeParam :: Text -> ParamValue+-- decodeParam inp = do+-- case A.eitherDecode (cs inp) of+-- Left _ -> paramFromText inp+-- Right v -> ParamValue inp v++-------------------------------------------------------------------------------+-- GENERICS+-------------------------------------------------------------------------------++-- GToEncoded: Generic ViewAction Encoding++class GToEncoded f where+ gToEncoded :: f p -> Encoded+++instance (GToEncoded f, GToEncoded g) => GToEncoded (f :+: g) where+ gToEncoded (L1 f) = gToEncoded f+ gToEncoded (R1 f) = gToEncoded f+++instance (GToEncoded f, GToEncoded g) => GToEncoded (f :*: g) where+ gToEncoded (f :*: g) =+ gToEncoded f <> gToEncoded g+++instance GToEncoded U1 where+ -- WARNING: not sure if this will work+ gToEncoded U1 = mempty+++instance (GToEncoded f) => GToEncoded (M1 D d f) where+ gToEncoded (M1 f) = gToEncoded f+++instance (Constructor c, GToEncoded f) => GToEncoded (M1 C c f) where+ gToEncoded (M1 f) =+ let con = cs (conName (undefined :: M1 C c f p))+ in Encoded (ConName con) mempty <> gToEncoded f+++instance (GToEncoded f) => GToEncoded (M1 S s f) where+ gToEncoded (M1 f) = gToEncoded f+++instance (ToParam a) => GToEncoded (K1 R a) where+ gToEncoded (K1 a) = Encoded mempty [toParam a]+++-- GFromEncoded: Generic ViewAction Decoding++class GFromEncoded f where+ gParseEncoded :: Encoded -> Either String (f p, [ParamValue])+++instance (GFromEncoded f, GFromEncoded g) => GFromEncoded (f :+: g) where+ gParseEncoded enc@(Encoded con vals) = do+ let el = gParseEncoded @f enc+ let er = gParseEncoded @g enc+ case (el, er) of+ (Right (l, lvals), _) -> pure (L1 l, lvals)+ (_, Right (r, rvals)) -> pure (R1 r, rvals)+ (Left _, Left _) ->+ Left $ "No matching sum constructor: " <> cs con.text <> " " <> cs (show vals)+++instance (GFromEncoded f, GFromEncoded g) => GFromEncoded (f :*: g) where+ gParseEncoded (Encoded con vals) = do+ (a, rest) <- gParseEncoded @f (Encoded con vals)+ (b, gone) <- gParseEncoded @g (Encoded con rest)+ pure (a :*: b, gone)+++instance GFromEncoded U1 where+ gParseEncoded (Encoded _ vals) = pure (U1, vals)+++instance (GFromEncoded f) => GFromEncoded (M1 D d f) where+ gParseEncoded enc = do+ first M1 <$> gParseEncoded enc+++instance (Constructor c, GFromEncoded f) => GFromEncoded (M1 C c f) where+ gParseEncoded enc@(Encoded cname _) = do+ if cs cname.text == con+ then first M1 <$> gParseEncoded @f enc+ else Left $ "Mismatched Constructor " <> cs cname.text <> " /= " <> con+ where+ con = cs $ conName (undefined :: M1 C c f p)+++instance (GFromEncoded f) => GFromEncoded (M1 S s f) where+ gParseEncoded enc = do+ (a, rest) <- gParseEncoded enc+ pure (M1 a, rest)+++instance (FromParam a) => GFromEncoded (K1 R a) where+ gParseEncoded (Encoded con vals) = do+ case vals of+ (param : rest) -> do+ case parseParam param of+ -- consume one param+ Right a -> pure (K1 a, rest)+ Left e -> Left (cs e)+ [] -> Left $ "Missing parameters for Encoded Constructor:" <> cs con.text
+ src/Web/Hyperbole/Data/JSON.hs view
@@ -0,0 +1,21 @@+module Web.Hyperbole.Data.JSON (JSON (..), ToJSON, FromJSON) where++import Data.Aeson as A+import Data.String.Conversions (cs)+import Web.Hyperbole.Data.Encoded+import Web.Hyperbole.Data.Param+++-- JSON Encoded Data ------------------------------------++-- | This type always encodes data via JSON+newtype JSON a = JSON a+++instance (ToJSON a) => ToEncoded (JSON a) where+ toEncoded (JSON a) = Encoded "" [jsonParam a]+instance (FromJSON a) => FromEncoded (JSON a) where+ parseEncoded (Encoded _ [ParamValue t]) = do+ JSON <$> A.eitherDecode (cs t)+ parseEncoded (Encoded _ prms) = do+ Left $ "Could not parse JSON Encoded, expected one param: " <> show prms
+ src/Web/Hyperbole/Data/Param.hs view
@@ -0,0 +1,274 @@+{-# LANGUAGE DefaultSignatures #-}++module Web.Hyperbole.Data.Param where++import Data.Aeson (FromJSON, GFromJSON, GToJSON, Options (..), SumEncoding (..), ToJSON, Value (..), Zero, defaultOptions, genericParseJSON, genericToJSON)+import Data.Aeson qualified as A+import Data.Aeson.Types qualified as A+import Data.Bifunctor (first)+import Data.String (IsString (..))+import Data.String.Conversions (cs)+import Data.Text (Text)+import Data.Time.Clock (UTCTime)+import Data.Word+import GHC.Exts (IsList (..))+import GHC.Generics+import Text.Read (readMaybe)+import Web.HttpApiData (FromHttpApiData, ToHttpApiData)+import Web.HttpApiData qualified as HttpApiData+import Web.Hyperbole.Data.URI (URI (..), parseURIReference, uriToText)+++newtype Param = Param {text :: Text}+ deriving newtype (Show, Eq, Ord, IsString)+++-- | Encode arbitrarily complex data into url form encoded data+data ParamValue = ParamValue {value :: Text}+ deriving (Eq, Show)+++instance IsString ParamValue where+ fromString s = ParamValue (cs s)+++{- | 'session's, 'form's, and 'query's all encode data as query strings. ToParam and FromParam control how a datatype is encoded to a parameter.+ -+This is equivalent to Web.HttpApiData, which is missing some instances and has some strange defaults++@+#EMBED Example/Docs/Sessions.hs data AppColor+@+-}+class ToParam a where+ toParam :: a -> ParamValue+ default toParam :: (Generic a, GToJSON Zero (Rep a)) => a -> ParamValue+ toParam = genericToParam+++instance ToParam Int where+ toParam = jsonParam+instance ToParam Integer where+ toParam = jsonParam+instance ToParam Text where+ toParam = toQueryParam+instance {-# OVERLAPS #-} ToParam String where+ toParam = toQueryParam+instance ToParam Float where+ toParam = jsonParam+instance ToParam Double where+ toParam = jsonParam+instance ToParam Word where+ toParam = jsonParam+instance ToParam Word8 where+ toParam = jsonParam+instance ToParam Word16 where+ toParam = jsonParam+instance ToParam Word32 where+ toParam = jsonParam+instance ToParam Word64 where+ toParam = jsonParam+instance ToParam Bool where+ toParam = jsonParam+instance ToParam Char where+ toParam = toQueryParam+instance ToParam UTCTime where+ toParam = toQueryParam+instance ToParam URI where+ toParam = toParam . uriToText+instance ToParam Value where+ toParam = jsonParam+++{- | Decode data from a 'query', 'session', or 'form' parameter value++@+#EMBED Example/Docs/Sessions.hs data AppColor+@+-}+class FromParam a where+ parseParam :: ParamValue -> Either String a+ default parseParam :: (Generic a, GFromJSON Zero (Rep a)) => ParamValue -> Either String a+ parseParam = genericParseParam+++ decodeFormValue :: Maybe Text -> Either String a+ decodeFormValue mval = do+ case mval of+ Nothing -> Left "missing form field value"+ Just t -> do+ parseParam $ ParamValue t+++-- decodeParamValue :: Text -> Either String a+-- decodeParamValue = parseParam . decodeParam++-- Permissive instances. Some of these come directly from forms!+instance FromParam Int where+ parseParam "" = pure 0+ parseParam p = jsonParse p+instance FromParam Integer where+ parseParam "" = pure 0+ parseParam p = jsonParse p+instance FromParam Float where+ parseParam "" = pure 0+ parseParam p = jsonParse p+instance FromParam Double where+ parseParam "" = pure 0+ parseParam p = jsonParse p+instance FromParam Text where+ parseParam = parseQueryParam+++-- -- we don't need to desanitize the text+-- parseFormField [inp] = do+-- parseParam $ ParamValue inp (String inp)++instance {-# OVERLAPS #-} FromParam String where+ parseParam p = cs <$> parseQueryParam @Text p+++-- parseFormField sel f = do+-- inp :: Text <- first cs $ FE.parseUnique @Text (cs sel) f+-- parseParam $ ParamValue inp (String inp)+instance FromParam Word where+ parseParam = jsonParse+instance FromParam Word8 where+ parseParam = jsonParse+instance FromParam Word16 where+ parseParam = jsonParse+instance FromParam Word32 where+ parseParam = jsonParse+instance FromParam Word64 where+ parseParam = jsonParse+instance FromParam Bool where+ parseParam (ParamValue t) =+ case t of+ "on" -> pure True+ "off" -> pure False+ "" -> pure False+ "false" -> pure False+ "true" -> pure True+ other -> Left $ "Could not parse bool param: " <> cs other+++ decodeFormValue Nothing = pure False+ decodeFormValue (Just t) =+ parseParam $ ParamValue t+++instance FromParam Char where+ parseParam = parseQueryParam+instance FromParam UTCTime where+ parseParam = parseQueryParam+instance FromParam Value where+ parseParam = jsonParse+++instance FromParam URI where+ parseParam (ParamValue t) = do+ case parseURIReference (cs t) of+ Nothing -> Left $ "Invalid URI: " <> cs t+ Just u -> pure u+++instance {-# OVERLAPPABLE #-} (ToParam a) => ToParam [a] where+ toParam as =+ -- JSON encode the individual params+ let ps :: [ParamValue] = fmap toParam as+ in toParam $ Array $ fromList $ fmap (String . (.value)) ps+instance {-# OVERLAPPABLE #-} (FromParam a) => FromParam [a] where+ parseParam p = do+ ts <- jsonParse @[Text] p+ mapM (parseParam . ParamValue) ts+++instance (ToParam a) => ToParam (Maybe a) where+ toParam Nothing = ParamValue "~"+ toParam (Just a) = toParam a+instance {-# OVERLAPPABLE #-} (FromParam a) => FromParam (Maybe a) where+ parseParam (ParamValue "") = pure Nothing+ parseParam (ParamValue "~") = pure Nothing+ parseParam t = Just <$> parseParam @a t+++ decodeFormValue Nothing = pure Nothing+ decodeFormValue (Just t) = do+ parseParam @(Maybe a) (ParamValue t)+++instance {-# OVERLAPS #-} FromParam (Maybe Text) where+ parseParam (ParamValue "~") = pure Nothing+ -- keep empty strings, the default instance discards them+ parseParam (ParamValue "") = pure (Just "")+ parseParam t = Just <$> parseParam @Text t+++ decodeFormValue Nothing = pure Nothing+ decodeFormValue (Just t) = do+ parseParam @(Maybe Text) (ParamValue t)+++instance (ToParam a, ToParam b) => ToParam (Either a b) where+ toParam (Left a) = toParam a+ toParam (Right b) = toParam b+instance (FromParam a, FromParam b) => FromParam (Either a b) where+ parseParam val =+ case parseParam @a val of+ Right a -> pure $ Left a+ Left _ -> do+ case parseParam @b val of+ Left _ -> Left $ "Could not parse Either param: " <> show val+ Right b -> pure $ Right b+++parseQueryParam :: (FromHttpApiData a) => ParamValue -> Either String a+parseQueryParam (ParamValue t) =+ first cs $ HttpApiData.parseQueryParam t+++toQueryParam :: (ToHttpApiData a) => a -> ParamValue+toQueryParam a =+ ParamValue $ HttpApiData.toQueryParam a+++-- | Encode a Show as a query param+showParam :: (Show a) => a -> ParamValue+showParam a = toQueryParam $ show a+++-- | Decode a Read as a query param+readParam :: (Read a) => ParamValue -> Either String a+readParam p = do+ str <- parseQueryParam p+ case readMaybe str of+ Nothing -> Left $ cs $ "Could not read query param: " <> str+ Just a -> pure a+++genericToParam :: (Generic a, GToJSON Zero (Rep a)) => a -> ParamValue+genericToParam a =+ case genericToJSON jsonOptions a of+ String t -> ParamValue t+ other -> jsonParam other+++genericParseParam :: (Generic a, GFromJSON Zero (Rep a)) => ParamValue -> Either String a+genericParseParam (ParamValue t) = do+ val <- maybe (pure $ String t) pure $ A.decode (cs t)+ A.parseEither (genericParseJSON jsonOptions) val+++-- Encoding ------------------------------------------------------------++jsonOptions :: A.Options+jsonOptions = defaultOptions{sumEncoding = TwoElemArray}+++jsonParam :: (ToJSON a) => a -> ParamValue+jsonParam a = ParamValue (cs $ A.encode a)+++jsonParse :: (FromJSON a) => ParamValue -> Either String a+jsonParse (ParamValue t) = do+ A.eitherDecode (cs t)
src/Web/Hyperbole/Data/QueryData.hs view
@@ -3,47 +3,42 @@ module Web.Hyperbole.Data.QueryData where -import Data.Bifunctor (bimap)+import Data.Aeson (Value (Null)) import Data.ByteString (ByteString) import Data.Default (Default (..)) import Data.Map.Strict (Map) import Data.Map.Strict qualified as M import Data.Maybe (fromMaybe)-import Data.String (IsString) import Data.String.Conversions (cs) import Data.Text (Text, pack)-import Data.Time (UTCTime)-import Data.Word+import GHC.Exts (IsList (..)) import GHC.Generics-import Network.HTTP.Types (Query, renderQuery)+import Network.HTTP.Types (Query, QueryItem, renderQuery) import Network.HTTP.Types qualified as HTTP-import Text.Read (readMaybe)-import Web.HttpApiData (parseQueryParam, toQueryParam)+import Web.Hyperbole.Data.Encoded (decodeParam, encodeParam)+import Web.Hyperbole.Data.Param import Prelude hiding (lookup) -newtype Param = Param {text :: Text}- deriving newtype (Show, Eq, Ord, IsString)---newtype ParamValue = ParamValue {text :: Text}- deriving newtype (Show, Eq, Ord, IsString)-- -- | Key-value store for query params and sessions newtype QueryData = QueryData (Map Param ParamValue) deriving (Show) deriving newtype (Monoid, Semigroup) +instance IsList QueryData where+ type Item QueryData = (Param, ParamValue)+ fromList = QueryData . fromList+ toList (QueryData m) = toList m++ singleton :: (ToParam a) => Param -> a -> QueryData singleton key a = QueryData $ M.singleton key (toParam a) insert :: (ToParam a) => Param -> a -> QueryData -> QueryData insert p a (QueryData m) =- let val = toParam a- in QueryData $ M.insert p val m+ QueryData $ M.insert p (toParam a) m insertAll :: (ToQuery a) => a -> QueryData -> QueryData@@ -63,10 +58,10 @@ either (const Nothing) pure $ parseParam t -require :: (FromParam a) => Param -> QueryData -> Either Text a+require :: (FromParam a) => Param -> QueryData -> Either String a require p (QueryData m) = do case M.lookup p m of- Nothing -> Left $ "Missing Key: " <> p.text+ Nothing -> Left $ "Missing Key: " <> cs p.text Just val -> parseParam val @@ -84,11 +79,8 @@ render :: QueryData -> ByteString-render (QueryData m) =- -- urlEncode True- renderQuery False (HTTP.toQuery $ fmap queryItem $ M.toList m)- where- queryItem (Param k, ParamValue val) = (k, val)+render qd =+ renderQuery False (HTTP.toQuery $ fromQueryData qd) parse :: ByteString -> QueryData@@ -98,37 +90,45 @@ queryData :: Query -> QueryData-queryData =- QueryData . M.fromList . map (bimap (Param . cs) value)+queryData q =+ fromList $ fmap fromQueryItem q where- -- empty / missing values are encoded as empty strings- value Nothing = ""- value (Just v) = ParamValue (cs v)+ fromQueryItem :: QueryItem -> (Param, ParamValue)+ fromQueryItem (key, mval) =+ (Param (cs key), fromParam mval) + fromParam :: Maybe ByteString -> ParamValue+ fromParam Nothing = jsonParam Null+ fromParam (Just t) = decodeParam (cs t) -fromList :: [(Param, ParamValue)] -> QueryData-fromList = QueryData . M.fromList +fromQueryData :: QueryData -> Query+fromQueryData q =+ fmap toQueryItem $ toList q+ where+ toQueryItem :: (Param, ParamValue) -> QueryItem+ toQueryItem (Param prm, pval) =+ (cs prm, Just $ toQueryValue pval) -toList :: QueryData -> [(Param, ParamValue)]-toList (QueryData m) = M.toList m+ toQueryValue :: ParamValue -> ByteString+ toQueryValue = cs . encodeParam -{- | Decode a type from a 'QueryData'. Missing fields are set to 'defaultParam'+{- | Decode a type from a 'QueryData'. Missing fields are set to 'Data.Default.def' @ #EMBED Example/Docs/Encoding.hs data Filters @ ->>> parseQuery $ parse "active=true&search=asdf"+>>> parseQuery $ QueryData.parse "active=true&search=asdf" Right (Filters True "asdf") ->>> parseQuery $ parse "search=asdf"+>>> parseQuery $ QueryData.parse "search=asdf" Right (Filters False "asdf") -} class FromQuery a where- parseQuery :: QueryData -> Either Text a- default parseQuery :: (Generic a, GFromQuery (Rep a)) => QueryData -> Either Text a+ parseQuery :: QueryData -> Either String a+ default parseQuery :: (Generic a, GFromQuery (Rep a)) => QueryData -> Either String a parseQuery q = to <$> gParseQuery q @@ -142,15 +142,15 @@ #EMBED Example/Docs/Encoding.hs data Filters @ ->>> render $ toQuery $ Filter True "asdf"+>>> QueryData.render $ toQuery $ Filter True "asdf" "active=true&search=asdf" -If the value of a field is the same as 'DefaultParam', it will be omitted from the query string+If the value of a field is the same as 'Default', it will be omitted from the query string ->>> render $ toQuery $ Filter True ""+>>> QueryData.render $ toQuery $ Filter True "" "active=true" ->>> render $ toQuery $ Filter False ""+>>> QueryData.render $ toQuery $ Filter False "" "" -} class ToQuery a where@@ -167,175 +167,9 @@ toQuery = queryData -{- | 'session's, 'form's, and 'query's all encode data as query strings. ToParam and FromParam control how a datatype is encoded to a parameter. By default it simply url-encodes the show instance.--@-#EMBED Example/Effects/Todos.hs data Todo = Todo-@--@-#EMBED Example/Docs/Encoding.hs data Tags--#EMBED Example/Docs/Encoding.hs instance ToParam Tags-@--}-class ToParam a where- toParam :: a -> ParamValue- default toParam :: (Show a) => a -> ParamValue- toParam = showQueryParam---{- | Decode data from a 'query', 'session', or 'form' parameter value--@-#EMBED Example/Effects/Todos.hs data Todo = Todo-@--@-#EMBED Example/Docs/Encoding.hs data Tags--#EMBED Example/Docs/Encoding.hs instance FromParam Tags-@--}-class FromParam a where- parseParam :: ParamValue -> Either Text a- default parseParam :: (Read a) => ParamValue -> Either Text a- parseParam = readQueryParam---instance ToParam Int where- toParam = ParamValue . toQueryParam-instance FromParam Int where- parseParam (ParamValue t) = parseQueryParam t---instance ToParam Integer where- toParam = ParamValue . toQueryParam-instance FromParam Integer where- parseParam (ParamValue t) = parseQueryParam t---instance ToParam Float where- toParam = ParamValue . toQueryParam-instance FromParam Float where- parseParam (ParamValue t) = parseQueryParam t---instance ToParam Double where- toParam = ParamValue . toQueryParam-instance FromParam Double where- parseParam (ParamValue t) = parseQueryParam t---instance ToParam Word where- toParam = ParamValue . toQueryParam-instance FromParam Word where- parseParam (ParamValue t) = parseQueryParam t---instance ToParam Word8 where- toParam = ParamValue . toQueryParam-instance FromParam Word8 where- parseParam (ParamValue t) = parseQueryParam t---instance ToParam Word16 where- toParam = ParamValue . toQueryParam-instance FromParam Word16 where- parseParam (ParamValue t) = parseQueryParam t---instance ToParam Word32 where- toParam = ParamValue . toQueryParam-instance FromParam Word32 where- parseParam (ParamValue t) = parseQueryParam t---instance ToParam Word64 where- toParam = ParamValue . toQueryParam-instance FromParam Word64 where- parseParam (ParamValue t) = parseQueryParam t---instance ToParam Bool where- toParam = ParamValue . toQueryParam-instance FromParam Bool where- parseParam (ParamValue t) = parseQueryParam t---instance ToParam Text where- toParam = ParamValue . toQueryParam-instance FromParam Text where- parseParam (ParamValue t) = parseQueryParam t---instance ToParam Char where- toParam = ParamValue . toQueryParam-instance FromParam Char where- parseParam (ParamValue t) = parseQueryParam t---instance ToParam UTCTime where- toParam = ParamValue . toQueryParam-instance FromParam UTCTime where- parseParam (ParamValue t) = parseQueryParam t---instance (Show a) => ToParam [a] where- toParam = showQueryParam-instance (Read a) => FromParam [a] where- parseParam = readQueryParam---instance (Show k, Show v) => ToParam (Map k v) where- toParam = showQueryParam-instance (Read k, Read v, Ord k) => FromParam (Map k v) where- parseParam = readQueryParam---instance (ToParam a) => ToParam (Maybe a) where- toParam Nothing = ""- toParam (Just a) = toParam a-instance (FromParam a) => FromParam (Maybe a) where- parseParam "" = pure Nothing- parseParam t = Just <$> parseParam @a t---instance (ToParam a, ToParam b) => ToParam (Either a b) where- toParam (Left a) = toParam a- toParam (Right b) = toParam b-instance (FromParam a, FromParam b) => FromParam (Either a b) where- parseParam val =- case parseParam @a val of- Right a -> pure $ Left a- Left _ -> do- case parseParam @b val of- Left _ -> Left $ "Could not parseParam Either: " <> val.text- Right b -> pure $ Right b----- | Encode a Show as a query param-showQueryParam :: (Show a) => a -> ParamValue-showQueryParam a = ParamValue $ toQueryParam $ show a----- | Decode a Read as a query param-readQueryParam :: (Read a) => ParamValue -> Either Text a-readQueryParam (ParamValue t) = do- str <- parseQueryParam t- case readMaybe str of- Nothing -> Left $ pack $ "Could not read query param: " <> str- Just a -> pure a----- | Parse a Traversable (list) of params-parseParams :: (Traversable t, FromParam a) => t ParamValue -> Either Text (t a)-parseParams = traverse parseParam-- -- | Generic decoding of records from a Query class GFromQuery f where- gParseQuery :: QueryData -> Either Text (f p)+ gParseQuery :: QueryData -> Either String (f p) instance (GFromQuery f, GFromQuery g) => GFromQuery (f :*: g) where@@ -353,13 +187,21 @@ gParseQuery q = M1 <$> gParseQuery q -instance (Selector s, FromParam a, DefaultParam a) => GFromQuery (M1 S s (K1 R a)) where+instance {-# OVERLAPPABLE #-} (Selector s, FromParam a, Default a) => GFromQuery (M1 S s (K1 R a)) where gParseQuery q = do let s = selName (undefined :: M1 S s (K1 R (f a)) p) let mval = lookup (Param $ pack s) q- pure $ M1 $ K1 $ fromMaybe defaultParam mval+ pure $ M1 $ K1 $ fromMaybe def mval +-- Text doesn't have a default instance. Annoying+instance {-# OVERLAPS #-} (Selector s) => GFromQuery (M1 S s (K1 R Text)) where+ gParseQuery q = do+ let s = selName (undefined :: M1 S s (K1 R (f a)) p)+ let mval = lookup (Param $ pack s) q+ pure $ M1 $ K1 $ fromMaybe "" mval++ -- | Generic encoding of records to a Query class GToQuery f where gToQuery :: f p -> QueryData@@ -377,24 +219,25 @@ gToQuery (M1 f) = gToQuery f -instance (Selector s, ToParam a, Eq a, DefaultParam a) => GToQuery (M1 S s (K1 R a)) where+instance {-# OVERLAPPABLE #-} (Selector s, ToParam a, Eq a, Default a) => GToQuery (M1 S s (K1 R a)) where gToQuery (M1 (K1 a))- | a == defaultParam = mempty+ | a == def = mempty | otherwise = let sel = Param $ pack $ selName (undefined :: M1 S s (K1 R (f a)) p) in singleton sel a --- | Data.Default doesn't have a Text instance. This class does-class DefaultParam a where- defaultParam :: a- default defaultParam :: (Default a) => a- defaultParam = def---instance {-# OVERLAPPABLE #-} (Default a) => DefaultParam a where- defaultParam = def-+-- Special case for Text, which has no Default instance+instance {-# OVERLAPS #-} (Selector s) => GToQuery (M1 S s (K1 R Text)) where+ gToQuery (M1 (K1 a))+ | a == "" = mempty+ | otherwise =+ let sel = Param $ pack $ selName (undefined :: M1 S s (K1 R (f a)) p)+ in singleton sel a -instance {-# OVERLAPS #-} DefaultParam Text where- defaultParam = ""+-- instance {-# OVERLAPS #-} (Selector s, ToParam a, Eq a) => GToQuery (M1 S s (K1 R [a])) where+-- gToQuery (M1 (K1 a))+-- | a == [] = mempty+-- | otherwise =+-- let sel = Param $ pack $ selName (undefined :: M1 S s (K1 R (f a)) p)+-- in singleton sel $ Plusses a
− src/Web/Hyperbole/Data/Session.hs
@@ -1,89 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE DefaultSignatures #-}--module Web.Hyperbole.Data.Session where--import Data.Map.Strict (Map)-import Data.Map.Strict qualified as M-import Data.String.Conversions (cs)-import Data.Text (Text)-import GHC.Generics-import Web.Hyperbole.Data.QueryData (Param (..), ParamValue (..), ToParam (..))-import Web.View.Types.Url (Segment)---data Cookie = Cookie- { key :: Param- , value :: Maybe ParamValue- , path :: Maybe [Segment]- }- deriving (Show, Eq)-newtype Cookies = Cookies (Map Param Cookie)- deriving newtype (Monoid, Semigroup, Show, Eq)---insert :: Cookie -> Cookies -> Cookies-insert cookie (Cookies m) =- Cookies $ M.insert cookie.key cookie m---delete :: Param -> Cookies -> Cookies-delete key (Cookies m) =- Cookies $ M.delete key m----lookup :: Param -> Cookies -> Maybe ParamValue-lookup key (Cookies m) = do- cook <- M.lookup key m- cook.value---deletedCookie :: forall a. (Session a) => Cookie-deletedCookie =- Cookie (sessionKey @a) Nothing (cookiePath @a)---sessionCookie :: forall a. (Session a, ToParam a) => a -> Cookie-sessionCookie a = Cookie (sessionKey @a) (Just $ toParam a) (cookiePath @a)---fromList :: [Cookie] -> Cookies-fromList cks = Cookies $ M.fromList (fmap keyValue cks)- where- keyValue c = (c.key, c)---toList :: Cookies -> [Cookie]-toList (Cookies m) = M.elems m---{- | Configure a data type to persist in the 'session'--@-#EMBED Example/Docs/Sessions.hs data Preferences--#EMBED Example/Docs/Sessions.hs instance DefaultParam Preferences-@--}-class Session a where- -- | Unique key for the Session. Defaults to the datatypeName- sessionKey :: Param- default sessionKey :: (Generic a, GDatatypeName (Rep a)) => Param- sessionKey = Param $ gDatatypeName $ from (undefined :: a)--- -- | By default Sessions are persisted only to the current page. Set this to `Just []` to make an application-wide Session- cookiePath :: Maybe [Segment]- default cookiePath :: Maybe [Segment]- cookiePath = Nothing----- | generic datatype name-class GDatatypeName f where- gDatatypeName :: f p -> Text---instance (Datatype d) => GDatatypeName (M1 D d f) where- gDatatypeName _ =- cs $ datatypeName (undefined :: M1 D d f p)
+ src/Web/Hyperbole/Data/URI.hs view
@@ -0,0 +1,131 @@+module Web.Hyperbole.Data.URI+ ( -- * URI+ URI (..)+ , URIAuth (..)+ , uri++ -- ** Path+ , Path (..)+ , Segment+ , path+ , parseURIReference+ , pathUri+ , uriToText+ , pathToText++ -- ** Query String+ , queryString+ , parseQuery+ , queryInsert+ , renderQuery+ , Query+ , QueryItem+ , (./.)+ , (.?.)+ , cleanSegment+ , Endpoint (..)+ )+where++import Data.ByteString (ByteString)+import Data.String (IsString (..))+import Data.String.Conversions (cs)+import Data.Text (Text)+import Data.Text qualified as T+import GHC.Exts (IsList (..))+import Network.HTTP.Types (Query, QueryItem, parseQuery, renderQuery)+import Network.URI (URI (..), URIAuth (..), parseURIReference, uriToString)+import Network.URI qualified as Network+import Network.URI.Static (uri)+import System.FilePath (normalise, (</>))+++-- Constructors ------------------------------------------+-- see `uri` for static URIs++-- Operators -----------------------------------------------++-- maybe lets not care about leading slashes at all until rendering+(./.) :: URI -> Path -> URI+u ./. p =+ u{Network.uriPath = addLeadingSlash $ normalise (u.uriPath </> newPath)}+ where+ newPath = cs $ pathToText False p++ addLeadingSlash pth =+ case take 1 pth of+ "/" -> pth+ _ -> '/' : pth+++infixl 5 ./.+++(.?.) :: URI -> QueryItem -> URI+u .?. (k, mv) = u{uriQuery = queryInsert k mv u.uriQuery}+++-- Query ---------------------------------------------------++type QueryString = String+++queryInsert :: ByteString -> Maybe ByteString -> QueryString -> QueryString+queryInsert k mv s =+ queryString $ parseQuery (cs s) <> [(k, mv)]+++queryString :: [(ByteString, Maybe ByteString)] -> QueryString+queryString = cs . renderQuery True+++-- Path -----------------------------------------------------++newtype Path = Path {segments :: [Segment]}+ deriving (Show, Eq)+instance IsList Path where+ type Item Path = Segment+ fromList = Path . filter (not . T.null)+ toList p = p.segments+instance IsString Path where+ fromString = path . cs+++type Segment = Text+++cleanSegment :: Segment -> Segment+cleanSegment = T.dropWhileEnd (== '/') . T.dropWhile (== '/')+++path :: Text -> Path+path p =+ fromList $ T.splitOn "/" $ T.dropWhile (== '/') p+++pathUri :: Path -> URI+pathUri p =+ URI+ { uriPath = cs $ pathToText True p+ , uriScheme = mempty+ , uriAuthority = Nothing+ , uriQuery = mempty+ , uriFragment = mempty+ }+++uriToText :: URI -> Text+uriToText u = cs $ uriToString id u ""+++pathToText :: Bool -> Path -> Text+pathToText isRoot p =+ pathPrefix <> T.intercalate "/" (fmap cleanSegment p.segments)+ where+ pathPrefix :: Text+ pathPrefix =+ if isRoot then "/" else ""+++-- | A URI with a phantom type to distinguish different endpoints+newtype Endpoint a = Endpoint {uri :: URI}
+ src/Web/Hyperbole/Document.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE QuasiQuotes #-}++module Web.Hyperbole.Document where++import Data.ByteString.Lazy qualified as BL+import Data.String.Interpolate (i)+import Web.Hyperbole.View+++data Document = Document+++{- | 'liveApp' requires a function which turns an html fragment into an entire html document. Use this to import javascript, css, etc. Use 'quickStartDocument' to get going quickly++> #EMBED Example/Docs/App.hs app+-}+document :: View DocumentHead () -> BL.ByteString -> BL.ByteString+document docHead cnt =+ [i|<html>+ <head>+ #{renderLazyByteString $ addContext DocumentHead docHead}+ </head>+ <body>+ #{cnt}+ </body>+</html>|]+++{- | Create a custom \<head\> to use with 'document'. Remember to include at least `scriptEmbed`!++> import Web.Hyperbole (scriptEmbed, cssEmbed)+>+> #EMBED Example/Docs/App.hs documentHead+>+> #EMBED Example/Docs/App.hs app+-}+data DocumentHead = DocumentHead+++{- | A simple mobile-friendly document with all required embeds and live reload++@+'liveApp' quickStartDocument ('routeRequest' router)+@+-}+quickStartDocument :: BL.ByteString -> BL.ByteString+quickStartDocument = document (mobileFriendly >> quickStart)+++-- | A simple mobile-friendly header with all required embeds and live reload+quickStart :: View DocumentHead ()+quickStart = do+ mobileFriendly+ style cssEmbed+ script' scriptEmbed+ script' scriptLiveReload+++-- | Set the viewport to handle mobile zoom+mobileFriendly :: View DocumentHead ()+mobileFriendly = do+ meta @ httpEquiv "Content-Type" . content "text/html" . charset "UTF-8"+ meta @ name "viewport" . content "width=device-width, initial-scale=1.0"
+ src/Web/Hyperbole/Effect/Client.hs view
@@ -0,0 +1,52 @@+module Web.Hyperbole.Effect.Client where++import Data.Aeson+import Data.Text (Text)+import Effectful+import Effectful.Dispatch.Dynamic+import Effectful.Reader.Dynamic+import Web.Hyperbole.Effect.Hyperbole+import Web.Hyperbole.HyperView+import Web.Hyperbole.Types.Client (clientSetPageTitle)+import Web.Hyperbole.Types.Event+++{- | Trigger an action for an arbitrary 'HyperView'++#EXAMPLE /advanced++@+#EMBED Example/Page/Advanced.hs instance HyperView Controls+@+-}+trigger :: (HyperView id es, HyperViewHandled id view, Hyperbole :> es) => id -> Action id -> Eff (Reader view : es) ()+trigger vid act = do+ send $ TriggerAction (TargetViewId $ encodeViewId vid) (toAction act)+++{- | Dispatch a custom javascript event. This is emitted on the current hyper view and bubbles up to the document++#EXAMPLE /javascript++@+#EMBED Example/Page/Javascript.hs instance HyperView Message+@++@+#EMBED static/custom.js function listenServerEvents+@+-}+pushEvent :: (ToJSON a, Hyperbole :> es) => Text -> a -> Eff es ()+pushEvent nm a = do+ send $ TriggerEvent nm (toJSON a)+++{- | Set the document title++@+#EMBED Example/Docs/Client.hs page+@+-}+pageTitle :: (Hyperbole :> es) => Text -> Eff es ()+pageTitle t = do+ send $ ModClient $ clientSetPageTitle t
− src/Web/Hyperbole/Effect/Event.hs
@@ -1,36 +0,0 @@-module Web.Hyperbole.Effect.Event where--import Data.ByteString (ByteString)-import Data.String.Conversions (cs)-import Data.Text (Text)-import Effectful-import Network.HTTP.Types (Query)-import Web.Hyperbole.Effect.Hyperbole (Hyperbole)-import Web.Hyperbole.Effect.Request (request)-import Web.Hyperbole.Effect.Server (Event (..), Request (..))-import Web.Hyperbole.HyperView (HyperView (..), ViewAction (..), ViewId (..))---getEvent :: (HyperView id es, Hyperbole :> es) => Eff es (Maybe (Event id (Action id)))-getEvent = do- q <- (.query) <$> request- pure $ do- Event ti ta <- lookupEvent q- vid <- parseViewId ti- act <- parseAction ta- pure $ Event vid act---lookupEvent :: Query -> Maybe (Event Text Text)-lookupEvent q = do- viewId <- lookupParam "hyp-id" q- action <- lookupParam "hyp-action" q- pure $ Event{viewId, action}----- | Lower-level lookup straight from the request-lookupParam :: ByteString -> Query -> Maybe Text-lookupParam key q = do- mval <- lookup key q- val <- mval- pure $ cs val
+ src/Web/Hyperbole/Effect/GenRandom.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE LambdaCase #-}++module Web.Hyperbole.Effect.GenRandom where++import Control.Monad (replicateM)+import Data.Aeson (FromJSON, ToJSON)+import Data.String.Conversions (cs)+import Data.Text (Text)+import Effectful+import Effectful.Dispatch.Dynamic+import System.Random (Random, randomRIO)+import Web.Hyperbole.Data.Param (FromParam, ToParam)+++data GenRandom :: Effect where+ GenRandom :: (Random a) => (a, a) -> GenRandom m a+ GenRandomToken :: Int -> GenRandom m (Token a)+ GenRandomList :: (Random a) => [a] -> GenRandom m a+++type instance DispatchOf GenRandom = 'Dynamic+++runRandom+ :: (IOE :> es)+ => Eff (GenRandom : es) a+ -> Eff es a+runRandom = interpret $ \_ -> \case+ GenRandom range -> liftIO $ randomRIO range+ GenRandomToken n -> do+ let chars = ['a' .. 'z'] ++ ['A' .. 'Z'] ++ ['0' .. '9']+ randStr <- liftIO $ replicateM n (randomFromList chars)+ pure $ Token $ cs randStr+ GenRandomList as ->+ liftIO $ randomFromList as+ where+ randomFromList :: (Random a) => [a] -> IO a+ randomFromList as = do+ index <- liftIO $ randomRIO (0, length as - 1)+ pure $ as !! index+++genRandom :: (Random a, GenRandom :> es) => (a, a) -> Eff es a+genRandom range = send $ GenRandom range+++genRandomToken :: (GenRandom :> es) => Int -> Eff es (Token a)+genRandomToken num = send $ GenRandomToken num+++genRandomList :: (Random a, GenRandom :> es) => [a] -> Eff es a+genRandomList as = send $ GenRandomList as+++newtype Token a = Token {value :: Text}+ deriving newtype (FromJSON, ToJSON, FromParam, ToParam, Eq, Show, Read)
− src/Web/Hyperbole/Effect/Handler.hs
@@ -1,139 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE UndecidableInstances #-}--module Web.Hyperbole.Effect.Handler where--import Data.Kind (Type)-import Effectful-import Effectful.Dispatch.Dynamic-import Effectful.Reader.Dynamic-import Web.Hyperbole.Effect.Event (getEvent, lookupEvent)-import Web.Hyperbole.Effect.Hyperbole-import Web.Hyperbole.Effect.Request (request)-import Web.Hyperbole.Effect.Response (respondEarly)-import Web.Hyperbole.Effect.Server-import Web.Hyperbole.HyperView-import Web.View---class RunHandlers (views :: [Type]) es where- runHandlers :: (Hyperbole :> es) => Eff es ()---instance RunHandlers '[] es where- runHandlers = pure ()---instance (HyperView view es, RunHandlers views es) => RunHandlers (view : views) es where- runHandlers = do- runHandler @view (update @view)- runHandlers @views---runHandler- :: forall id es- . (HyperView id es, Hyperbole :> es)- => (Action id -> Eff (Reader id : es) (View id ()))- -> Eff es ()-runHandler run = do- -- Get an event matching our type. If it doesn't match, skip to the next handler- mev <- getEvent @id :: Eff es (Maybe (Event id (Action id)))- case mev of- Just event -> do- vw <- runReader event.viewId $ run event.action- respondEarly event.viewId vw- _ -> do- pure ()---runLoad- :: forall views es- . (Hyperbole :> es, RunHandlers views es)- => Eff es (View (Root views) ())- -> Eff es Response-runLoad loadPage = do- runHandlers @views- guardNoEvent- loadToResponse loadPage---guardNoEvent :: (Hyperbole :> es) => Eff es ()-guardNoEvent = do- q <- (.query) <$> request- case lookupEvent q of- -- Are id and action set to something?- Just e -> send $ RespondEarly $ Err $ ErrNotHandled e- Nothing -> pure ()---loadToResponse :: Eff es (View (Root total) ()) -> Eff es Response-loadToResponse run = do- vw <- run- let vid = TargetViewId (toViewId Root)- let res = Response vid $ addContext Root vw- pure res----- deriving newtype (Applicative, Monad, Functor)--{- | The load handler is run when the page is first loaded. Run any side effects needed, then return a view of the full page--@-myPage :: (Hyperbole :> es) => UserId -> Page es Response-myPage userId = do- 'load' $ do- user <- loadUserFromDatabase userId- pure $ userPageView user-@--}---- load--- :: (Hyperbole :> es)--- => Eff es (View (Root views) ())--- -> Page views es Response--- load run = Page $ do--- r <- request--- case lookupEvent r.query of--- -- Are id and action set to sometjhing?--- Just e ->--- pure $ Err $ ErrNotHandled e--- Nothing -> do--- vw <- run--- view vw--{- | A handler is run when an action for that 'HyperView' is triggered. Run any side effects needed, then return a view of the corresponding type--@-myPage :: ('Hyperbole' :> es) => 'Page' es 'Response'-myPage = do- 'handle' messages- 'load' pageView--messages :: ('Hyperbole' :> es, MessageDatabase) => Message -> MessageAction -> 'Eff' es ('View' Message ())-messages (Message mid) ClearMessage = do- deleteMessageSideEffect mid- pure $ messageView ""--messages (Message mid) (Louder m) = do- let new = m <> "!"- saveMessageSideEffect mid new- pure $ messageView new-@--}--{- | Hyperbole applications are divided into Pages. Each Page must 'load' the whole page , and 'handle' each /type/ of 'HyperView'--@-myPage :: ('Hyperbole' :> es) => 'Page' es 'Response'-myPage = do- 'handle' messages- 'load' pageView--pageView = do- el_ "My Page"- 'hyper' (Message 1) $ messageView "Starting Message"-@--}---- pageView :: (Hyperbole :> es, Handlers views es) => View (Root views) () -> Eff es (Page views)--- pageView = pure
src/Web/Hyperbole/Effect/Hyperbole.hs view
@@ -3,62 +3,68 @@ module Web.Hyperbole.Effect.Hyperbole where +import Data.Aeson (Value)+import Data.Text (Text) import Effectful import Effectful.Dispatch.Dynamic import Effectful.Error.Static import Effectful.State.Static.Local-import Web.Hyperbole.Effect.Server+import Effectful.Writer.Static.Local+import Web.Hyperbole.Data.Encoded+import Web.Hyperbole.Types.Client+import Web.Hyperbole.Types.Event+import Web.Hyperbole.Types.Request+import Web.Hyperbole.Types.Response --- | The 'Hyperbole' 'Effect' allows you to access information in the 'Request', manually 'respondEarly', and manipulate the Client 'session' and 'query'.+-- | The 'Hyperbole' 'Effect' allows you to access information in the 'Request', manually respond, and manipulate the Client 'session' and 'query'. data Hyperbole :: Effect where GetRequest :: Hyperbole m Request- RespondEarly :: Response -> Hyperbole m a+ RespondNow :: Response -> Hyperbole m a ModClient :: (Client -> Client) -> Hyperbole m () GetClient :: Hyperbole m Client+ -- TODO: this should actually execute the other view, and send the response to the client+ TriggerAction :: TargetViewId -> Encoded -> Hyperbole m ()+ TriggerEvent :: Text -> Value -> Hyperbole m () type instance DispatchOf Hyperbole = 'Dynamic --- | Run the 'Hyperbole' effect to 'Server'+data Remote+ = RemoteAction TargetViewId Encoded+ | RemoteEvent Text Value+++-- | Run the 'Hyperbole' effect to get a response runHyperbole- :: (Server :> es)- => Eff (Hyperbole : es) Response- -> Eff es Response-runHyperbole = fmap combine $ reinterpret runLocal $ \_ -> \case+ :: Request+ -> Eff (Hyperbole : es) Response+ -> Eff es (Response, Client, [Remote])+runHyperbole req = reinterpret runLocal $ \_ -> \case GetRequest -> do- gets @HyperState (.request)- RespondEarly r -> do- s <- gets @HyperState (.client)- send $ SendResponse s r+ pure req+ RespondNow r -> do throwError_ r GetClient -> do- gets @HyperState (.client)+ get @Client ModClient f -> do- modify @HyperState $ \st -> st{client = f st.client}+ modify @Client f+ TriggerAction vid act -> do+ tell [RemoteAction vid act]+ TriggerEvent name dat -> do+ tell [RemoteEvent name dat] where- runLocal :: (Server :> es) => Eff (State HyperState : Error Response : es) a -> Eff es (Either Response (a, HyperState))+ runLocal :: Eff (Error Response : State Client : Writer [Remote] : es) Response -> Eff es (Response, Client, [Remote]) runLocal eff = do- -- Load the request ONCE right when we start- r <- send LoadRequest- let client = Client mempty mempty- let st = HyperState r client- runErrorNoCallStack @Response . runState st $ eff-- combine :: (Server :> es) => Eff es (Either Response (Response, HyperState)) -> Eff es Response- combine eff = do- er <- eff- case er of- Left res ->- -- responded early, don't need to respond again- pure res- Right (res, st) -> do- send $ SendResponse st.client res- pure res-+ ((eresp, client'), rmts) <- runWriter @[Remote] . runState (emptyClient req.requestId) . runErrorNoCallStack @Response $ eff+ pure (either id id eresp, client', rmts) -data HyperState = HyperState- { request :: Request- , client :: Client- }+ emptyClient :: RequestId -> Client+ emptyClient requestId =+ Client+ { requestId+ , session = mempty+ , query = mempty+ , pageTitle = Nothing+ }
+ src/Web/Hyperbole/Effect/OAuth2.hs view
@@ -0,0 +1,290 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE LambdaCase #-}++module Web.Hyperbole.Effect.OAuth2+ ( OAuth2 (..)+ , authUrl+ , validateCode+ , exchangeAuth+ , exchangeRefresh+ , runOAuth2+ , getConfigEnv+ , Scopes (..)+ , AuthFlow (..)+ , Config (..)+ , TokenType (..)+ , Authenticated (..)+ , Token (..)+ , ClientId+ , ClientSecret+ , Code+ , Access+ , Refresh+ , State+ , Auth+ , OAuth2Error (..)+ ) where++import Control.Monad (unless, when)+import Data.Aeson (FromJSON (..), Options (..), ToJSON (..), Value (..), defaultOptions, eitherDecode, genericParseJSON, genericToJSON)+import Data.ByteString.Lazy qualified as BL+import Data.Default+import Data.Maybe (isJust)+import Data.String (IsString (..))+import Data.String.Conversions (cs)+import Data.Text (Text)+import Data.Text qualified as T+import Effectful+import Effectful.Dispatch.Dynamic+import Effectful.Environment+import Effectful.Exception+import GHC.Generics (Generic)+import Network.HTTP.Client (HttpException, Request (..), RequestBody (..))+import Network.HTTP.Client qualified as HTTP+import Network.HTTP.Types (hAccept, hContentType)+import Network.URI (parseURI)+import Text.Casing (quietSnake)+import Web.Hyperbole.Data.Encoded+import Web.Hyperbole.Data.Param+import Web.Hyperbole.Data.URI+import Web.Hyperbole.Effect.GenRandom+import Web.Hyperbole.Effect.Hyperbole+import Web.Hyperbole.Effect.Query+import Web.Hyperbole.Effect.Response (respondError)+import Web.Hyperbole.Effect.Session (Session (..), deleteSession, saveSession, session)+import Web.Hyperbole.Types.Response+++-- TODO: could all hyperbole applications have an oauth2 redirect endpoint?+-- TVar (Map State (Eff es _))+-- then you could execute it afterwards!++-- High level effect interface++authUrl :: (OAuth2 :> es) => URI -> Scopes -> Eff es URI+authUrl redirectUrl scopes = send $ AuthUrl redirectUrl scopes+++validateCode :: (OAuth2 :> es) => Eff es (Token Code)+validateCode = send ValidateCode+++exchangeAuth :: (OAuth2 :> es) => Token Code -> Eff es Authenticated+exchangeAuth authCode = send $ ExchangeAuth authCode+++exchangeRefresh :: (OAuth2 :> es) => Token Refresh -> Eff es Authenticated+exchangeRefresh refToken = send $ ExchangeRefresh refToken+++data OAuth2 :: Effect where+ AuthUrl :: URI -> Scopes -> OAuth2 m URI+ ValidateCode :: OAuth2 m (Token Code)+ ExchangeAuth :: Token Code -> OAuth2 m Authenticated+ ExchangeRefresh :: Token Refresh -> OAuth2 m Authenticated+++type instance DispatchOf OAuth2 = 'Dynamic+++runOAuth2+ :: (GenRandom :> es, IOE :> es, Hyperbole :> es)+ => Config+ -> HTTP.Manager+ -> Eff (OAuth2 : es) a+ -> Eff es a+runOAuth2 cfg mgr = interpret $ \_ -> \case+ AuthUrl red scopes -> do+ state <- genRandomToken 6+ let url = authorizationUrl cfg.authorize cfg.clientId red scopes state+ saveSession $ AuthFlow red state+ pure url+ ValidateCode -> do+ flow <- session @AuthFlow+ validateRedirectParams flow+ ExchangeAuth authCode -> do+ flow <- session @AuthFlow+ let params = tokenParams cfg.clientId cfg.clientSecret flow.redirect authCode+ auth <- sendTokenRequest cfg mgr params+ deleteSession @AuthFlow+ pure auth+ ExchangeRefresh refToken -> do+ let params = refreshParams cfg.clientId cfg.clientSecret refToken+ sendTokenRequest cfg mgr params+++{- | read oauth config from env. This is not required, you can obtain these secrets another way+and configure the app however you please. Just pass the results into runOAuth2 in your app+-}+getConfigEnv :: (Environment :> es) => Eff es Config+getConfigEnv = do+ clientId <- Token . cs <$> getEnv "OAUTH2_CLIENT_ID"+ clientSecret <- Token . cs <$> getEnv "OAUTH2_CLIENT_SECRET"+ authorize <- Endpoint <$> getEnvURI "OAUTH2_AUTHORIZE_ENDPOINT"+ token <- Endpoint <$> getEnvURI "OAUTH2_TOKEN_ENDPOINT"+ pure $ Config{clientId, clientSecret, authorize, token}+ where+ getEnvURI n = do+ str <- getEnv n+ case parseURI str of+ Nothing -> throwIO $ OAuth2BadEnv n str+ Just u -> pure u+++-- Types -------------------------------------------------++newtype Scopes = Scopes [Text]+ deriving (Show, Generic)+ deriving anyclass (FromParam, ToParam)+instance ToJSON Scopes where+ toJSON (Scopes ss) = String $ T.unwords ss+instance FromJSON Scopes where+ parseJSON v = do+ t <- parseJSON @String v+ pure $ fromString t+instance IsString Scopes where+ fromString s = Scopes $ T.words $ cs s+++data ClientId+data ClientSecret+data Code+data Refresh+data Access+data State+data Auth++#if (!MIN_VERSION_aeson(2,2,0))+instance FromJSON URI+instance ToJSON URI+instance FromJSON URIAuth+instance ToJSON URIAuth+#endif+++data AuthFlow = AuthFlow+ { redirect :: URI+ , state :: Token State+ }+ deriving (Generic, FromEncoded, ToEncoded)+instance Session AuthFlow where+ sessionKey = "OAuth2AuthFlow"+ cookiePath = Just "/"+instance Default AuthFlow where+ def = AuthFlow (pathUri "/") (Token mempty)+++data Config = Config+ { clientId :: Token ClientId+ , clientSecret :: Token ClientSecret+ , authorize :: Endpoint Auth+ , token :: Endpoint (Token ())+ }+++data TokenType+ = Bearer+ deriving (Show, Read, Generic, ToParam, FromParam)+instance ToJSON TokenType where+ toJSON s = toJSON $ show s+instance FromJSON TokenType where+ parseJSON (String ttyp) | T.toLower ttyp == "bearer" = pure Bearer+ parseJSON val = fail $ "expected TokenType but got " <> show val+++data Authenticated = Authenticated+ { tokenType :: TokenType+ , expiresIn :: Maybe Int+ , scope :: Maybe Scopes+ , accessToken :: Token Access+ , refreshToken :: Maybe (Token Refresh)+ }+ deriving (Generic, Show, ToParam, FromParam, ToEncoded, FromEncoded)+instance FromJSON Authenticated where+ parseJSON = genericParseJSON defaultOptions{fieldLabelModifier = quietSnake}+instance ToJSON Authenticated where+ toJSON = genericToJSON defaultOptions{fieldLabelModifier = quietSnake}+instance Session Authenticated where+ sessionKey = "OAuth2Authenticated"+ cookiePath = Just "/"+++data OAuth2Error+ = OAuth2BadResponse String BL.ByteString+ | OAuth2TokenRequest HttpException+ | OAuth2BadEnv String String+ deriving (Show, Exception)+++-- Lower level --------------------------------------------------++authorizationUrl :: Endpoint Auth -> Token ClientId -> URI -> Scopes -> Token State -> URI+authorizationUrl (Endpoint auth) (Token cid) redUrl (Scopes scopes) (Token state) =+ auth{uriQuery = cs $ renderQuery True authParams}+ where+ authParams =+ [ ("response_type", Just "code")+ , ("client_id", Just $ cs cid)+ , ("redirect_uri", Just $ cs $ uriToText redUrl)+ , ("scope", Just $ cs $ T.intercalate " " scopes)+ , ("state", Just $ cs state)+ ]+++tokenParams :: Token ClientId -> Token ClientSecret -> URI -> Token Code -> Query+tokenParams (Token cid) (Token sec) redUrl (Token ac) =+ [ ("grant_type", Just "authorization_code")+ , ("client_id", Just $ cs cid)+ , ("client_secret", Just $ cs sec)+ , ("redirect_uri", Just $ cs $ uriToText redUrl)+ , ("code", Just $ cs ac)+ ]+++refreshParams :: Token ClientId -> Token ClientSecret -> Token Refresh -> Query+refreshParams (Token cid) (Token sec) (Token ref) =+ [ ("grant_type", Just "refresh_token")+ , ("client_id", Just $ cs cid)+ , ("client_secret", Just $ cs sec)+ , ("refresh_token", Just $ cs ref)+ ]+++validateRedirectParams :: (Hyperbole :> es) => AuthFlow -> Eff es (Token Code)+validateRedirectParams flow = do+ err <- lookupParam @Text "error"++ when (isJust err) $ do+ desc <- param "error_description"+ respondError $ ErrAuth desc++ authState <- param @(Token State) "state"+ authCode <- param @(Token Code) "code"++ unless (flow.state == authState) $ do+ respondError $ ErrAuth "Oauth2 State mismatch"++ pure authCode+++sendTokenRequest :: (IOE :> es) => Config -> HTTP.Manager -> Query -> Eff es Authenticated+sendTokenRequest cfg mgr params = do+ baseReq <- HTTP.requestFromURI cfg.token.uri++ let req =+ baseReq+ { method = "POST"+ , requestBody = RequestBodyBS $ renderQuery False params+ , requestHeaders =+ [ (hContentType, "application/x-www-form-urlencoded")+ , (hAccept, "application/json")+ ]+ }++ res <- liftIO (HTTP.httpLbs req mgr) `catch` (throwIO . OAuth2TokenRequest)++ let body = HTTP.responseBody res+ case eitherDecode @Authenticated body of+ Left e -> throwIO $ OAuth2BadResponse e body+ Right tr -> pure tr
src/Web/Hyperbole/Effect/Query.hs view
@@ -1,22 +1,26 @@ module Web.Hyperbole.Effect.Query where import Data.ByteString qualified as BS+import Data.Default (Default (..)) import Data.Maybe (fromMaybe) import Data.String.Conversions (cs) import Effectful import Effectful.Dispatch.Dynamic (send)-import Web.Hyperbole.Data.QueryData (FromParam (..), FromQuery (..), Param, QueryData (..), ToParam (..), ToQuery (..), queryData)+import Web.Hyperbole.Data.Param (FromParam (..), Param, ToParam (..))+import Web.Hyperbole.Data.QueryData (FromQuery (..), QueryData (..), ToQuery (..), queryData) import Web.Hyperbole.Data.QueryData qualified as QueryData import Web.Hyperbole.Effect.Hyperbole (Hyperbole (..)) import Web.Hyperbole.Effect.Request (request)-import Web.Hyperbole.Effect.Server (Client (..), Request (..), Response (..), ResponseError (..))+import Web.Hyperbole.Types.Client (Client (..), clientSetQuery)+import Web.Hyperbole.Types.Request+import Web.Hyperbole.Types.Response import Prelude {- | Parse querystring from the 'Request' into a datatype. See 'FromQuery' @-#EMBED Example/Docs/Encoding.hs data Filters+#EMBED Example/Docs/Params.hs data Filters #EMBED Example/Docs/Params.hs page @@@ -25,7 +29,7 @@ query = do q <- queryParams case parseQuery q of- Left e -> send $ RespondEarly $ Err $ ErrQuery $ "Query Parse " <> e <> " from " <> cs (show q)+ Left e -> send $ RespondNow $ Err $ ErrQuery $ "Query Parse " <> e <> " from " <> cs (show q) Right a -> pure a @@ -37,9 +41,17 @@ -} setQuery :: (ToQuery a, Hyperbole :> es) => a -> Eff es () setQuery a = do- modifyQuery (const $ toQuery a)+ modifyQueryData (const $ toQuery a) +modifyQuery :: (ToQuery a, FromQuery a, Default a, Hyperbole :> es) => (a -> a) -> Eff es a+modifyQuery f = do+ s <- query+ let updated = f s+ setQuery updated+ pure updated++ {- | Parse a single query parameter. Return a 400 status if missing or if parsing fails. See 'FromParam' @@@ -50,11 +62,17 @@ param p = do q <- queryParams case QueryData.require p q of- Left e -> send $ RespondEarly $ Err $ ErrQuery e+ Left e -> send $ RespondNow $ Err $ ErrQuery (cs e) Right a -> pure a --- | Parse a single parameter from the query string if available+{- | Parse a single parameter from the query string if available+++@+#EMBED Example/Docs/SideEffects.hs page+@+-} lookupParam :: (FromParam a, Hyperbole :> es) => Param -> Eff es (Maybe a) lookupParam p = do QueryData.lookup p <$> queryParams@@ -68,16 +86,16 @@ -} setParam :: (ToParam a, Hyperbole :> es) => Param -> a -> Eff es () setParam key a = do- modifyQuery (QueryData.insert key a)+ modifyQueryData (QueryData.insert key a) -- | Delete a single parameter from the query string deleteParam :: (Hyperbole :> es) => Param -> Eff es () deleteParam key = do- modifyQuery (QueryData.delete key)+ modifyQueryData (QueryData.delete key) --- | Return the query from 'Request' as a 'QueryData'+-- | Return the querystring from 'Request' as a 'QueryData' queryParams :: (Hyperbole :> es) => Eff es QueryData queryParams = do cq <- clientQuery@@ -95,8 +113,7 @@ "hyp-" `BS.isPrefixOf` key -modifyQuery :: (Hyperbole :> es) => (QueryData -> QueryData) -> Eff es ()-modifyQuery f = do+modifyQueryData :: (Hyperbole :> es) => (QueryData -> QueryData) -> Eff es ()+modifyQueryData f = do q <- queryParams- send $ ModClient $ \client ->- Client{query = Just $ f q, session = client.session}+ send $ ModClient $ clientSetQuery (f q)
src/Web/Hyperbole/Effect/Request.hs view
@@ -1,11 +1,13 @@ module Web.Hyperbole.Effect.Request where +import Data.String.Conversions (cs) import Effectful import Effectful.Dispatch.Dynamic import Web.FormUrlEncoded (Form, urlDecodeForm)+import Web.Hyperbole.Data.URI (Path (..)) import Web.Hyperbole.Effect.Hyperbole-import Web.Hyperbole.Effect.Server-import Web.View+import Web.Hyperbole.Types.Request+import Web.Hyperbole.Types.Response -- | Return all information about the 'Request'@@ -18,7 +20,7 @@ >>> reqPath ["users", "100"] -}-reqPath :: (Hyperbole :> es) => Eff es [Segment]+reqPath :: (Hyperbole :> es) => Eff es Path reqPath = (.path) <$> request @@ -30,4 +32,4 @@ formBody = do b <- (.body) <$> request let ef = urlDecodeForm b- either (send . RespondEarly . Err . ErrParse) pure ef+ either (send . RespondNow . Err . ErrParse . cs) pure ef
src/Web/Hyperbole/Effect/Response.hs view
@@ -3,22 +3,36 @@ import Data.Text (Text) import Effectful import Effectful.Dispatch.Dynamic+import Web.Hyperbole.Data.Encoded+import Web.Hyperbole.Data.URI import Web.Hyperbole.Effect.Hyperbole (Hyperbole (..))-import Web.Hyperbole.Effect.Server (Response (..), ResponseError (..), TargetViewId (..)) import Web.Hyperbole.HyperView (HyperView (..), ViewId (..), hyperUnsafe)-import Web.View (Url, View)+import Web.Hyperbole.Types.Event+import Web.Hyperbole.Types.Response+import Web.Hyperbole.View.Types --- | Respond with the given view, and stop execution-respondEarly :: (Hyperbole :> es, HyperView id es) => id -> View id () -> Eff es ()-respondEarly i vw = do- let vid = TargetViewId (toViewId i)- let res = Response vid $ hyperUnsafe i vw- send $ RespondEarly res+-- | Respond with the given hyperview+hyperView :: (HyperView id es) => id -> View id () -> Eff es Response+hyperView i vw = do+ let vid = TargetViewId (encodedToText $ toViewId i)+ pure $ Response vid $ hyperUnsafe i vw -{- | Respond immediately with 404 Not Found+-- | Abort execution and respond with an error+respondError :: (Hyperbole :> es) => ResponseError -> Eff es a+respondError err = do+ send $ RespondNow $ Err err ++-- | Abort execution and respond with an error view+respondErrorView :: (Hyperbole :> es) => Text -> View Body () -> Eff es a+respondErrorView msg vw = do+ send $ RespondNow $ Err $ ErrCustom $ ServerError msg vw+++{- | Abort execution and respond with 404 Not Found+ @ #EMBED Example/Docs/App.hs findUser @@ -26,20 +40,20 @@ @ -} notFound :: (Hyperbole :> es) => Eff es a-notFound = send $ RespondEarly NotFound+notFound = send $ RespondNow $ Err NotFound -- | Respond immediately with a parse error-parseError :: (Hyperbole :> es) => Text -> Eff es a-parseError = send . RespondEarly . Err . ErrParse+parseError :: (Hyperbole :> es) => String -> Eff es a+parseError = respondError . ErrParse --- | Redirect immediately to the 'Url'-redirect :: (Hyperbole :> es) => Url -> Eff es a-redirect = send . RespondEarly . Redirect+-- | Abort execution and redirect to a 'URI'+redirect :: (Hyperbole :> es) => URI -> Eff es a+redirect = send . RespondNow . Redirect --- | Manually set the response to the given view. Normally you would return a 'View' from 'runPage' instead-view :: (Hyperbole :> es) => View () () -> Eff es Response-view vw = do- pure $ Response (TargetViewId "") vw+-- | Respond with a generic view. Normally you will return a view from the page or handler instead of using this function+view :: View Body () -> Response+view =+ Response (TargetViewId "")
− src/Web/Hyperbole/Effect/Server.hs
@@ -1,292 +0,0 @@-{-# LANGUAGE LambdaCase #-}--module Web.Hyperbole.Effect.Server where--import Data.ByteString qualified as BS-import Data.ByteString.Lazy qualified as BL-import Data.List qualified as L-import Data.Maybe (fromMaybe)-import Data.String.Conversions (cs)-import Data.Text (Text, pack)-import Effectful-import Effectful.Dispatch.Dynamic-import Effectful.Error.Static-import Effectful.State.Static.Local-import Network.HTTP.Types (HeaderName, Method, Query, status200, status400, status401, status404, status500, urlDecode, urlEncode)-import Network.Wai qualified as Wai-import Network.Wai.Internal (ResponseReceived (..))-import Network.WebSockets (Connection)-import Network.WebSockets qualified as WS-import Web.Cookie (parseCookies)-import Web.Hyperbole.Data.QueryData as QueryData-import Web.Hyperbole.Data.Session as Cookies-import Web.Hyperbole.Route-import Web.View (Segment, View, renderLazyByteString, renderUrl)----- | Low level effect mapping request/response to either HTTP or WebSockets-data Server :: Effect where- LoadRequest :: Server m Request- SendResponse :: Client -> Response -> Server m ()---type instance DispatchOf Server = 'Dynamic---runServerWai- :: (IOE :> es)- => (BL.ByteString -> BL.ByteString)- -> Wai.Request- -> (Wai.Response -> IO ResponseReceived)- -> Eff (Server : es) a- -> Eff es (Maybe Wai.ResponseReceived)-runServerWai toDoc req respond =- reinterpret runLocal $ \_ -> \case- LoadRequest -> do- fromWaiRequest req- SendResponse client r -> do- rr <- liftIO $ sendResponse client r- put (Just rr)- where- runLocal :: (IOE :> es) => Eff (State (Maybe ResponseReceived) : es) a -> Eff es (Maybe ResponseReceived)- runLocal = execState Nothing-- sendResponse :: Client -> Response -> IO Wai.ResponseReceived- sendResponse client r =- respond $ response r- where- response :: Response -> Wai.Response- response NotFound = respError status404 "Not Found"- response Empty = respError status500 "Empty Response"- response (Err (ErrParse e)) = respError status400 ("Parse Error: " <> cs e)- response (Err (ErrQuery e)) = respError status400 $ "ErrQuery: " <> cs e- response (Err (ErrSession param e)) = respError status400 $ "ErrSession: " <> cs (show param) <> " " <> cs e- response (Err (ErrOther e)) = respError status500 $ "Server Error: " <> cs e- response (Err ErrAuth) = respError status401 "Unauthorized"- response (Err (ErrNotHandled e)) = respError status400 $ cs $ errNotHandled e- response (Response _ vw) =- respHtml $- addDocument (Wai.requestMethod req) (renderLazyByteString vw)- response (Redirect u) = do- let url = renderUrl u- -- We have to use a 200 javascript redirect because javascript- -- will redirect the fetch(), while we want to redirect the whole page- -- see index.ts sendAction()- let headers = ("Location", cs url) : contentType ContentHtml : setCookies- Wai.responseLBS status200 headers $ "<script>window.location = '" <> cs url <> "'</script>"-- respError s = Wai.responseLBS s [contentType ContentText]-- respHtml body =- -- always set the session...- let headers = contentType ContentHtml : (setCookies <> setQuery client.query)- in Wai.responseLBS status200 headers body-- setCookies =- fmap setCookie $ Cookies.toList client.session-- setCookie :: Cookie -> (HeaderName, BS.ByteString)- setCookie cookie =- ("Set-Cookie", renderCookie (Wai.pathInfo req) cookie)-- setQuery Nothing = []- setQuery (Just qd) =- [("Set-Query", QueryData.render qd)]-- -- convert to document if full page request. Subsequent POST requests will only include fragments- addDocument :: Method -> BL.ByteString -> BL.ByteString- addDocument "GET" bd = toDoc bd- addDocument _ bd = bd-- fromWaiRequest :: (MonadIO m) => Wai.Request -> m Request- fromWaiRequest wr = do- body <- liftIO $ Wai.consumeRequestBodyLazy wr- let path = Wai.pathInfo wr- query = Wai.queryString wr- headers = Wai.requestHeaders wr- cookie = fromMaybe "" $ L.lookup "Cookie" headers- host = Host $ fromMaybe "" $ L.lookup "Host" headers- cookies = cookiesFromHeader (parseCookies cookie)- method = Wai.requestMethod wr- pure $ Request{body, path, query, method, cookies, host}---renderCookie :: [Segment] -> Cookie -> BS.ByteString-renderCookie requestPath cookie =- let path = fromMaybe requestPath cookie.path- in key <> "=" <> value cookie.value <> "; SameSite=None; secure; path=" <> cs (renderUrl (pathUrl path))- where- key = cs cookie.key.text- value Nothing = "; expires=Thu, 01 Jan 1970 00:00:00 GMT"- value (Just val) = urlEncode True (cs val.text)---cookiesFromHeader :: [(BS.ByteString, BS.ByteString)] -> Cookies-cookiesFromHeader cks = do- Cookies.fromList $ fmap toCookie cks- where- toCookie (k, v) =- let value = ParamValue (cs $ urlDecode True v)- key = Param (cs k)- in Cookie key (Just value) Nothing---runServerSockets- :: (IOE :> es)- => Connection- -> Request- -> Eff (Server : es) Response- -> Eff es Response-runServerSockets conn req = reinterpret runLocal $ \_ -> \case- LoadRequest -> pure req -- receiveRequest conn- SendResponse client res -> do- case res of- (Response vid vw) -> do- let meta = sessionMetas client.session <> queryMeta client.query- sendView meta vid vw- (Err r) -> sendError r- Empty -> sendError $ ErrOther "Empty"- NotFound -> sendError $ ErrOther "NotFound"- (Redirect url) -> sendRedirect (sessionMetas client.session) url- where- runLocal = runErrorNoCallStackWith @SocketError onSocketError-- onSocketError :: (IOE :> es) => SocketError -> Eff es Response- onSocketError e = do- let r = ErrOther $ cs $ show e- sendError r- pure $ Err r-- sendMessage :: (MonadIO m) => Metadata -> BL.ByteString -> m ()- sendMessage meta cnt = do- let msg = renderMetadata meta <> "\n" <> cnt- liftIO $ WS.sendTextData conn msg-- sendError :: (IOE :> es) => ResponseError -> Eff es ()- sendError r = do- -- TODO: better error handling!- sendMessage (metadata "ERROR" (pack (show r))) ""-- sendView :: (IOE :> es) => Metadata -> TargetViewId -> View () () -> Eff es ()- sendView meta vid vw = do- sendMessage (viewIdMeta vid <> meta) (renderLazyByteString vw)-- renderMetadata :: Metadata -> BL.ByteString- renderMetadata (Metadata m) = BL.intercalate "\n" $ fmap (uncurry metaLine) m-- sendRedirect :: (IOE :> es) => Metadata -> Url -> Eff es ()- sendRedirect meta u = do- -- conn <- ask @Connection- let r = metadata "REDIRECT" (renderUrl u)- sendMessage (r <> meta) ""-- sessionMetas :: Cookies -> Metadata- sessionMetas cookies = mconcat $ fmap cookieMeta $ Cookies.toList cookies-- cookieMeta :: Cookie -> Metadata- cookieMeta cookie =- Metadata [("COOKIE", cs (renderCookie req.path cookie))]-- queryMeta :: Maybe QueryData -> Metadata- queryMeta Nothing = mempty- queryMeta (Just q) =- Metadata [("QUERY", cs $ QueryData.render q)]-- viewIdMeta :: TargetViewId -> Metadata- viewIdMeta (TargetViewId vid) = Metadata [("VIEW-ID", cs vid)]-- metadata :: BL.ByteString -> Text -> Metadata- metadata name value = Metadata [(name, value)]-- metaLine :: BL.ByteString -> Text -> BL.ByteString- metaLine name value = "|" <> name <> "|" <> cs value---errNotHandled :: Event Text Text -> String-errNotHandled ev =- L.intercalate- "\n"- [ "No Handler for Event viewId: " <> cs ev.viewId <> " action: " <> cs ev.action- , "<p>Remember to add a `hyper` handler in your page function</p>"- , "<pre>"- , "page :: (Hyperbole :> es) => Page es Response"- , "page = do"- , " handle contentsHandler"- , " load $ do"- , " pure $ hyper Contents contentsView"- , "</pre>"- ]---data Client = Client- { session :: Cookies- , query :: Maybe QueryData- }---data SocketError- = InvalidMessage Text- deriving (Show, Eq)---data ContentType- = ContentHtml- | ContentText---contentType :: ContentType -> (HeaderName, BS.ByteString)-contentType ContentHtml = ("Content-Type", "text/html; charset=utf-8")-contentType ContentText = ("Content-Type", "text/plain; charset=utf-8")---newtype Metadata = Metadata [(BL.ByteString, Text)]- deriving newtype (Semigroup, Monoid)---newtype Host = Host {text :: BS.ByteString}- deriving (Show)---data Request = Request- { host :: Host- , path :: [Segment]- , query :: Query- , body :: BL.ByteString- , method :: Method- , cookies :: Cookies- }- deriving (Show)----- | Valid responses for a 'Hyperbole' effect. Use 'notFound', etc instead.-data Response- = Response TargetViewId (View () ())- | NotFound- | Redirect Url- | Err ResponseError- | Empty---data ResponseError- = ErrParse Text- | ErrQuery Text- | ErrSession Param Text- | ErrOther Text- | ErrNotHandled (Event Text Text)- | ErrAuth- deriving (Show)----- | Serialized ViewId-newtype TargetViewId = TargetViewId Text----- | An action, with its corresponding id-data Event id act = Event- { viewId :: id- , action :: act- }---instance (Show act, Show id) => Show (Event id act) where- show e = "Event " <> show e.viewId <> " " <> show e.action
src/Web/Hyperbole/Effect/Session.hs view
@@ -1,42 +1,84 @@ {-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DefaultSignatures #-} module Web.Hyperbole.Effect.Session where +import Data.Default (Default (..)) import Data.Maybe (fromMaybe)+import Data.String.Conversions (cs)+import Data.Text (Text) import Effectful import Effectful.Dispatch.Dynamic-import Web.Hyperbole.Data.QueryData-import Web.Hyperbole.Data.Session as Cookies+import GHC.Generics+import Web.Hyperbole.Data.Cookie as Cookie+import Web.Hyperbole.Data.Encoded as Encoded+import Web.Hyperbole.Data.Param+import Web.Hyperbole.Data.URI (Path) import Web.Hyperbole.Effect.Hyperbole (Hyperbole (..)) import Web.Hyperbole.Effect.Request (request)-import Web.Hyperbole.Effect.Server (Client (..), Request (..), Response (..), ResponseError (..))-import Prelude+import Web.Hyperbole.Types.Client (Client (..), clientModCookies)+import Web.Hyperbole.Types.Request+import Web.Hyperbole.Types.Response -{- | Persist datatypes in browser cookies. If the session doesn't exist, the 'DefaultParam' is used+{- | Configure a data type to persist in the 'session' as a cookie. These are type-indexed, so only one of each can exist in the session @ #EMBED Example/Docs/Sessions.hs data Preferences -#EMBED Example/Docs/Sessions.hs instance DefaultParam Preferences+#EMBED Example/Docs/Sessions.hs instance Default Preferences+@+-}+class Session a where+ -- | Unique key for this Session Type. Defaults to the datatypeName+ sessionKey :: Key+ default sessionKey :: (Generic a, GDatatypeName (Rep a)) => Key+ sessionKey = gDatatypeName $ from (undefined :: a) ++ -- | By default Sessions are persisted only to the current page. Set to `Just "/"` to make an instance available application-wide+ cookiePath :: Maybe Path+ default cookiePath :: Maybe Path+ cookiePath = Nothing+++ -- | Encode type to a a cookie value+ toCookie :: a -> CookieValue+ default toCookie :: (ToEncoded a) => a -> CookieValue+ toCookie = CookieValue . cs . Encoded.encode+++ -- | Decode from a cookie value. Defaults to FromJSON+ parseCookie :: CookieValue -> Either String a+ default parseCookie :: (FromEncoded a) => CookieValue -> Either String a+ parseCookie (CookieValue bs) = do+ Encoded.decodeEither (cs bs)+++{- | Load data from a browser cookie. If it doesn't exist, the 'Default' instance is used++@+#EMBED Example/Docs/Sessions.hs data Preferences++#EMBED Example/Docs/Sessions.hs instance Default Preferences+ #EMBED Example/Docs/Sessions.hs page @ -}-session :: (Session a, DefaultParam a, FromParam a, Hyperbole :> es) => Eff es a+session :: (Session a, Default a, Hyperbole :> es) => Eff es a session = do ms <- lookupSession- pure $ fromMaybe defaultParam ms+ pure $ fromMaybe def ms -- | Return a session if it exists-lookupSession :: forall a es. (Session a, FromParam a, Hyperbole :> es) => Eff es (Maybe a)+lookupSession :: forall a es. (Session a, Hyperbole :> es) => Eff es (Maybe a) lookupSession = do let key = sessionKey @a- mck <- Cookies.lookup key <$> sessionCookies+ mck <- Cookie.lookup key <$> sessionCookies case mck of Nothing -> pure Nothing- Just val -> parseSession key val+ Just val -> Just <$> parseSession key val {- | Persist datatypes in browser cookies@@ -44,17 +86,17 @@ @ #EMBED Example/Docs/Sessions.hs data Preferences -#EMBED Example/Docs/Sessions.hs instance DefaultParam Preferences+#EMBED Example/Docs/Sessions.hs instance Default Preferences #EMBED Example/Docs/Sessions.hs instance HyperView Content @ -}-saveSession :: (Session a, ToParam a, Hyperbole :> es) => a -> Eff es ()+saveSession :: forall a es. (Session a, Hyperbole :> es) => a -> Eff es () saveSession a = do- modifyCookies $ Cookies.insert (sessionCookie a)+ modifyCookies $ Cookie.insert $ sessionCookie a -modifySession :: (Session a, DefaultParam a, ToParam a, FromParam a, Hyperbole :> es) => (a -> a) -> Eff es a+modifySession :: (Session a, Default a, Hyperbole :> es) => (a -> a) -> Eff es a modifySession f = do s <- session let updated = f s@@ -62,7 +104,7 @@ pure updated -modifySession_ :: (Session a, DefaultParam a, ToParam a, FromParam a, Hyperbole :> es) => (a -> a) -> Eff es ()+modifySession_ :: (Session a, Default a, Hyperbole :> es) => (a -> a) -> Eff es () modifySession_ f = do _ <- modifySession f pure ()@@ -71,27 +113,27 @@ -- | Remove a single 'Session' from the browser cookies deleteSession :: forall a es. (Session a, Hyperbole :> es) => Eff es () deleteSession = do- modifyCookies $ Cookies.insert (deletedCookie @a)+ let cookie = Cookie (sessionKey @a) (cookiePath @a) Nothing+ modifyCookies $ Cookie.insert cookie -parseSession :: (FromParam a, Hyperbole :> es) => Param -> ParamValue -> Eff es a-parseSession prm val = do- case parseParam val of- Left e -> send $ RespondEarly $ Err $ ErrSession prm e+parseSession :: (Session a, Hyperbole :> es) => Key -> CookieValue -> Eff es a+parseSession prm cook = do+ case parseCookie cook of+ Left e -> send $ RespondNow $ Err $ ErrSession prm e Right a -> pure a -- | save a single datatype to a specific key in the session setCookie :: (ToParam a, Hyperbole :> es) => Cookie -> Eff es () setCookie ck = do- modifyCookies (Cookies.insert ck)+ modifyCookies (Cookie.insert ck) -- | Modify the client cookies modifyCookies :: (Hyperbole :> es) => (Cookies -> Cookies) -> Eff es () modifyCookies f =- send $ ModClient $ \client ->- Client{session = f client.session, query = client.query}+ send $ ModClient $ clientModCookies f -- | Return all the cookies, both those sent in the request and others added by the page@@ -112,3 +154,23 @@ requestSessionCookies :: (Hyperbole :> es) => Eff es Cookies requestSessionCookies = do (.cookies) <$> request+++sessionCookie :: forall a. (Session a) => a -> Cookie+sessionCookie a =+ Cookie (sessionKey @a) (cookiePath @a) (Just $ toCookie a)+++-- | generic datatype name+genericTypeName :: forall a. (Generic a, GDatatypeName (Rep a)) => Text+genericTypeName =+ gDatatypeName $ from (undefined :: a)+++class GDatatypeName f where+ gDatatypeName :: f p -> Text+++instance (Datatype d) => GDatatypeName (M1 D d f) where+ gDatatypeName _ =+ cs $ datatypeName (undefined :: M1 D d f p)
src/Web/Hyperbole/HyperView.hs view
@@ -1,217 +1,16 @@-{-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE UndecidableInstances #-}--module Web.Hyperbole.HyperView where--import Data.Kind (Constraint, Type)-import Data.Text (Text)-import Effectful-import Effectful.Reader.Dynamic-import GHC.TypeLits hiding (Mod)-import Web.Hyperbole.Data.QueryData (ParamValue (..), readQueryParam, showQueryParam)-import Web.Hyperbole.Effect.Hyperbole (Hyperbole)-import Web.Hyperbole.TypeList-import Web.View (View, addContext, att, context, el, flexCol, none)---{- | HyperViews are interactive subsections of a 'Page'--Create an instance with a unique view id type and a sum type describing the actions the HyperView supports. The View Id can contain context (a database id, for example)--@-#EMBED Example/Docs/Interactive.hs data Message--#EMBED Example/Docs/Interactive.hs instance HyperView Message es where-@--}-class (ViewId id, ViewAction (Action id)) => HyperView id es where- -- | Outline all actions that are permitted in this HyperView- --- -- > data Action Message = SetMessage Text | ClearMessage- -- > deriving (Show, Read, ViewAction)- data Action id--- -- | Include any child hyperviews here. The compiler will make sure that the page knows how to handle them- --- -- > type Require = '[ChildView]- type Require id :: [Type]--- type Require id = '[]--- -- | Specify how the view should be updated for each Action- --- -- > update (SetMessage msg) = pure $ messageView msg- -- > update ClearMessage = pure $ messageView ""- update :: (Hyperbole :> es) => Action id -> Eff (Reader id : es) (View id ())----- | The top-level view returned by a 'Page'. It carries a type-level list of every 'HyperView' used in our 'Page' so the compiler can check our work and wire everything together.-data Root (views :: [Type]) = Root- deriving (Show, Read, ViewId)---instance HyperView (Root views) es where- data Action (Root views) = RootNone- deriving (Show, Read, ViewAction)- type Require (Root views) = views- update _ = pure none---type family ValidDescendents x :: [Type] where- ValidDescendents x = x : NextDescendents '[] '[x]---type family NextDescendents (ex :: [Type]) (xs :: [Type]) where- NextDescendents _ '[] = '[]- NextDescendents ex (x ': xs) =- RemoveAll (x : ex) (Require x)- <++> NextDescendents ((x : ex) <++> Require x) (RemoveAll (x : ex) (Require x))- <++> NextDescendents (x : ex) (RemoveAll (x : ex) xs)---type NotHandled id ctx (views :: [Type]) =- TypeError- ( 'Text "HyperView "- :<>: 'ShowType id- :<>: 'Text " not found in (Require "- :<>: 'ShowType ctx- :<>: 'Text ")"- :$$: 'Text " "- :<>: 'ShowType views- :$$: 'Text "Try adding it to the HyperView instance:"- :$$: 'Text " instance HyperView "- :<>: 'ShowType ctx- :<>: 'Text " where"- :$$: 'Text " type Action "- :<>: 'ShowType ctx- :<>: 'Text " = "- :<>: ShowType (Action id)- :<>: 'Text ""- :$$: 'Text " type Require "- :<>: 'ShowType ctx- :<>: 'Text " = ["- :<>: ShowType id- :<>: 'Text ", ...]"- )---type NotDesc id ctx x cs =- TypeError- ( 'Text ""- :<>: 'ShowType x- :<>: 'Text ", a child of HyperView "- :<>: 'ShowType id- :<>: 'Text ", not handled by context "- :<>: 'ShowType ctx- :$$: ('Text " Require = " ':<>: 'ShowType cs)- -- ':$$: 'ShowType x- -- ':$$: 'ShowType cs- )---type NotInPage x total =- TypeError- ( 'Text ""- :<>: 'ShowType x- :<>: 'Text " not included in: "- :$$: 'Text " Page es "- :<>: ShowType total- :$$: 'Text "try expanding the page views to:"- :$$: 'Text " Page es "- :<>: ShowType (x : total)- -- :$$: 'Text " " :<>: 'ShowType ctx :<>: 'Text " = " :<>: ShowType (Action id) :<>: 'Text ""- -- :$$: 'Text " page :: (Hyperbole :> es) => Page es '[" :<>: 'ShowType ctx :<>: 'Text " = [" :<>: ShowType id :<>: 'Text ", ...]"- )---type HyperViewHandled id ctx =- ( -- the id must be found in the children of the context- ElemOr id (Require ctx) (NotHandled id ctx (Require ctx))- , -- Make sure the descendents of id are in the context for the root page- CheckDescendents id ctx- )----- TODO: Report which view requires the missing one-type family CheckDescendents id ctx :: Constraint where- CheckDescendents id (Root total) =- ( AllInPage (ValidDescendents id) total- )- CheckDescendents id ctx = ()---type family AllInPage ids total :: Constraint where- AllInPage '[] _ = ()- AllInPage (x ': xs) total =- (ElemOr x total (NotInPage x total), AllInPage xs total)---{- | Embed a 'HyperView' into another 'View'--@-#EMBED Example/Docs/Interactive.hs page-@--}-hyper- :: forall id ctx- . (HyperViewHandled id ctx, ViewId id)- => id- -> View id ()- -> View ctx ()-hyper = hyperUnsafe---hyperUnsafe :: (ViewId id) => id -> View id () -> View ctx ()-hyperUnsafe vid vw = do- el (att "id" (toViewId vid) . flexCol) $- addContext vid vw---class ViewAction a where- toAction :: a -> Text- default toAction :: (Show a) => a -> Text- toAction = (.text) . showQueryParam--- parseAction :: Text -> Maybe a- default parseAction :: (Read a) => Text -> Maybe a- parseAction t =- either (const Nothing) pure $ readQueryParam (ParamValue t)---instance ViewAction () where- toAction _ = ""- parseAction _ = Just ()---class ViewId a where- toViewId :: a -> Text- default toViewId :: (Show a) => a -> Text- toViewId = (.text) . showQueryParam--- parseViewId :: Text -> Maybe a- default parseViewId :: (Read a) => Text -> Maybe a- parseViewId t =- either (const Nothing) pure $ readQueryParam (ParamValue t)---{- | Access the 'viewId' in a 'View' or 'update'--@-#EMBED Example/Page/LazyLoading.hs data LazyData--#EMBED Example/Page/LazyLoading.hs instance (Debug :> es, GenRandom :> es) => HyperView LazyData es where-@--}-class HasViewId m view where- viewId :: m view+module Web.Hyperbole.HyperView+ ( module Web.Hyperbole.HyperView.Types+ , module Web.Hyperbole.HyperView.Input+ , module Web.Hyperbole.HyperView.Event+ , module Web.Hyperbole.HyperView.ViewId+ , module Web.Hyperbole.HyperView.ViewAction+ , module Web.Hyperbole.HyperView.Handled+ ) where +import Web.Hyperbole.HyperView.Event+import Web.Hyperbole.HyperView.Handled+import Web.Hyperbole.HyperView.Input+import Web.Hyperbole.HyperView.Types+import Web.Hyperbole.HyperView.ViewAction+import Web.Hyperbole.HyperView.ViewId -instance HasViewId (View ctx) ctx where- viewId = context-instance HasViewId (Eff (Reader view : es)) view where- viewId = ask
+ src/Web/Hyperbole/HyperView/Event.hs view
@@ -0,0 +1,134 @@+module Web.Hyperbole.HyperView.Event where++import Data.String.Conversions (cs)+import Data.Text (Text)+import Text.Casing (kebab)+import Web.Atomic.Types+import Web.Hyperbole.Data.Encoded+import Web.Hyperbole.HyperView.Handled+import Web.Hyperbole.HyperView.Types+import Web.Hyperbole.HyperView.ViewAction+import Web.Hyperbole.HyperView.ViewId+import Web.Hyperbole.View+import Web.Hyperbole.View.Types (ViewContext)+++type DelayMs = Int+++event :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Name -> Action id -> Attributes a -> Attributes a+event eventName a = att ("data-on" <> eventName) (encodedToText $ toAction a)+++{- | Send the action after N milliseconds. Can be used to implement lazy loading or polling. See [Example.Page.Concurrent](https://docs.hyperbole.live/concurrent)++@+#EMBED Example/Page/Concurrency.hs viewTaskLoad+@+-}+onLoad :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> DelayMs -> Attributes a -> Attributes a+onLoad a delay = do+ event "load" a . att "data-delay" (cs $ show delay)+++onClick :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a+onClick = event "click"+++onDblClick :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a+onDblClick = event "dblclick"+++onMouseEnter :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a+onMouseEnter = event "mouseenter"+++onMouseLeave :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a+onMouseLeave = event "mouseleave"+++{- | Run an action when the user types into an 'input' or 'textarea'.++WARNING: a short delay can result in poor performance. It is not recommended to set the 'value' of the input++> input (onInput OnSearch) 250 id+-}+onInput :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => (Text -> Action id) -> DelayMs -> Attributes a -> Attributes a+onInput a delay = do+ att "data-oninput" (encodedToText $ toActionInput a) . att "data-delay" (cs $ show delay)+++-- WARNING: no way to do this generically right now, because toActionInput is specialized to Text+-- the change event DOES assume that the target has a string value+-- but, that doesn't let us implement dropdown+onChange :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => (value -> Action id) -> Attributes a -> Attributes a+onChange a = do+ att "data-onchange" (encodedToText $ toActionInput a)+++onSubmit :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a+onSubmit = event "submit"+++onKeyDown :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Key -> Action id -> Attributes a -> Attributes a+onKeyDown key act = do+ att ("data-on-keydown-" <> keyDataAttribute key) (encodedToText $ toAction act)+++onKeyUp :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Key -> Action id -> Attributes a -> Attributes a+onKeyUp key act = do+ att ("data-on-keyup-" <> keyDataAttribute key) (encodedToText $ toAction act)+++keyDataAttribute :: Key -> Text+keyDataAttribute = cs . kebab . showKey+ where+ showKey (OtherKey t) = cs t+ showKey k = show k+++-- https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values+data Key+ = ArrowDown+ | ArrowUp+ | ArrowLeft+ | ArrowRight+ | Enter+ | Space+ | Escape+ | Alt+ | CapsLock+ | Control+ | Fn+ | Meta+ | Shift+ | OtherKey Text+ deriving (Show, Read)+++-- | Serialize a constructor that expects a single input, like `data MyAction = GoSearch Text`+toActionInput :: (ViewAction a) => (val -> a) -> Encoded+toActionInput act =+ -- laziness should let us drop the last item?+ -- maybe... I bet it evaluates it strictly+ let Encoded con vals = toAction (act undefined)+ in if null vals+ then Encoded con vals+ else Encoded con (init vals)+++-- | Internal+dataTarget :: (ViewId id, ViewContext a ~ id, Attributable a) => id -> Attributes a -> Attributes a+dataTarget = att "data-target" . encodedToText . toViewId+++{- | Allow inputs to trigger actions for a different view++@+#EMBED Example/Page/Advanced.hs targetView+@+-}+target :: forall id ctx. (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx ()+target newId view = do+ addContext newId $ do+ view @ dataTarget newId
+ src/Web/Hyperbole/HyperView/Forms.hs view
@@ -0,0 +1,644 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE UndecidableInstances #-}++module Web.Hyperbole.HyperView.Forms+ ( FromForm (..)+ , FromFormF (..)+ , GenFields (..)+ , fieldNames+ , FieldName (..)+ , FormFields (..)+ , Field+ , InputType (..)+ , Input (..)+ , field+ , label+ , input+ , checkbox+ , Radio (..)+ , radioGroup+ , radio+ , select+ , form+ , textarea+ , submit+ , formData+ , FormOptions (..)+ , Validated (..)+ , isInvalid+ , invalidText+ , validate+ , Identity++ -- * Re-exports+ , FE.FromFormKey+ , Generic+ , GFieldsGen (..)+ , GenField (..)+ , Form (..)+ )+where++import Data.Bifunctor (first)+import Data.Functor.Identity (Identity (..))+import Data.Kind (Type)+import Data.Maybe (fromMaybe)+import Data.String (IsString (..))+import Data.String.Conversions (cs)+import Data.Text (Text, pack)+import Effectful+import GHC.Generics+import Text.Casing (kebab)+import Web.Atomic.Types hiding (Selector)+import Web.FormUrlEncoded (Form (..), FormOptions (..))+import Web.FormUrlEncoded qualified as FE+import Web.Hyperbole.Data.Param+import Web.Hyperbole.Effect.Hyperbole+import Web.Hyperbole.Effect.Request+import Web.Hyperbole.Effect.Response (parseError)+import Web.Hyperbole.HyperView.Event (onSubmit)+import Web.Hyperbole.HyperView.Input (Option (..), checked)+import Web.Hyperbole.HyperView.Types+import Web.Hyperbole.HyperView.ViewAction+import Web.Hyperbole.View+++------------------------------------------------------------------------------+-- FORM PARSING+------------------------------------------------------------------------------++{- | Simple types that be decoded from form data++@+#EMBED Example/Page/FormSimple.hs data ContactForm+@+-}+class FromForm (form :: Type) where+ fromForm :: FE.Form -> Either String form+ default fromForm :: (Generic form, GFormParse (Rep form)) => FE.Form -> Either String form+ fromForm f = to <$> gFormParse f+++{- | A Higher-Kinded type that can be parsed from a 'Web.FormUrlEncoded.Form'++@+#EMBED Example/Page/FormValidation.hs data UserForm+@+-}+class FromFormF (f :: (Type -> Type) -> Type) where+ fromFormF :: FE.Form -> Either String (f Identity)+ default fromFormF :: (Generic (f Identity), GFormParse (Rep (f Identity))) => FE.Form -> Either String (f Identity)+ fromFormF f = to <$> gFormParse f+++-- Any FromFormF can be parsed using fromForm @(form Identity)+-- we can't make it an instance because it is an orphan instance+instance (FromFormF form) => FromForm (form Identity) where+ fromForm = fromFormF+++-- | Parse a full type from a submitted form body+formData :: forall form es. (FromForm form, Hyperbole :> es) => Eff es form+formData = do+ f <- formBody+ let ef = fromForm @form f :: Either String form+ either parseError pure ef+++------------------------------------------------------------------------------+-- GEN FIELDS: Generate a type from selector names+------------------------------------------------------------------------------++{- | Generate a Higher Kinded record with all selectors filled with default values. See 'GenField'++@+#EMBED Example/Page/FormValidation.hs data UserForm+@++@+#EMBED Example/Page/Contacts.hs newContactForm+@+-}+class GenFields f (form :: (Type -> Type) -> Type) where+ genFields :: form f+ default genFields :: (Generic (form f), GFieldsGen (Rep (form f))) => form f+ genFields = to gFieldsGen+++{- | Generate FieldNames for a form++#EXAMPLE /forms++> #EMBED Example/Page/Todos/Todo.hs data TodoForm+>+> #EMBED Example/Page/Todos/Todo.hs todoForm+-}+fieldNames :: forall form. (GenFields FieldName form) => form FieldName+fieldNames = genFields+++-- Given a selector, generate the type+class GenField a where+ genField :: String -> a+++instance GenField (FieldName a) where+ genField s = FieldName $ pack s+++instance GenField (Validated a) where+ genField = const NotInvalid+++instance GenField (Maybe a) where+ genField _ = Nothing+++------------------------------------------------------------------------------+-- FORM VIEWS+------------------------------------------------------------------------------++-- | Context that allows form fields+data FormFields id = FormFields id+++{- | Type-safe \<form\>. Calls (Action id) on submit++@+#EMBED Example/Page/FormSimple.hs formView+@+-}+form :: (ViewAction (Action id)) => Action id -> View (FormFields id) () -> View id ()+form a cnt = do+ vid <- context+ tag "form" @ onSubmit a $ do+ addContext (FormFields vid) cnt+++-- | Button that submits the 'form'+submit :: View (FormFields id) () -> View (FormFields id) ()+submit = tag "button" @ att "type" "submit"+++-- | Form FieldName. This is embeded as the name attribute, and refers to the key need to parse the form when submitted. See 'fieldNames'+newtype FieldName a = FieldName Text+ deriving newtype (Show, IsString)+++-- | Display a 'FormField'. See 'form' and 'Form'+field+ :: forall (id :: Type) (a :: Type)+ . FieldName a+ -> View (Input id a) ()+ -> View (FormFields id) ()+field fn =+ addContext (Input fn)+++-- | Choose one for 'input's to give the browser autocomplete hints+data InputType+ = -- TODO: there are many more of these: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete+ NewPassword+ | CurrentPassword+ | Username+ | Email+ | Number+ | TextInput+ | Name+ | OneTimeCode+ | Organization+ | StreetAddress+ | Country+ | CountryName+ | PostalCode+ | Search+ deriving (Show)+++data Input (id :: Type) (a :: Type) = Input+ { inputName :: FieldName a+ }+++{- | label for a 'field'+label :: Text -> View (Input id a) ()+-}+label :: View c () -> View c ()+label = tag "label"+++-- | input for a 'field'+input :: InputType -> View (Input id a) ()+input ft = do+ Input (FieldName nm) <- context+ tag "input" @ att "type" (inpType ft) . name nm . att "autocomplete" (auto ft) $ none+ where+ inpType NewPassword = "password"+ inpType CurrentPassword = "password"+ inpType Number = "number"+ inpType Email = "email"+ inpType Search = "search"+ inpType _ = "text"++ auto :: InputType -> Text+ auto = pack . kebab . show+++checkbox :: Bool -> View (Input id a) ()+checkbox isChecked = do+ Input (FieldName nm) <- context+ tag "input" @ att "type" "checkbox" . name nm $ none @ checked isChecked+++-- NOTE: Radio is a special type of selection different from list type or+-- select. select or list input can be thought of one wrapper and multiple+-- options whereas radio is multiple wrappers with options. The context required+-- for radio is more than that required for select.+data Radio (id :: Type) (a :: Type) (b :: Type) = Selection+ { inputCtx :: Input id a+ , defaultOption :: b+ }+++radioGroup :: b -> View (Radio id a b) () -> View (Input id a) ()+radioGroup defOpt = modifyContext $ \inp -> Selection inp defOpt+++radio :: (Eq b, ToParam b) => b -> View (Radio id a b) ()+radio val = do+ Selection (Input (FieldName nm)) defOpt <- context+ tag "input"+ @ att "type" "radio"+ . name nm+ . value (toParam val).value+ . checked (defOpt == val)+ $ none+++select :: (Eq opt) => opt -> View (Option opt id) () -> View (Input id a) ()+select defOpt options = do+ Input (FieldName nm) <- context+ tag "select" @ name nm $ addContext (Option defOpt) options+++-- | textarea for a 'field'+textarea :: Maybe Text -> View (Input id a) ()+textarea mDefaultText = do+ Input (FieldName nm) <- context+ tag "textarea" @ name nm $ text $ fromMaybe "" mDefaultText+++------------------------------------------------------------------------------+-- VALIDATION+------------------------------------------------------------------------------++{- | Validation results for a 'Form'. See 'validate'++@+#EMBED Example/Page/FormValidation.hs data UserForm++#EMBED Example/Page/FormValidation.hs validateForm++#EMBED Example/Page/FormValidation.hs validateAge+@+-}+data Validated a = Invalid Text | NotInvalid | Valid+ deriving (Show)+++instance Semigroup (Validated a) where+ Invalid t <> _ = Invalid t+ _ <> Invalid t = Invalid t+ Valid <> _ = Valid+ _ <> Valid = Valid+ a <> _ = a+++instance Monoid (Validated a) where+ mempty = NotInvalid+++-- instance (FromParam a, ValidateField a) => FromParam (Validated a) where+-- parseParam inp = do+-- a <- parseParam @a inp+-- pure $ validateField a++isInvalid :: Validated a -> Bool+isInvalid (Invalid _) = True+isInvalid _ = False+++-- class ValidateField a where+-- validateField :: a -> Validated a+--++-- class ValidationState (v :: Type -> Type) where+-- convert :: v a -> v b+-- isInvalid :: v a -> Bool+--+--+-- instance ValidationState Validated where+-- convert :: Validated a -> Validated b+-- convert (Invalid t) = Invalid t+-- convert NotInvalid = NotInvalid+-- convert Valid = Valid+--+--++{- Only shows if 'Validated' is 'Invalid'. See 'formFieldsWith'formform+@+@+-}+invalidText :: forall a id. Validated a -> View (Input id a) ()+invalidText v = do+ case v of+ Invalid t -> text t+ _ -> none+++{- | specify a check for a 'Validation'++@+#EMBED Example/Page/FormValidation.hs validateAge+@+-}+validate :: Bool -> Text -> Validated a+validate True t = Invalid t -- Validation [(inputName @a, Invalid t)]+validate False _ = NotInvalid -- Validation [(inputName @a, NotInvalid)]+++{- | Field allows a Higher Kinded 'Form' to reuse the same selectors for form parsing, generating html forms, and validation++> Field Identity Text ~ Text+> Field Maybe Text ~ Maybe Text+-}+type family Field (context :: Type -> Type) a+++-- type instance Field (FormField f) a = FormField f a+type instance Field Identity a = a+type instance Field FieldName a = FieldName a+type instance Field Validated a = Validated a+type instance Field Maybe a = Maybe a+type instance Field (Either String) a = Either String a+++------------------------------------------------------------------------------+-- GENERIC FORM PARSE+------------------------------------------------------------------------------++class GFormParse f where+ gFormParse :: FE.Form -> Either String (f p)+++instance (GFormParse f, GFormParse g) => GFormParse (f :*: g) where+ gFormParse f = do+ a <- gFormParse f+ b <- gFormParse f+ pure $ a :*: b+++instance (GFormParse f) => GFormParse (M1 D d f) where+ gFormParse f = M1 <$> gFormParse f+++instance (GFormParse f) => GFormParse (M1 C c f) where+ gFormParse f = M1 <$> gFormParse f+++-- TODO: need a bool instance?+-- TODO: need a Maybe a instance?+instance (Selector s, FromParam a) => GFormParse (M1 S s (K1 R a)) where+ -- these CANNOT be json encoded, they are encoded by the browser+ gFormParse f = do+ let sel = selName (undefined :: M1 S s (K1 R (f a)) p)+ mt :: Maybe Text <- first cs $ FE.lookupMaybe (cs sel) f+ a <- first (\err -> sel <> ": " <> err) $ decodeFormValue mt+ pure $ M1 . K1 $ a+++-- instance {-# OVERLAPPING #-} (Selector s, FromParam a) => GFormParse (M1 S s (K1 R (Maybe a))) where+-- gFormParse f = do+-- let sel = selName (undefined :: M1 S s (K1 R (f a)) p)+-- mt :: Maybe Text <- first cs $ FE.lookupMaybe (cs sel) f+-- ma :: Maybe a <- maybe (pure Nothing) (parseParam . decodeParam) mt+-- pure $ M1 . K1 $ ma++------------------------------------------------------------------------------+-- GENERIC GENERATE FIELDS+------------------------------------------------------------------------------++class GFieldsGen f where+ gFieldsGen :: f p+++instance GFieldsGen U1 where+ gFieldsGen = U1+++instance (GFieldsGen f, GFieldsGen g) => GFieldsGen (f :*: g) where+ gFieldsGen = gFieldsGen :*: gFieldsGen+++-- instance (Selector s, GenField g a, Field f a ~ g a) => GFieldsGen (M1 S s (K1 R (g a))) where+-- gFieldsGen =+-- let sel = selName (undefined :: M1 S s (K1 R (f a)) p)+-- in M1 . K1 $ genField @g @a sel++instance (Selector s, GenField a) => GFieldsGen (M1 S s (K1 R a)) where+ gFieldsGen =+ let sel = selName (undefined :: M1 S s (K1 R (f a)) p)+ in M1 . K1 $ genField @a sel+++instance (GFieldsGen f) => GFieldsGen (M1 D d f) where+ gFieldsGen = M1 gFieldsGen+++instance (GFieldsGen f) => GFieldsGen (M1 C c f) where+ gFieldsGen = M1 gFieldsGen++------------------------------------------------------------------------------+-- GMerge - combine two records with the same structure+------------------------------------------------------------------------------++-- class GMerge ra rb rc where+-- gMerge :: ra p -> rb p -> rc p+--+--+-- instance (GMerge ra0 rb0 rc0, GMerge ra1 rb1 rc1) => GMerge (ra0 :*: ra1) (rb0 :*: rb1) (rc0 :*: rc1) where+-- gMerge (a0 :*: a1) (b0 :*: b1) = gMerge a0 b0 :*: gMerge a1 b1+--+--+-- instance (GMerge ra rb rc) => GMerge (M1 D d ra) (M1 D d rb) (M1 D d rc) where+-- gMerge (M1 fa) (M1 fb) = M1 $ gMerge fa fb+--+--+-- instance (GMerge ra rb rc) => GMerge (M1 C d ra) (M1 C d rb) (M1 C d rc) where+-- gMerge (M1 fa) (M1 fb) = M1 $ gMerge fa fb+--+--+-- instance (Selector s, MergeField a b c) => GMerge (M1 S s (K1 R a)) (M1 S s (K1 R b)) (M1 S s (K1 R c)) where+-- gMerge (M1 (K1 a)) (M1 (K1 b)) = M1 . K1 $ mergeField a b+--+--+-- class MergeField a b c where+-- mergeField :: a -> b -> c++-- instance MergeField (FieldName a) (Validated a) (FormField Validated a) where+-- mergeField = FormField++------------------------------------------------------------------------------+-- GConvert - combine two records with the same structure+------------------------------------------------------------------------------++-- class ConvertFields a where+-- convertFields :: (FromSelector f g) => a f -> a g+-- default convertFields :: (Generic (a f), Generic (a g), GConvert (Rep (a f)) (Rep (a g))) => a f -> a g+-- convertFields x = to $ gConvert (from x)+--+-- class GConvert ra rc where+-- gConvert :: ra p -> rc p+--+--+-- instance (GConvert ra0 rc0, GConvert ra1 rc1) => GConvert (ra0 :*: ra1) (rc0 :*: rc1) where+-- gConvert (a0 :*: a1) = gConvert a0 :*: gConvert a1+--+--+-- instance (GConvert ra rc) => GConvert (M1 D d ra) (M1 D d rc) where+-- gConvert (M1 fa) = M1 $ gConvert fa+--+--+-- instance (GConvert ra rc) => GConvert (M1 C d ra) (M1 C d rc) where+-- gConvert (M1 fa) = M1 $ gConvert fa+--+--+-- instance (Selector s, GenFieldFrom f g a, Field g a ~ g a) => GConvert (M1 S s (K1 R (f a))) (M1 S s (K1 R (g a))) where+-- gConvert (M1 (K1 inp)) =+-- let sel = selName (undefined :: M1 S s (K1 R (f a)) p)+-- in M1 . K1 $ genFieldFrom @f @g sel inp+--+--+-- class GenFieldFrom inp f a where+-- genFieldFrom :: String -> inp a -> Field f a+--+--+-- -- instance GenFieldFrom Validated (FormField Validated) a where+-- -- genFieldFrom s = FormField (FieldName $ pack s)+--+-- instance GenFieldFrom val (FormField val) a where+-- genFieldFrom s = FormField (FieldName $ pack s)++------------------------------------------------------------------------------++-- class GCollect ra v where+-- gCollect :: ra p -> [v ()]+--+--+-- instance GCollect U1 v where+-- gCollect _ = []+--+--+-- instance (GCollect f v, GCollect g v) => GCollect (f :*: g) v where+-- gCollect (a :*: b) = gCollect a <> gCollect b+--+--+-- instance (Selector s, ValidationState v) => GCollect (M1 S s (K1 R (v a))) v where+-- gCollect (M1 (K1 val)) = [convert val]+--+--+-- instance (GCollect f v) => GCollect (M1 D d f) v where+-- gCollect (M1 f) = gCollect f+--+--+-- instance (GCollect f v) => GCollect (M1 C c f) v where+-- gCollect (M1 f) = gCollect f++------------------------------------------------------------------------------++-- newtype User = User Text+-- deriving newtype (FromParam)+--+--+-- data TestForm f = TestForm+-- { name :: Field f Text+-- , age :: Field f Int+-- , user :: Field f User+-- }+-- deriving (Generic, FromFormF, GenFields Maybe, GenFields Validated)++-- test :: (Hyperbole :> es) => Eff es (TestForm Identity)+-- test = do+-- tf <- formData+-- pure tf++-- formView :: (ViewAction (Action id)) => View id ()+-- formView = do+-- -- generate a ContactForm' FieldName+-- let f = fieldNames @ContactForm+-- form undefined (gap 10 . pad 10) $ do+-- -- f.name :: FieldName Text+-- -- f.name = FieldName "name"+-- field f.name id $ do+-- label "Contact Name"+-- input Username (placeholder "contact name")+--+-- -- f.age :: FieldName Int+-- -- f.age = FieldName "age"+-- field f.age id $ do+-- label "Age"+-- input Number (placeholder "age" . value "0")+--+-- submit id "Submit"+--+--+-- formView' :: (ViewAction (Action id)) => ContactForm Validated -> View id ()+-- formView' contact = do+-- -- generate a ContactForm' FieldName+-- let f = formFields @ContactForm contact+-- form undefined (gap 10 . pad 10) $ do+-- -- f.name :: FieldName Text+-- -- f.name = FieldName "name"+-- field f.name id $ do+-- label "Contact Name"+-- input Username (placeholder "contact name")+--+-- -- f.age :: FieldName Int+-- -- f.age = FieldName "age"+-- field f.age id $ do+-- label "Age"+-- input Number (placeholder "age" . value "0")+--+-- field f.age id $ do+-- label "Username"+-- input Username (placeholder "username")+--+-- case f.age.value of+-- Invalid t -> el_ (text t)+-- Valid -> el_ "Username is available"+-- _ -> none+--+-- submit id "Submit"+-- where+-- valStyle (Invalid _) = id+-- valStyle Valid = id+-- valStyle _ = id+--+--+-- data ContactForm' = ContactForm'+-- { name :: Text+-- , age :: Int+-- }+-- deriving (Generic)+-- instance FormParse ContactForm'+--+--+-- formView'' :: (ViewAction (Action id)) => View id ()+-- formView'' = do+-- form undefined (gap 10 . pad 10) $ do+-- -- f.name :: FieldName Text+-- -- f.name = FieldName "name"+-- field (FieldName "name") id $ do+-- label "Contact Name"+-- input Username (placeholder "contact name")
+ src/Web/Hyperbole/HyperView/Handled.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE UndecidableInstances #-}++module Web.Hyperbole.HyperView.Handled where++import Data.Kind (Constraint, Type)+import GHC.TypeLits hiding (Mod)+import Web.Atomic.Types+import Web.Hyperbole.Data.Encoded as Encoded+import Web.Hyperbole.HyperView.Types+import Web.Hyperbole.HyperView.ViewId+import Web.Hyperbole.TypeList+import Web.Hyperbole.View (View, addContext, tag)+++{- | Embed a 'HyperView' into a page or another 'View'++@+#EMBED Example/Docs/Interactive.hs page+@+-}+hyper+ :: forall id ctx+ . (HyperViewHandled id ctx, ViewId id)+ => id+ -> View id ()+ -> View ctx ()+hyper = hyperUnsafe+++hyperUnsafe :: (ViewId id) => id -> View id () -> View ctx ()+hyperUnsafe vid vw = do+ tag "div" @ att "id" (encodedToText $ toViewId vid) $+ addContext vid vw+++type family ValidDescendents x :: [Type] where+ ValidDescendents x = x : NextDescendents '[] '[x]+++type family NextDescendents (ex :: [Type]) (xs :: [Type]) where+ NextDescendents _ '[] = '[]+ NextDescendents ex (x ': xs) =+ RemoveAll (x : ex) (Require x)+ <++> NextDescendents ((x : ex) <++> Require x) (RemoveAll (x : ex) (Require x))+ <++> NextDescendents (x : ex) (RemoveAll (x : ex) xs)+++type NotHandled id ctx (views :: [Type]) =+ TypeError+ ( 'Text "HyperView "+ :<>: 'ShowType id+ :<>: 'Text " not found in (Require "+ :<>: 'ShowType ctx+ :<>: 'Text ")"+ :$$: 'Text " "+ :<>: 'ShowType views+ :$$: 'Text "Try adding it to the HyperView instance:"+ :$$: 'Text " instance HyperView "+ :<>: 'ShowType ctx+ :<>: 'Text " where"+ :$$: 'Text " type Action "+ :<>: 'ShowType ctx+ :<>: 'Text " = "+ :<>: ShowType (Action id)+ :<>: 'Text ""+ :$$: 'Text " type Require "+ :<>: 'ShowType ctx+ :<>: 'Text " = ["+ :<>: ShowType id+ :<>: 'Text ", ...]"+ )+++type NotDesc id ctx x cs =+ TypeError+ ( 'Text ""+ :<>: 'ShowType x+ :<>: 'Text ", a child of HyperView "+ :<>: 'ShowType id+ :<>: 'Text ", not handled by context "+ :<>: 'ShowType ctx+ :$$: ('Text " Require = " ':<>: 'ShowType cs)+ -- ':$$: 'ShowType x+ -- ':$$: 'ShowType cs+ )+++type NotInPage x total =+ TypeError+ ( 'Text ""+ :<>: 'ShowType x+ :<>: 'Text " not included in: "+ :$$: 'Text " Page es "+ :<>: ShowType total+ :$$: 'Text "try expanding the page views to:"+ :$$: 'Text " Page es "+ :<>: ShowType (x : total)+ -- :$$: 'Text " " :<>: 'ShowType ctx :<>: 'Text " = " :<>: ShowType (Action id) :<>: 'Text ""+ -- :$$: 'Text " page :: (Hyperbole :> es) => Page es '[" :<>: 'ShowType ctx :<>: 'Text " = [" :<>: ShowType id :<>: 'Text ", ...]"+ )+++type HyperViewHandled id ctx =+ ( -- the id must be found in the children of the context+ ElemOr id (ctx : Require ctx) (NotHandled id ctx (Require ctx))+ , -- Make sure the descendents of id are in the context for the root page+ CheckDescendents id ctx+ )+++-- TODO: Report which view requires the missing one+type family CheckDescendents id ctx :: Constraint where+ CheckDescendents id (Root total) =+ ( AllInPage (ValidDescendents id) total+ )+ CheckDescendents id ctx = ()+++type family AllInPage ids total :: Constraint where+ AllInPage '[] _ = ()+ AllInPage (x ': xs) total =+ (ElemOr x total (NotInPage x total), AllInPage xs total)
+ src/Web/Hyperbole/HyperView/Input.hs view
@@ -0,0 +1,100 @@+module Web.Hyperbole.HyperView.Input where++import Data.String.Conversions (cs)+import Data.Text (Text)+import Web.Atomic.Types+import Web.Hyperbole.Data.Param (ParamValue (..), ToParam (..))+import Web.Hyperbole.HyperView.Event (DelayMs, onChange, onClick, onInput)+import Web.Hyperbole.HyperView.Types (HyperView (..))+import Web.Hyperbole.HyperView.ViewAction (ViewAction (..))+import Web.Hyperbole.Route (Route (..), routeUri)+import Web.Hyperbole.View+++{- | \<button\> HTML tag which sends the action when pressed++@+#EMBED Example/Page/Simple.hs messageView+@+-}+button :: (ViewAction (Action id)) => Action id -> View id () -> View id ()+button action cnt = do+ tag "button" cnt @ onClick action+++-- tag "button" @ att "whatber" "asdf" $ cnt++-- {- | \<input type="checkbox"\> which toggles automatically+--+-- > toggle True SetIsSelected id+-- -}+-- toggle :: (ViewAction (Action id)) => Bool -> (Bool -> Action id) -> Mod id -> View id ()+-- toggle isSelected clickAction f = do+-- tag "input" (att "type" "checkbox" . checked isSelected . onClick (clickAction (not isSelected)) . f) none++{- | Type-safe dropdown. Sends (opt -> Action id) when selected. The default will be selected.++#EXAMPLE /data/filter++@+#EMBED Example/Page/DataLists/Filter.hs familyDropdown+@+-}+dropdown+ :: (ViewAction (Action id))+ => (opt -> Action id)+ -> opt -- default option+ -> View (Option opt id) ()+ -> View id ()+dropdown act defOpt options = do+ tag "select" @ onChange act $ do+ addContext (Option defOpt) options+++-- | An option for a 'dropdown' or 'select'+option+ :: (ViewAction (Action id), Eq opt, ToParam opt)+ => opt+ -> Text+ -> View (Option opt id) ()+option opt cnt = do+ os <- context+ tag "option" @ att "value" (toParam opt).value @ selected (os.defaultOption == opt) $ text cnt+++-- | sets selected = true if the 'dropdown' predicate returns True+selected :: (Attributable h) => Bool -> Attributes h -> Attributes h+selected b = if b then att "selected" "true" else id+++-- | The view context for an 'option'+data Option opt id = Option+ { defaultOption :: opt+ }+++{- | A live search field. Set a DelayMs to avoid hitting the server on every keystroke++@+#EMBED Example/Page/Errors.hs viewSearchUsers+@+-}+search :: (ViewAction (Action id)) => (Text -> Action id) -> DelayMs -> View id ()+search go delay = do+ tag "input" none @ onInput go delay+++-- | Set checkbox = checked via the client (VDOM doesn't work)+checked :: (Attributable a) => Bool -> Attributes a -> Attributes a+checked c =+ att "data-checked" (cs $ show c)+ . if c then att "checked" "" else id+++{- | A hyperlink to another route++>>> route (User 100) id "View User"+<a href="/user/100">View User</a>+-}+route :: (Route a) => a -> View c () -> View c ()+route r = link (routeUri r)
+ src/Web/Hyperbole/HyperView/Types.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE UndecidableInstances #-}++module Web.Hyperbole.HyperView.Types where++import Data.Kind (Type)+import Effectful+import Effectful.Reader.Dynamic+import GHC.Generics+import Web.Hyperbole.Effect.Hyperbole (Hyperbole)+import Web.Hyperbole.HyperView.ViewAction+import Web.Hyperbole.HyperView.ViewId+import Web.Hyperbole.View (View, none)+++{- | HyperViews are interactive subsections of a 'Page'++Create an instance with a unique view id type and a sum type describing the actions the HyperView supports. The View Id can contain context (a database id, for example)++@+#EMBED Example/Docs/Interactive.hs data Message++#EMBED Example/Docs/Interactive.hs instance HyperView Message es where+@+-}+class (ViewId id, ViewAction (Action id)) => HyperView id es where+ -- | Outline all actions that are permitted in this HyperView+ --+ -- > data Action Message = SetMessage Text | ClearMessage+ -- > deriving (Generic, ViewAction)+ data Action id+++ -- | Include any child hyperviews here. The compiler will make sure that the page knows how to handle them+ --+ -- > type Require = '[ChildView]+ type Require id :: [Type]+++ type Require id = '[]+++ -- | Specify how the view should be updated for each Action+ --+ -- > update (SetMessage msg) = pure $ messageView msg+ -- > update ClearMessage = pure $ messageView ""+ update :: (Hyperbole :> es) => Action id -> Eff (Reader id : es) (View id ())+++-- | The top-level view returned by a 'Page'. It carries a type-level list of every 'HyperView' used in our 'Page' so the compiler can check our work and wire everything together.+data Root (views :: [Type]) = Root+ deriving (Generic, ViewId)+++instance HyperView (Root views) es where+ data Action (Root views) = RootNone+ deriving (Generic, ViewAction)+ type Require (Root views) = views+ update _ = pure none
+ src/Web/Hyperbole/HyperView/ViewAction.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE DefaultSignatures #-}++module Web.Hyperbole.HyperView.ViewAction where++import Data.Text (Text)+import GHC.Generics+import Web.Hyperbole.Data.Encoded as Encoded+++{- | Define every action possible for a given 'HyperView'++@+#EMBED Example/Page/Simple.hs instance HyperView Message+@+-}+class ViewAction a where+ toAction :: a -> Encoded+ default toAction :: (Generic a, GToEncoded (Rep a)) => a -> Encoded+ toAction = genericToEncoded+++ parseAction :: Encoded -> Either String a+ default parseAction :: (Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a+ parseAction = genericParseEncoded+++instance ViewAction () where+ toAction _ = mempty+ parseAction _ = pure ()+++encodeAction :: (ViewAction act) => act -> Text+encodeAction = encodedToText . toAction+++decodeAction :: (ViewAction act) => Text -> Maybe act+decodeAction t = do+ case parseAction =<< encodedParseText t of+ Left _ -> Nothing+ Right a -> pure a
+ src/Web/Hyperbole/HyperView/ViewId.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE DefaultSignatures #-}++module Web.Hyperbole.HyperView.ViewId where++import Data.Text (Text)+import Effectful+import Effectful.Reader.Dynamic+import GHC.Generics+import Web.Hyperbole.Data.Encoded as Encoded+import Web.Hyperbole.View (View, context)+++{- | A unique identifier for a 'HyperView'++@+#EMBED Example/Page/Simple.hs data Message+@+-}+class ViewId a where+ toViewId :: a -> Encoded+ default toViewId :: (Generic a, GToEncoded (Rep a)) => a -> Encoded+ toViewId = genericToEncoded+++ parseViewId :: Encoded -> Either String a+ default parseViewId :: (Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a+ parseViewId = genericParseEncoded+++{- | Access the 'viewId' in a 'View' or 'update'++@+#EMBED Example/Page/Concurrency.hs data LazyData++#EMBED Example/Page/Concurrency.hs instance (Debug :> es, GenRandom :> es) => HyperView LazyData es where+@+-}+class HasViewId m view where+ viewId :: m view+++instance HasViewId (View ctx) ctx where+ viewId = context+instance HasViewId (Eff (Reader view : es)) view where+ viewId = ask+++encodeViewId :: (ViewId id) => id -> Text+encodeViewId = encodedToText . toViewId+++decodeViewId :: (ViewId id) => Text -> Maybe id+decodeViewId t = do+ case parseViewId =<< encodedParseText t of+ Left _ -> Nothing+ Right a -> pure a
src/Web/Hyperbole/Page.hs view
@@ -2,20 +2,21 @@ import Data.Kind (Type) import Effectful-import Web.Hyperbole.Effect.Handler (RunHandlers, runLoad)+import Effectful.Reader.Dynamic import Web.Hyperbole.Effect.Hyperbole-import Web.Hyperbole.Effect.Server (Response)-import Web.Hyperbole.HyperView (Root)-import Web.View (View)+import Web.Hyperbole.HyperView (Root (..))+import Web.Hyperbole.Server.Handler (RunHandlers, runLoad)+import Web.Hyperbole.Types.Response (Response)+import Web.Hyperbole.View (View) -{- | Conceptually, an application is dividied up into multiple [Pages](#g:pages). Each page module should have a function that returns a 'Page'. The 'Page' itself is a 'View' with a type-level list of 'HyperView's used on the page.+{- | An application is divided into multiple [Pages](#g:pages). Each page module should have a 'Page' function, which returns a root 'View' @ #EMBED Example/Docs/MultiView.hs page @ -}-type Page (views :: [Type]) = View (Root views) ()+type Page es (views :: [Type]) = Eff (Reader (Root views) : es) (View (Root views) ()) {- | Run a 'Page' and return a 'Response'@@ -28,6 +29,14 @@ -} runPage :: (Hyperbole :> es, RunHandlers views es)- => Eff es (Page views)+ => Page es views -> Eff es Response-runPage = runLoad+runPage eff = runLoad $ runReader Root eff+++subPage+ :: (Hyperbole :> es)+ => Eff (Reader (Root inner) : es) a+ -> Eff es a+subPage pg = do+ runReader Root pg
src/Web/Hyperbole/Route.hs view
@@ -1,13 +1,16 @@ {-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE OverloadedLists #-} module Web.Hyperbole.Route ( Route (..)- , findRoute- , pathUrl- , routeUrl+ , routeUri , GenRoute (..)+ , genMatchRoute+ , genRoutePath , genRouteRead- , Url+ , matchRouteRead+ , routePathShow+ , module Web.Hyperbole.Data.URI ) where import Control.Applicative ((<|>))@@ -16,7 +19,7 @@ import Data.Text qualified as T import GHC.Generics import Text.Read (readMaybe)-import Web.View.Types.Url (Segment, Url, pathUrl)+import Web.Hyperbole.Data.URI import Prelude hiding (dropWhile) @@ -28,42 +31,35 @@ #EMBED Example/Docs/App.hs instance Route @ ->>> routeUrl Main+>>> routeUri Main / ->>> routeUrl (User 9)+>>> routeUri (User 9) /user/9 -} class Route a where- -- | The route to use if attempting to match on empty segments+ -- | The route to use if attempting to match an empty path baseRoute :: Maybe a default baseRoute :: (Generic a, GenRoute (Rep a)) => Maybe a baseRoute = Nothing - -- | Try to match segments to a route- matchRoute :: [Segment] -> Maybe a- default matchRoute :: (Generic a, GenRoute (Rep a)) => [Segment] -> Maybe a+ -- | Try to match a path to a route+ matchRoute :: Path -> Maybe a+ default matchRoute :: (Generic a, GenRoute (Rep a)) => Path -> Maybe a -- this will match a trailing slash, but not if it is missing- matchRoute segs =- case (segs, baseRoute) of- ([""], Just b) -> pure b+ matchRoute p =+ case (p, baseRoute) of ([], Just b) -> pure b- (_, _) -> genMatchRoute segs+ (_, _) -> genMatchRoute p.segments - -- | Map a route to segments- routePath :: a -> [Segment]- default routePath :: (Generic a, Eq a, GenRoute (Rep a)) => a -> [Segment]+ -- | Map a route to a path+ routePath :: a -> Path+ default routePath :: (Generic a, Eq a, GenRoute (Rep a)) => a -> Path routePath p | Just p == baseRoute = []- | otherwise = genRoutePath p----- | Try to match a route, use 'defRoute' if it's empty-findRoute :: (Route a) => [Segment] -> Maybe a-findRoute [] = baseRoute-findRoute ps = matchRoute ps+ | otherwise = Path (genRoutePath p) genMatchRoute :: (Generic a, GenRoute (Rep a)) => [Segment] -> Maybe a@@ -74,13 +70,13 @@ genRoutePath = genPaths . from -{- | Convert a 'Route' to a 'Url'+{- | Convert a 'Route' to a 'URI' ->>> routeUrl (User 100)+>>> routeUri (User 100) /user/100 -}-routeUrl :: (Route a) => a -> Url-routeUrl = pathUrl . routePath+routeUri :: (Route a) => a -> URI+routeUri = pathUri . routePath -- | Automatically derive 'Route'@@ -150,10 +146,8 @@ instance (Route sub) => GenRoute (K1 R sub) where- genRoute ts = K1 <$> matchRoute ts--- genPaths (K1 sub) = routePath sub+ genRoute ts = K1 <$> matchRoute (Path ts)+ genPaths (K1 sub) = (routePath sub).segments genRouteRead :: (Read x) => [Text] -> Maybe (K1 R x a)@@ -196,10 +190,10 @@ baseRoute = Nothing -matchRouteRead :: (Read a) => [Segment] -> Maybe a+matchRouteRead :: (Read a) => Path -> Maybe a matchRouteRead [t] = readMaybe (unpack t) matchRouteRead _ = Nothing -routePathShow :: (Show a) => a -> [Segment]+routePathShow :: (Show a) => a -> Path routePathShow a = [pack (show a)]
+ src/Web/Hyperbole/Server/Handler.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE UndecidableInstances #-}++module Web.Hyperbole.Server.Handler where++import Data.Kind (Type)+import Effectful+import Effectful.Dispatch.Dynamic+import Effectful.Reader.Dynamic+import Web.Hyperbole.Data.Encoded+import Web.Hyperbole.Effect.Hyperbole+import Web.Hyperbole.Effect.Response (hyperView, respondError)+import Web.Hyperbole.HyperView+import Web.Hyperbole.Types.Event+import Web.Hyperbole.Types.Request+import Web.Hyperbole.Types.Response+import Web.Hyperbole.View+++class RunHandlers (views :: [Type]) es where+ runHandlers :: (Hyperbole :> es) => Event TargetViewId Encoded -> Eff es (Maybe Response)+++instance RunHandlers '[] es where+ runHandlers _ = pure Nothing+++instance (HyperView view es, RunHandlers views es) => RunHandlers (view : views) es where+ runHandlers rawEvent = do+ mr <- runHandler @view rawEvent (update @view)+ case mr of+ Nothing -> runHandlers @views rawEvent+ Just r -> pure (Just r)+++runHandler+ :: forall id es+ . (HyperView id es, Hyperbole :> es)+ => Event TargetViewId Encoded+ -> (Action id -> Eff (Reader id : es) (View id ()))+ -> Eff es (Maybe Response)+runHandler rawEvent run = do+ -- Get an event matching our type. If it doesn't match, skip to the next handler+ mev <- decodeEvent @id rawEvent :: Eff es (Maybe (Event id (Action id)))+ case mev of+ Just evt -> do+ vw <- runReader evt.viewId $ run evt.action+ res <- hyperView evt.viewId vw+ pure $ Just res+ _ -> do+ pure Nothing+++runLoad+ :: forall views es+ . (Hyperbole :> es, RunHandlers views es)+ => Eff es (View (Root views) ())+ -> Eff es Response+runLoad page = do+ ev <- (.event) <$> send GetRequest+ case ev of+ Just rawEvent -> do+ res <- runHandlers @views rawEvent+ case res of+ -- if we found an event, it should have been handled by one of the views+ Nothing -> respondError $ ErrNotHandled rawEvent+ Just r -> pure r+ Nothing -> do+ loadPageResponse page+++loadPageResponse :: Eff es (View (Root total) ()) -> Eff es Response+loadPageResponse run = do+ vw <- run+ let vid = TargetViewId (encodedToText $ toViewId Root)+ let res = Response vid $ addContext Root vw+ pure res+++-- despite not needing any effects, this must be in Eff es to get `es` on the RHS+decodeEvent :: forall id es. (HyperView id es) => Event TargetViewId Encoded -> Eff es (Maybe (Event id (Action id)))+decodeEvent (Event (TargetViewId ti) eact) =+ pure $ do+ vid <- decodeViewId ti+ act <- either (const Nothing) Just $ parseAction eact+ pure $ Event vid act
+ src/Web/Hyperbole/Server/Message.hs view
@@ -0,0 +1,229 @@+{-# LANGUAGE LambdaCase #-}++module Web.Hyperbole.Server.Message where++import Control.Exception (Exception)+import Data.Aeson qualified as Aeson+import Data.Attoparsec.Text (Parser, char, endOfLine, isEndOfLine, parseOnly, sepBy, string, takeText, takeTill, takeWhile1)+import Data.ByteString qualified as BS+import Data.ByteString.Lazy qualified as BL+import Data.List qualified as L+import Data.String.Conversions (cs)+import Data.Text (Text)+import Data.Text qualified as T+import Web.Hyperbole.Data.Cookie (Cookie, Cookies)+import Web.Hyperbole.Data.Cookie qualified as Cookie+import Web.Hyperbole.Data.Encoded+import Web.Hyperbole.Data.QueryData (QueryData)+import Web.Hyperbole.Data.QueryData qualified as QueryData+import Web.Hyperbole.Data.URI (Path, URI, uriToText)+import Web.Hyperbole.Effect.Hyperbole (Remote (..))+import Web.Hyperbole.Types.Client (Client (..))+import Web.Hyperbole.Types.Event+import Web.Hyperbole.Types.Request+++{-+ |UPDATE|+ viewId: wahoo+ action: hello+ requestId: ipgeim++ body+ body+ body+-}++data Message = Message+ { messageType :: Text+ , event :: Event TargetViewId Encoded+ , requestId :: RequestId+ , metadata :: Metadata+ , body :: MessageBody+ }+ deriving (Show)+++newtype MessageBody = MessageBody {value :: BL.ByteString}+ deriving newtype (Show)+++data MessageError+ = InvalidMessage String Text+ | InvalidCookie BS.ByteString String+ | MissingMeta String+ deriving (Show, Exception)+++-- Read Messages -------------------------------------++mimeType :: Text+mimeType = "application/hyperbole.message"+++parseActionMessage :: Text -> Either String Message+parseActionMessage = parseOnly parser+ where+ parser :: Parser Message+ parser = do+ mt <- messageType+ ev <- event+ rq <- requestId+ ms <- meta `sepBy` endOfLine+ bd <- body+ pure $ Message mt ev rq (mconcat ms) bd++ messageType :: Parser Text+ messageType = do+ _ <- char '|'+ t <- takeWhile1 (/= '|')+ _ <- char '|'+ endOfLine+ pure t++ body :: Parser MessageBody+ body = do+ MessageBody . cs . T.strip <$> takeText++ event :: Parser (Event TargetViewId Encoded)+ event = do+ vid <- targetViewId+ endOfLine+ act <- encodedAction+ endOfLine+ pure $ Event vid act+ where+ targetViewId :: Parser TargetViewId+ targetViewId = do+ _ <- string "ViewId: "+ TargetViewId <$> takeLine++ encodedAction :: Parser Encoded+ encodedAction = do+ _ <- string "Action: "+ inp <- takeLine+ case encodedParseText inp of+ Left e -> fail $ "Parse Encoded ViewAction failed: " <> cs e <> " from " <> cs inp+ Right a -> pure a++ requestId :: Parser RequestId+ requestId = do+ _ <- string "RequestId: "+ r <- RequestId <$> takeLine+ endOfLine+ pure r++ meta :: Parser Metadata+ meta = do+ key <- metaKey+ value <- takeLine+ pure $ metadata (cs key) value++ metaKey :: Parser MetaKey+ metaKey = do+ key <- takeWhile1 (/= ':')+ _ <- string ": "+ pure key++ takeLine :: Parser Text+ takeLine = do+ takeTill isEndOfLine+++-- Render ---------------------------------------------++renderMetadata :: Metadata -> Text+renderMetadata (Metadata m) = T.intercalate "\n" $ fmap (uncurry metaLine) m+++metaLine :: MetaKey -> Text -> Text+metaLine name value = name <> ": " <> cs value+++-- Metadata --------------------------------------------++type MetaKey = Text+++newtype Metadata = Metadata [(Text, Text)]+ deriving newtype (Semigroup, Monoid)+ deriving (Show)+++metadata :: MetaKey -> Text -> Metadata+metadata key value = Metadata [(key, value)]+++lookupMetadata :: MetaKey -> Metadata -> Maybe Text+lookupMetadata key (Metadata kvs) = L.lookup key kvs+++requestMetadata :: Request -> Metadata+requestMetadata req =+ maybe mempty eventMetadata req.event <> metaRequestId req.requestId+ where+ metaRequestId :: RequestId -> Metadata+ metaRequestId (RequestId "") = mempty+ metaRequestId (RequestId reqId) =+ metadata "RequestId" (cs reqId)++ eventMetadata :: Event TargetViewId Encoded -> Metadata+ eventMetadata event =+ Metadata+ [ ("ViewId", event.viewId.text)+ , ("Action", encodedToText event.action)+ ]+++responseMetadata :: Path -> Client -> [Remote] -> Metadata+responseMetadata reqPath client remotes =+ clientMetadata reqPath client <> metaRemotes remotes+++clientMetadata :: Path -> Client -> Metadata+clientMetadata reqPath client =+ metaSession client.session <> metaQuery client.query <> metaPageTitle client.pageTitle+ where+ metaPageTitle :: Maybe Text -> Metadata+ metaPageTitle = \case+ Nothing -> mempty+ Just pt -> metadata "PageTitle" pt++ metaQuery :: Maybe QueryData -> Metadata+ metaQuery Nothing = mempty+ metaQuery (Just q) =+ Metadata [("Query", cs $ QueryData.render q)]++ metaSession :: Cookies -> Metadata+ metaSession cookies = mconcat $ fmap metaCookie $ Cookie.toList cookies+ where+ metaCookie :: Cookie -> Metadata+ metaCookie cookie =+ Metadata [("Cookie", cs (Cookie.render reqPath cookie))]+++metaRemotes :: [Remote] -> Metadata+metaRemotes rs = mconcat $ fmap meta rs+ where+ meta = \case+ RemoteAction (TargetViewId vid) act ->+ metadata "Trigger" $ vid <> "|" <> encodedToText act+ RemoteEvent ev dat ->+ metadata "Event" $ T.intercalate "|" [ev, cs $ Aeson.encode dat]+++metaError :: Text -> Metadata+metaError = metadata "Error"+++metaRedirect :: URI -> Metadata+metaRedirect u = metadata "Redirect" (uriToText u)+++data ContentType+ = ContentHtml+ | ContentText+++newtype RenderedHtml = RenderedHtml BL.ByteString+ deriving newtype (Semigroup, Monoid)
+ src/Web/Hyperbole/Server/Options.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE LambdaCase #-}++module Web.Hyperbole.Server.Options where++import Data.ByteString.Lazy qualified as BL+import Data.String.Conversions (cs)+import Data.Text (Text)+import Data.Text qualified as T+import Web.Hyperbole.Data.Encoded (Encoded, encodedToText)+import Web.Hyperbole.Types.Event+import Web.Hyperbole.Types.Response+import Web.Hyperbole.View+import Web.Atomic.CSS+++data ServerOptions = ServerOptions+ { toDocument :: BL.ByteString -> BL.ByteString+ , serverError :: ResponseError -> ServerError+ }+++defaultErrorMessage :: ResponseError -> Text+defaultErrorMessage = \case+ -- mask server errors+ ErrCustom e -> e.message+ NotFound -> "Not Found"+ ErrInternal -> "Internal Server Error"+ ErrServer m -> m+ e -> cs $ drop 3 $ show e+++defaultErrorBody :: Text -> View Body ()+defaultErrorBody msg =+ el ~ bg (HexColor "#F00") . color (HexColor "#FFF") $ do+ text msg+++defaultError :: ResponseError -> ServerError+defaultError = \case+ ErrCustom e -> e+ ErrNotHandled e -> errNotHandled e+ err -> + let msg = defaultErrorMessage err+ in ServerError msg (defaultErrorBody msg)+ where+ errNotHandled :: Event TargetViewId Encoded -> ServerError+ errNotHandled ev =+ ServerError "Action Not Handled" $ do+ el $ do+ text "No Handler for Event viewId: "+ text ev.viewId.text+ text " action: "+ text $ encodedToText ev.action+ el $ do+ text "Remember to add a `hyper` handler in your page function"+ pre $+ T.intercalate+ "\n"+ [ "page :: (Hyperbole :> es) => Page es Response"+ , "page = do"+ , " handle contentsHandler"+ , " load $ do"+ , " pure $ hyper Contents contentsView"+ , "</pre>"+ ]
+ src/Web/Hyperbole/Server/Socket.hs view
@@ -0,0 +1,139 @@+module Web.Hyperbole.Server.Socket where++import Control.Monad (void)+import Data.Bifunctor (first)+import Data.List qualified as L+import Data.Maybe (fromMaybe)+import Data.String.Conversions (cs)+import Data.Text (Text)+import Effectful+import Effectful.Concurrent.Async+import Effectful.Exception+import Network.HTTP.Types as HTTP (parseQuery)+import Network.Wai qualified as Wai+import Network.WebSockets (Connection)+import Network.WebSockets qualified as WS+import Web.Cookie qualified+import Web.Hyperbole.Data.Cookie qualified as Cookie+import Web.Hyperbole.Data.URI (URI, path)+import Web.Hyperbole.Effect.Hyperbole+import Web.Hyperbole.Server.Message+import Web.Hyperbole.Server.Options+import Web.Hyperbole.Types.Request+import Web.Hyperbole.Types.Response+import Web.Hyperbole.View (View, addContext, renderLazyByteString)+++data SocketRequest = SocketRequest+ { request :: Maybe Request+ }+++handleRequestSocket+ :: (IOE :> es, Concurrent :> es)+ => ServerOptions+ -> Wai.Request+ -> Connection+ -> Eff (Hyperbole : es) Response+ -> Eff es ()+handleRequestSocket opts wreq conn actions = do+ flip catch onMessageError $ do+ msg <- receiveMessage+ req <- parseMessageRequest msg++ void $ async $ do+ res <- trySync $ runHyperbole req actions+ case res of+ -- TODO: catch socket errors separately from SomeException?+ Left (ex :: SomeException) -> do+ -- It's not safe to send any exception over the wire+ -- log it to the console and send the error to the client+ liftIO $ print ex+ res2 <- trySync $ sendError conn (requestMetadata req) (opts.serverError ErrInternal)+ case res2 of+ Left e -> liftIO $ putStrLn $ "Socket Error while sending previous error to client: " <> show e+ Right _ -> pure ()+ Right (resp, clnt, rmts) -> do+ let meta = requestMetadata req <> responseMetadata req.path clnt rmts+ case resp of+ (Response _ vw) -> do+ sendUpdateView conn meta vw+ -- (Err (ErrServer m)) -> sendError conn meta (opts.serverError m)+ (Err err) -> sendError conn meta (opts.serverError err)+ (Redirect url) -> sendRedirect conn meta url+ where+ onMessageError :: (IOE :> es) => MessageError -> Eff es a+ onMessageError e = do+ liftIO $ do+ putStrLn "Socket Message Error"+ throwIO e++ receiveMessage :: (IOE :> es) => Eff es Message+ receiveMessage = do+ t <- receiveText conn+ case parseActionMessage t of+ Left e -> throwIO $ InvalidMessage e t+ Right msg -> pure msg++ receiveText :: (IOE :> es) => Connection -> Eff es Text+ receiveText _ = do+ -- c <- ask @Connection+ liftIO $ WS.receiveData conn++ parseMessageRequest :: (IOE :> es) => Message -> Eff es Request+ parseMessageRequest msg =+ case messageRequest msg of+ Left e -> throwIO e+ Right a -> pure a++ messageRequest :: Message -> Either MessageError Request+ messageRequest msg = do+ let pth = path $ cs $ Wai.rawPathInfo wreq+ host = Host $ fromMaybe "" $ L.lookup "Host" headers+ headers = Wai.requestHeaders wreq+ method = "POST"++ body = msg.body.value++ query <- HTTP.parseQuery . cs <$> requireMeta "Query" msg.metadata+ cookie <- cs <$> requireMeta "Cookie" msg.metadata++ cookies <- first (InvalidCookie cookie) <$> Cookie.parse $ Web.Cookie.parseCookies cookie++ pure $+ Request+ { path = pth+ , event = Just msg.event+ , host+ , query+ , body+ , method+ , cookies+ , requestId = msg.requestId+ }+ where+ requireMeta :: MetaKey -> Metadata -> Either MessageError Text+ requireMeta key m =+ maybe (Left $ MissingMeta (cs key)) pure $ lookupMetadata key m+++sendUpdateView :: (IOE :> es) => Connection -> Metadata -> View Body () -> Eff es ()+sendUpdateView conn meta vw = do+ sendMessage conn meta (RenderedHtml $ renderLazyByteString $ addContext Body vw)+++sendRedirect :: (IOE :> es) => Connection -> Metadata -> URI -> Eff es ()+sendRedirect conn meta u = do+ sendMessage conn (metaRedirect u <> meta) mempty+++sendError :: (IOE :> es) => Connection -> Metadata -> ServerError -> Eff es ()+sendError conn meta (ServerError err body) = do+ sendMessage conn (metadata "Error" err <> meta) (RenderedHtml $ renderLazyByteString $ addContext Body body)+++-- low level message. Use sendResponse+sendMessage :: (MonadIO m) => Connection -> Metadata -> RenderedHtml -> m ()+sendMessage conn meta' (RenderedHtml html) = do+ let out = "|UPDATE|\n" <> cs (renderMetadata meta') <> "\n\n" <> html+ liftIO $ WS.sendTextData conn out
+ src/Web/Hyperbole/Server/Wai.hs view
@@ -0,0 +1,172 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE QuasiQuotes #-}++module Web.Hyperbole.Server.Wai where++import Data.Bifunctor (first)+import Data.ByteString qualified as BS+import Data.ByteString.Lazy qualified as BL+import Data.List qualified as L+import Data.Maybe (fromMaybe)+import Data.String.Conversions (cs)+import Data.String.Interpolate (i)+import Effectful+import Effectful.Exception (throwIO)+import Network.HTTP.Types (Header, HeaderName, status200, status400, status401, status404, status500)+import Network.Wai qualified as Wai+import Network.Wai.Internal (ResponseReceived (..))+import Web.Atomic (att, (@))+import Web.Cookie qualified+import Web.Hyperbole.Data.Cookie (Cookie, Cookies)+import Web.Hyperbole.Data.Cookie qualified as Cookie+import Web.Hyperbole.Data.Encoded (Encoded, encodedParseText)+import Web.Hyperbole.Data.URI (path, uriToText)+import Web.Hyperbole.Effect.Hyperbole+import Web.Hyperbole.Server.Message+import Web.Hyperbole.Server.Options+import Web.Hyperbole.Types.Client+import Web.Hyperbole.Types.Event+import Web.Hyperbole.Types.Request+import Web.Hyperbole.Types.Response+import Web.Hyperbole.View (View, addContext, renderLazyByteString, script', type_)+++handleRequestWai+ :: (IOE :> es)+ => ServerOptions+ -> Wai.Request+ -> (Wai.Response -> IO ResponseReceived)+ -> Eff (Hyperbole : es) Response+ -> Eff es Wai.ResponseReceived+handleRequestWai options req respond actions = do+ -- NOTE: Remember, this is called for both updates AND for page loads+ body <- liftIO $ Wai.consumeRequestBodyLazy req+ rq <- either throwIO pure $ do+ fromWaiRequest req body+ (res, client, rmts) <- runHyperbole rq actions+ liftIO $ sendResponse options rq client res rmts respond+++sendResponse :: ServerOptions -> Request -> Client -> Response -> [Remote] -> (Wai.Response -> IO ResponseReceived) -> IO Wai.ResponseReceived+sendResponse options req client res remotes respond = do+ let metas = requestMetadata req <> responseMetadata req.path client remotes+ respond $ response metas res+ where+ response :: Metadata -> Response -> Wai.Response+ response metas = \case+ (Err err) ->+ respondError (errStatus err) [] $ options.serverError err+ (Response _ vw) -> do+ respondHtml status200 (clientHeaders client) $ renderViewResponse metas vw+ (Redirect u) -> do+ let url = uriToText u+ -- We have to use a 200 javascript redirect because javascript+ -- will redirect the fetch(), while we want to redirect the whole page+ -- see index.ts sendAction()+ let hs = ("Location", cs url) : clientHeaders client+ respondHtml status200 hs $ renderViewResponse (metaRedirect u <> metas) $ do+ script'+ -- static script is safe to execute+ [i|+ let metaInput = document.getElementById("hyp.metadata").innerText;+ let meta = Hyperbole.parseMetadata(metaInput)+ if (meta.redirect) {+ window.location = meta.redirect+ }+ else {+ console.error("Invalid Redirect", meta.rediect)+ }+ |]++ errStatus = \case+ NotFound -> status404+ ErrParse _ -> status400+ ErrQuery _ -> status400+ ErrSession _ _ -> status400+ ErrAuth _ -> status401+ _ -> status500++ -- convert to document if full page request+ addDocument :: BL.ByteString -> BL.ByteString+ addDocument body =+ case req.event of+ Nothing -> options.toDocument body+ _ -> body++ renderViewResponse :: Metadata -> View Body () -> BL.ByteString+ renderViewResponse metas vw =+ addDocument $ renderLazyByteString (addContext metas $ scriptMeta metas) <> "\n\n" <> renderLazyByteString (addContext Body vw)++ respondError s hs serr = respondHtml s hs $ renderViewResponse (metaError serr.message) serr.body+ respondHtml s hs = Wai.responseLBS s (contentType ContentHtml : hs)+ -- respondText s hs = Wai.responseLBS s (contentType ContentText : hs)++ -- via HTTP, we want to manually set some headers rather than just rely on the client+ clientHeaders :: Client -> [Header]+ clientHeaders = setCookies+ where+ setCookies clnt =+ fmap setCookie $ Cookie.toList clnt.session++ setCookie :: Cookie -> (HeaderName, BS.ByteString)+ setCookie cookie =+ ("Set-Cookie", Cookie.render req.path cookie)+++scriptMeta :: Metadata -> View Metadata ()+scriptMeta m =+ script' @ type_ "application/hyp.metadata" . att "id" "hyp.metadata" $+ cs $+ "\n" <> renderMetadata m <> "\n"+++messageFromBody :: BL.ByteString -> Either MessageError Message+messageFromBody inp = do+ first (\e -> InvalidMessage e (cs inp)) $ parseActionMessage (cs inp)+++fromWaiRequest :: Wai.Request -> BL.ByteString -> Either MessageError Request+fromWaiRequest wr body = do+ let pth = path $ cs $ Wai.rawPathInfo wr+ query = Wai.queryString wr+ headers = Wai.requestHeaders wr+ cookie = fromMaybe "" $ L.lookup "Cookie" headers+ host = Host $ fromMaybe "" $ L.lookup "Host" headers+ requestId = RequestId $ cs $ fromMaybe "" $ L.lookup "Hyp-RequestId" headers+ method = Wai.requestMethod wr+ event = lookupEvent headers++ cookies <- fromCookieHeader cookie++ pure $+ Request+ { body = body+ , path = pth+ , event+ , query+ , method+ , cookies+ , host+ , requestId+ }+ where+ lookupEvent :: [Header] -> Maybe (Event TargetViewId Encoded)+ lookupEvent headers = do+ viewId <- TargetViewId . cs <$> L.lookup "Hyp-ViewId" headers+ actText <- cs <$> L.lookup "Hyp-Action" headers+ case encodedParseText actText of+ Left _ -> Nothing+ Right a -> pure $ Event viewId a+++-- Client only returns ONE Cookie header, with everything concatenated+fromCookieHeader :: BS.ByteString -> Either MessageError Cookies+fromCookieHeader h =+ case Cookie.parse (Web.Cookie.parseCookies h) of+ Left err -> Left $ InvalidCookie h err+ Right a -> pure a+++contentType :: ContentType -> (HeaderName, BS.ByteString)+contentType ContentHtml = ("Content-Type", "text/html; charset=utf-8")+contentType ContentText = ("Content-Type", "text/plain; charset=utf-8")
+ src/Web/Hyperbole/Types/Client.hs view
@@ -0,0 +1,29 @@+module Web.Hyperbole.Types.Client where++import Data.Text (Text)+import Web.Hyperbole.Data.Cookie (Cookies)+import Web.Hyperbole.Data.QueryData as QueryData+import Web.Hyperbole.Types.Request+++data Client = Client+ { requestId :: RequestId+ , session :: Cookies+ , query :: Maybe QueryData+ , pageTitle :: Maybe Text+ }+++clientSetPageTitle :: Text -> Client -> Client+clientSetPageTitle t Client{session, query, requestId} =+ Client{pageTitle = Just t, session = session, query, requestId}+++clientModCookies :: (Cookies -> Cookies) -> Client -> Client+clientModCookies f Client{session, query, requestId, pageTitle} =+ Client{session = f session, query, requestId, pageTitle}+++clientSetQuery :: QueryData -> Client -> Client+clientSetQuery q Client{session, requestId, pageTitle} =+ Client{query = Just q, session, requestId, pageTitle}
+ src/Web/Hyperbole/Types/Event.hs view
@@ -0,0 +1,25 @@+module Web.Hyperbole.Types.Event where++import Data.Aeson (ToJSON)+import Data.String.Conversions (cs)+import Data.Text (Text)+++-- | Serialized ViewId+newtype TargetViewId = TargetViewId {text :: Text}+ deriving newtype (ToJSON)+++instance Show TargetViewId where+ show (TargetViewId t) = "TargetViewId " <> cs t+++-- | An action, with its corresponding id+data Event id act = Event+ { viewId :: id+ , action :: act+ }+++instance (Show act, Show id) => Show (Event id act) where+ show e = "Event " <> show e.viewId <> " " <> show e.action
+ src/Web/Hyperbole/Types/Request.hs view
@@ -0,0 +1,31 @@+module Web.Hyperbole.Types.Request where++import Data.ByteString qualified as BS+import Data.ByteString.Lazy qualified as BL+import Data.Text (Text)+import Network.HTTP.Types (Method)+import Web.Hyperbole.Data.Cookie (Cookies)+import Web.Hyperbole.Data.Encoded+import Web.Hyperbole.Data.URI (Path, Query)+import Web.Hyperbole.Types.Event (Event (..), TargetViewId)+++newtype Host = Host {text :: BS.ByteString}+ deriving (Show)+++data Request = Request+ { host :: Host+ , path :: Path+ , query :: Query+ , body :: BL.ByteString+ , method :: Method+ , cookies :: Cookies+ , event :: Maybe (Event TargetViewId Encoded)+ , requestId :: RequestId+ }+ deriving (Show)+++newtype RequestId = RequestId Text+ deriving (Show)
+ src/Web/Hyperbole/Types/Response.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE LambdaCase #-}++module Web.Hyperbole.Types.Response where++import Data.String (IsString (..))+import Data.String.Conversions (cs)+import Data.Text (Text)+import Web.Hyperbole.Data.Encoded (Encoded)+import Web.Hyperbole.Data.URI (URI)+import Web.Hyperbole.Types.Event+import Web.Hyperbole.View+++data Body = Body+++-- | A processed response for the client, which might be a 'ResponseError'+data Response+ = Response TargetViewId (View Body ())+ | Redirect URI+ | Err ResponseError+++data ResponseError+ = NotFound+ | ErrParse String+ | ErrQuery String+ | ErrSession Text String+ | ErrServer Text+ | ErrCustom ServerError+ | ErrInternal+ | ErrNotHandled (Event TargetViewId Encoded)+ | ErrAuth Text+instance Show ResponseError where+ show = \case+ NotFound -> "NotFound"+ ErrParse m -> "ErrParse " <> cs m+ ErrQuery m -> "ErrQuery " <> cs m+ ErrSession k m -> "ErrSession " <> cs k <> " " <> cs m+ ErrServer m -> "ErrServer " <> cs m+ ErrCustom err -> "ErrCustom " <> cs err.message+ ErrInternal -> "ErrInternal"+ ErrNotHandled ev -> "ErrNotHandled " <> show ev+ ErrAuth m -> "ErrAuth " <> cs m+instance IsString ResponseError where+ fromString s = ErrServer (cs s)+++-- Serialized server error+data ServerError = ServerError+ { message :: Text+ , body :: View Body ()+ }
src/Web/Hyperbole/View.hs view
@@ -1,14 +1,16 @@ module Web.Hyperbole.View- ( hyper- , module Web.Hyperbole.View.Element- , module Web.Hyperbole.View.Event- , module Web.View+ ( module Web.Hyperbole.View.Types , module Web.Hyperbole.View.Embed+ , module Web.Hyperbole.View.Render+ , module Web.Hyperbole.View.Tag+ , module Web.Hyperbole.View.CSS+ , module Web.Atomic.Attributes ) where -import Web.Hyperbole.HyperView (hyper)-import Web.Hyperbole.View.Element+import Web.Atomic.Attributes+import Web.Hyperbole.View.CSS import Web.Hyperbole.View.Embed-import Web.Hyperbole.View.Event-import Web.View hiding (Query, Segment, button, cssResetEmbed, form, input, label)+import Web.Hyperbole.View.Render+import Web.Hyperbole.View.Tag hiding (form, input, label)+import Web.Hyperbole.View.Types (View (..), addContext, context, modifyContext, none, raw, tag, text)
+ src/Web/Hyperbole/View/CSS.hs view
@@ -0,0 +1,27 @@+module Web.Hyperbole.View.CSS where++import Web.Atomic.CSS+++{- | Apply CSS only when a request is in flight. See [Example.Page.Contact](https://docs.hyperbole.live/contacts/1)++@+#EMBED Example/Page/Contact.hs contactEditView+@+-}+whenLoading :: (Styleable h) => (CSS h -> CSS h) -> CSS h -> CSS h+whenLoading = do+ descendentOf "hyp-loading"+++disabled :: (Styleable h) => CSS h -> CSS h+disabled =+ utility+ "disabled"+ [ "opacity" :. "0.7"+ , "pointer-events" :. "none"+ ]+++loading :: (Styleable h) => CSS h -> CSS h+loading = whenLoading disabled
− src/Web/Hyperbole/View/Element.hs
@@ -1,72 +0,0 @@-module Web.Hyperbole.View.Element where--import Data.Text (Text)-import Web.Hyperbole.HyperView (HyperView (..), ViewAction (..))-import Web.Hyperbole.Route (Route (..), routeUrl)-import Web.Hyperbole.View.Event (DelayMs, onClick, onInput)-import Web.View hiding (Query, Segment, button, cssResetEmbed, form, input, label)---{- | \<button\> HTML tag which sends the action when pressed--> button SomeAction (border 1) "Click Me"--}-button :: (ViewAction (Action id)) => Action id -> Mod id -> View id () -> View id ()-button action f cd = do- tag "button" (onClick action . f) cd---{- | Type-safe dropdown. Sends (opt -> Action id) when selected. The selection predicate (opt -> Bool) controls which option is selected. See [Example.Page.Filter](https://docs.hyperbole.live/filter)--@-#EMBED Example/Page/Filter.hs familyDropdown-@--}-dropdown- :: (ViewAction (Action id))- => (opt -> Action id)- -> (opt -> Bool) -- check if selec- -> Mod id- -> View (Option opt id (Action id)) ()- -> View id ()-dropdown act isSel f options = do- tag "select" (att "data-on-change" "" . f) $ do- addContext (Option act isSel) options----- | An option for a 'dropdown'. First argument is passed to (opt -> Action id) in the 'dropdown', and to the selected predicate-option- :: (ViewAction (Action id), Eq opt)- => opt- -> View (Option opt id (Action id)) ()- -> View (Option opt id (Action id)) ()-option opt cnt = do- os <- context- tag "option" (att "value" (toAction (os.toAction opt)) . selected (os.selected opt)) cnt----- | sets selected = true if the 'dropdown' predicate returns True-selected :: Bool -> Mod id-selected b = if b then att "selected" "true" else id----- | The view context for an 'option'-data Option opt id action = Option- { toAction :: opt -> action- , selected :: opt -> Bool- }----- | A live search field-search :: (ViewAction (Action id)) => (Text -> Action id) -> DelayMs -> Mod id -> View id ()-search go delay f = do- tag "input" (onInput go delay . f) none---{- | A hyperlink to another route-->>> route (User 100) id "View User"-<a href="/user/100">View User</a>--}-route :: (Route a) => a -> Mod c -> View c () -> View c ()-route r = link (routeUrl r)
src/Web/Hyperbole/View/Embed.hs view
@@ -1,16 +1,34 @@ {-# LANGUAGE TemplateHaskell #-} module Web.Hyperbole.View.Embed- ( cssResetEmbed- , cssResetLink+ ( cssEmbed , scriptEmbed+ , scriptEmbedSourceMap+ , scriptLiveReload ) where import Data.ByteString import Data.FileEmbed-import Web.View.Reset+import Web.Atomic.CSS.Reset qualified as Atomic scriptEmbed :: ByteString scriptEmbed = $(embedFile "client/dist/hyperbole.js")+++scriptEmbedSourceMap :: ByteString+scriptEmbedSourceMap = $(embedFile "client/dist/hyperbole.js.map")+++scriptLiveReload :: ByteString+scriptLiveReload = $(embedFile "client/util/live-reload.js")+++cssEmbed :: ByteString+cssEmbed =+ Atomic.cssResetEmbed+ <> "\n"+ <> intercalate+ "\n"+ ["form, label { display: flex; flex-direction: column } "]
− src/Web/Hyperbole/View/Event.hs
@@ -1,126 +0,0 @@-{-# LANGUAGE LambdaCase #-}--module Web.Hyperbole.View.Event where--import Data.String.Conversions (cs)-import Data.Text (Text)-import Text.Casing (kebab)-import Web.Hyperbole.HyperView-import Web.View (Mod, View, addContext, att, parent)-import Web.View.Types (Content (Node), Element (..))-import Web.View.View (viewModContents)---type DelayMs = Int---{- | Send the action after N milliseconds. Can be used to implement lazy loading or polling. See [Example.Page.Concurrent](https://docs.hyperbole.live/concurrent)--@-#EMBED Example/Page/LazyLoading.hs viewTaskLoad-@--}-onLoad :: (ViewAction (Action id)) => Action id -> DelayMs -> Mod id-onLoad a delay = do- att "data-on-load" (toAction a) . att "data-delay" (cs $ show delay)---onClick :: (ViewAction (Action id)) => Action id -> Mod id-onClick a = do- att "data-on-click" (toAction a)---onDblClick :: (ViewAction (Action id)) => Action id -> Mod id-onDblClick a = do- att "data-on-dblclick" (toAction a)---{- | Run an action when the user types into an 'input' or 'textarea'.--WARNING: a short delay can result in poor performance. It is not recommended to set the 'value' of the input--> input (onInput OnSearch) 250 id--}-onInput :: (ViewAction (Action id)) => (Text -> Action id) -> DelayMs -> Mod id-onInput a delay = do- att "data-on-input" (toActionInput a) . att "data-delay" (cs $ show delay)---onSubmit :: (ViewAction (Action id)) => Action id -> Mod id-onSubmit act = do- att "data-on-submit" (toAction act)---onKeyDown :: (ViewAction (Action id)) => Key -> Action id -> Mod id-onKeyDown key act = do- att ("data-on-keydown-" <> keyDataAttribute key) (toAction act)---onKeyUp :: (ViewAction (Action id)) => Key -> Action id -> Mod id-onKeyUp key act = do- att ("data-on-keyup-" <> keyDataAttribute key) (toAction act)---keyDataAttribute :: Key -> Text-keyDataAttribute = cs . kebab . showKey- where- showKey (OtherKey t) = cs t- showKey k = show k----- https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values-data Key- = ArrowDown- | ArrowUp- | ArrowLeft- | ArrowRight- | Enter- | Space- | Escape- | Alt- | CapsLock- | Control- | Fn- | Meta- | Shift- | OtherKey Text- deriving (Show, Read)----- | Serialize a constructor that expects a single 'Text', like `data MyAction = GoSearch Text`-toActionInput :: (ViewAction a) => (Text -> a) -> Text-toActionInput con =- let marker = "%HYP-INP%"- in toAction $ con marker---{- | Apply a Mod only when a request is in flight. See [Example.Page.Contact](https://docs.hyperbole.live/contacts/1)--@-#EMBED Example/Page/Contact.hs contactEditView-@--}-onRequest :: Mod id -> Mod id-onRequest f = do- parent "hyp-loading" f----- | Internal-dataTarget :: (ViewId a) => a -> Mod x-dataTarget = att "data-target" . toViewId----- | Allow inputs to trigger actions for a different view-target :: forall id ctx. (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx ()-target newId view = do- addContext newId $ do- view- viewModContents (fmap addDataTarget)- where- addDataTarget :: Content -> Content- addDataTarget = \case- Node elm ->- Node $- let atts = elm.attributes- in elm{attributes = dataTarget newId atts}- cnt -> cnt
− src/Web/Hyperbole/View/Forms.hs
@@ -1,559 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE FunctionalDependencies #-}--module Web.Hyperbole.View.Forms- ( FormFields (..)- , InputType (..)- , FieldName- , Invalid- , Input (..)- , field- , label- , input- , form- , textarea- , placeholder- , submit- , formData- , Form (..)- , formParseParam- , formLookupParam- , formFields- , formFieldsWith- , Field- , defaultFormOptions- , FormOptions (..)- , Validated (..)- , FormField (..)- , fieldValid- , anyInvalid- , invalidText- , validate- , Identity-- -- * Re-exports- , FromParam- , Generic- , GenFields (..)- , GenField (..)- )-where--import Data.Function ((&))-import Data.Functor.Identity (Identity (..))-import Data.Kind (Type)-import Data.Maybe (fromMaybe)-import Data.Text (Text, pack)-import Debug.Trace-import Effectful-import GHC.Generics-import Text.Casing (kebab)-import Web.FormUrlEncoded (FormOptions (..), defaultFormOptions, parseUnique)-import Web.FormUrlEncoded qualified as FE-import Web.Hyperbole.Data.QueryData (FromParam (..), Param (..), ParamValue (..))-import Web.Hyperbole.Effect.Hyperbole-import Web.Hyperbole.Effect.Request-import Web.Hyperbole.Effect.Response (parseError)-import Web.Hyperbole.HyperView-import Web.Hyperbole.View.Event (onSubmit)-import Web.View hiding (form, input, label)-import Web.View.Style (addClass, cls, prop)----- | The only time we can use Fields is inside a form-data FormFields id = FormFields id---data FormField v a = FormField- { fieldName :: FieldName a- , validated :: v a- }- deriving (Show)----- instance Show (v a) => Show (FormField v) where--- show f = "Form Field"---- instance (ViewId id) => ViewId (FormFields id v fs) where--- parseViewId t = do--- i <- parseViewId t--- pure $ FormFields i lbls mempty--- toViewId (FormFields i _ _) = toViewId i--------- instance (HyperView id, ViewId id) => HyperView (FormFields id v fs) where--- type Action (FormFields id v fs) = Action id---- | Choose one for 'input's to give the browser autocomplete hints-data InputType- = -- TODO: there are many more of these: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete- NewPassword- | CurrentPassword- | Username- | Email- | Number- | TextInput- | Name- | OneTimeCode- | Organization- | StreetAddress- | Country- | CountryName- | PostalCode- | Search- deriving (Show)---{- | Validation results for a 'Form'. See 'validate'--@-#EMBED Example/Page/FormValidation.hs data UserForm-#EMBED Example/Page/FormValidation.hs instance Form UserForm--#EMBED Example/Page/FormValidation.hs validateForm--#EMBED Example/Page/FormValidation.hs validateAge-@--}-data Validated a = Invalid Text | NotInvalid | Valid- deriving (Show)---instance Semigroup (Validated a) where- Invalid t <> _ = Invalid t- _ <> Invalid t = Invalid t- Valid <> _ = Valid- _ <> Valid = Valid- a <> _ = a---instance Monoid (Validated a) where- mempty = NotInvalid---class ValidationState (v :: Type -> Type) where- convert :: v a -> v b- isInvalid :: v a -> Bool---instance ValidationState Validated where- convert :: Validated a -> Validated b- convert (Invalid t) = Invalid t- convert NotInvalid = NotInvalid- convert Valid = Valid--- isInvalid :: Validated a -> Bool- isInvalid (Invalid _) = True- isInvalid _ = False---{- Only shows if 'Validated' is 'Invalid'. See 'formFieldsWith'-@-@--}-invalidText :: forall a id. View (Input id Validated a) ()-invalidText = do- Input _ v <- context- case v of- Invalid t -> text t- _ -> none---{- | specify a check for a 'Validation'--@-#EMBED Example/Page/FormValidation.hs validateAge-@--}-validate :: Bool -> Text -> Validated a-validate True t = Invalid t -- Validation [(inputName @a, Invalid t)]-validate False _ = NotInvalid -- Validation [(inputName @a, NotInvalid)]----- validateWith :: forall a fs v. (FormField a, Elem a fs, ValidationState v) => v a -> Validation' v fs--- validateWith v = Validation [(inputName @a, convert v)]---- eh... not sure how to do this...-anyInvalid :: forall form val. (Form form val, ValidationState val) => form val -> Bool-anyInvalid f = any isInvalid (collectValids f :: [val ()])----- any (isInvalid . snd) vs---- | Returns the 'Validated' for the 'field'. See 'formFieldsWith'-fieldValid :: View (Input id v a) (v a)-fieldValid = do- Input _ v <- context- pure v---data FieldName a = FieldName Text- deriving (Show)---data Invalid a---data Input (id :: Type) (valid :: Type -> Type) (a :: Type) = Input- { inputName :: FieldName a- , valid :: valid a- }----- | Display a 'FormField'. See 'form' and 'Form'-field- :: forall (id :: Type) (v :: Type -> Type) (a :: Type)- . FormField v a- -> (v a -> Mod (FormFields id))- -> View (Input id v a) ()- -> View (FormFields id) ()-field fld md cnt = do- tag "label" (md fld.validated . flexCol) $ do- addContext (Input fld.fieldName fld.validated) cnt----- | label for a 'field'-label :: Text -> View (Input id v a) ()-label = text----- | input for a 'field'-input :: InputType -> Mod (Input id v a) -> View (Input id v a) ()-input ft f = do- Input (FieldName nm) _ <- context- tag "input" (f . name nm . att "type" (inpType ft) . att "autocomplete" (auto ft)) none- where- inpType NewPassword = "password"- inpType CurrentPassword = "password"- inpType Number = "number"- inpType Email = "email"- inpType Search = "search"- inpType _ = "text"-- auto :: InputType -> Text- auto = pack . kebab . show---placeholder :: Text -> Mod id-placeholder = att "placeholder"----- | textarea for a 'field'-textarea :: Mod (Input id v a) -> Maybe Text -> View (Input id v a) ()-textarea f mDefaultText = do- Input (FieldName nm) _ <- context- tag "textarea" (f . name nm) (text $ fromMaybe "" mDefaultText)---{- | Type-safe \<form\>. Calls (Action id) on submit--@-#EMBED Example/Page/FormSimple.hs formView-@--}-form :: (Form form v, ViewAction (Action id)) => Action id -> Mod id -> View (FormFields id) () -> View id ()-form a md cnt = do- vid <- context- tag "form" (onSubmit a . md . flexCol . marginEnd0) $ do- addContext (FormFields vid) cnt- where- -- not sure why chrome is adding margin-block-end: 16 to forms? Add to web-view?- marginEnd0 =- addClass $- cls "mg-end-0"- & prop @PxRem "margin-block-end" 0----- | Button that submits the 'form'. Use 'button' to specify actions other than submit-submit :: Mod (FormFields id) -> View (FormFields id) () -> View (FormFields id) ()-submit f = tag "button" (att "type" "submit" . f)---{- | Field allows a Higher Kinded 'Form' to reuse the same selectors for form parsing, generating html forms, and validation--> Field Identity Text ~ Text-> Field Maybe Text ~ Maybe Text--}-type family Field (context :: Type -> Type) a---type instance Field Identity a = a-type instance Field FieldName a = FieldName a-type instance Field (FormField v) a = FormField v a-type instance Field Validated a = Validated a-type instance Field Maybe a = Maybe a-type instance Field (Either String) a = Either String a---formData :: forall form val es. (Form form val, Hyperbole :> es) => Eff es (form Identity)-formData = do- f <- formBody- traceM $ show f- let ef = formParse @form @val f :: Either Text (form Identity)- case ef of- Left e -> parseError e- Right a -> pure a---{- | A Form is a Higher Kinded record listing each 'Field'. `ContactForm` `Identity` behaves like a normal record, while `ContactForm` `Maybe` would be maybe values for each field--From [Example.Page.FormSimple](https://docs.hyperbole.live/formsimple)--@-#EMBED Example/Page/FormSimple.hs data ContactForm-#EMBED Example/Page/FormSimple.hs instance Form ContactForm-@--}-class Form form (val :: Type -> Type) | form -> val where- formParse :: FE.Form -> Either Text (form Identity)- default formParse :: (Generic (form Identity), GFormParse (Rep (form Identity))) => FE.Form -> Either Text (form Identity)- formParse f = to <$> gFormParse f--- collectValids :: (ValidationState val) => form val -> [val ()]- default collectValids :: (Generic (form val), GCollect (Rep (form val)) val) => form val -> [val ()]- collectValids f = gCollect (from f)--- genForm :: form val- default genForm :: (Generic (form val), GenFields (Rep (form val))) => form val- genForm = to gGenFields--- genFieldsWith :: form val -> form (FormField val)- default genFieldsWith- :: (Generic (form val), Generic (form (FormField val)), GConvert (Rep (form val)) (Rep (form (FormField val))))- => form val- -> form (FormField val)- genFieldsWith fv = to $ gConvert (from fv)---formParseParam :: (FromParam a) => Param -> FE.Form -> Either Text a-formParseParam (Param key) frm = do- t <- FE.parseUnique @Text key frm- parseParam (ParamValue t)---formLookupParam :: (FromParam a) => Param -> FE.Form -> Either Text (Maybe a)-formLookupParam (Param key) frm = do- mt <- FE.parseMaybe @Text key frm- maybe (pure Nothing) (parseParam . ParamValue) mt---{- | Generate FormFields for the given instance of 'Form', with no validation information. See [Example.Page.FormSimple](https://docs.hyperbole.live/formsimple)--> #EMBED Example/Page/FormSimple.hs data ContactForm->-> #EMBED Example/Page/FormSimple.hs formView--}-formFields :: (Form form val) => form (FormField val)-formFields = genFieldsWith genForm---{- | Generate FormFields for the given instance of 'Form' from validation data. See [Example.Page.FormValidation](https://docs.hyperbole.live/formvalidation)--> #EMBED Example/Page/FormValidation.hs data UserForm-> #EMBED Example/Page/FormValidation.hs instance Form UserForm->-> #EMBED Example/Page/FormValidation.hs formView--}-formFieldsWith :: (Form form val) => form val -> form (FormField val)-formFieldsWith = genFieldsWith----- | Automatically derive labels from form field names-class GFormParse f where- gFormParse :: FE.Form -> Either Text (f p)----- instance GForm U1 where--- gForm = U1--instance (GFormParse f, GFormParse g) => GFormParse (f :*: g) where- gFormParse f = do- a <- gFormParse f- b <- gFormParse f- pure $ a :*: b---instance (GFormParse f) => GFormParse (M1 D d f) where- gFormParse f = M1 <$> gFormParse f---instance (GFormParse f) => GFormParse (M1 C c f) where- gFormParse f = M1 <$> gFormParse f---instance (Selector s, FromParam a) => GFormParse (M1 S s (K1 R a)) where- gFormParse f = do- let s = selName (undefined :: M1 S s (K1 R (f a)) p)- t <- parseUnique @Text (pack s) f- M1 . K1 <$> parseParam (ParamValue t)------------------------------------------------------------------------------------ GEN FIELDS :: Create the field! ----------------------------------------------------------------------------------------------------------------------------class GenFields f where- gGenFields :: f p---instance GenFields U1 where- gGenFields = U1---instance (GenFields f, GenFields g) => GenFields (f :*: g) where- gGenFields = gGenFields :*: gGenFields---instance (Selector s, GenField f a, Field f a ~ f a) => GenFields (M1 S s (K1 R (f a))) where- gGenFields =- let sel = selName (undefined :: M1 S s (K1 R (f a)) p)- in M1 . K1 $ genField @f @a sel---instance (GenFields f) => GenFields (M1 D d f) where- gGenFields = M1 gGenFields---instance (GenFields f) => GenFields (M1 C c f) where- gGenFields = M1 gGenFields------------------------------------------------------------------------------------ GenField -- Generate a value from the selector name---------------------------------------------------------------------------------class GenField f a where- genField :: String -> Field f a---instance GenField FieldName a where- genField s = FieldName $ pack s---instance GenField Validated a where- genField = const NotInvalid---instance GenField (FormField Validated) a where- genField s = FormField (FieldName $ pack s) NotInvalid---instance GenField (FormField Maybe) a where- genField s = FormField (FieldName $ pack s) Nothing---instance GenField Maybe a where- genField _ = Nothing------------------------------------------------------------------------------------ GMerge - combine two records with the same structure----------------------------------------------------------------------------------- class ConvertFields a where--- convertFields :: (FromSelector f g) => a f -> a g--- default convertFields :: (Generic (a f), Generic (a g), GConvert (Rep (a f)) (Rep (a g))) => a f -> a g--- convertFields x = to $ gConvert (from x)--class GMerge ra rb rc where- gMerge :: ra p -> rb p -> rc p---instance (GMerge ra0 rb0 rc0, GMerge ra1 rb1 rc1) => GMerge (ra0 :*: ra1) (rb0 :*: rb1) (rc0 :*: rc1) where- gMerge (a0 :*: a1) (b0 :*: b1) = gMerge a0 b0 :*: gMerge a1 b1---instance (GMerge ra rb rc) => GMerge (M1 D d ra) (M1 D d rb) (M1 D d rc) where- gMerge (M1 fa) (M1 fb) = M1 $ gMerge fa fb---instance (GMerge ra rb rc) => GMerge (M1 C d ra) (M1 C d rb) (M1 C d rc) where- gMerge (M1 fa) (M1 fb) = M1 $ gMerge fa fb---instance (Selector s, MergeField a b c) => GMerge (M1 S s (K1 R a)) (M1 S s (K1 R b)) (M1 S s (K1 R c)) where- gMerge (M1 (K1 a)) (M1 (K1 b)) = M1 . K1 $ mergeField a b---class MergeField a b c where- mergeField :: a -> b -> c---instance MergeField (FieldName a) (Validated a) (FormField Validated a) where- mergeField = FormField------------------------------------------------------------------------------------ GConvert - combine two records with the same structure----------------------------------------------------------------------------------- class ConvertFields a where--- convertFields :: (FromSelector f g) => a f -> a g--- default convertFields :: (Generic (a f), Generic (a g), GConvert (Rep (a f)) (Rep (a g))) => a f -> a g--- convertFields x = to $ gConvert (from x)--class GConvert ra rc where- gConvert :: ra p -> rc p---instance (GConvert ra0 rc0, GConvert ra1 rc1) => GConvert (ra0 :*: ra1) (rc0 :*: rc1) where- gConvert (a0 :*: a1) = gConvert a0 :*: gConvert a1---instance (GConvert ra rc) => GConvert (M1 D d ra) (M1 D d rc) where- gConvert (M1 fa) = M1 $ gConvert fa---instance (GConvert ra rc) => GConvert (M1 C d ra) (M1 C d rc) where- gConvert (M1 fa) = M1 $ gConvert fa---instance (Selector s, GenFieldFrom f g a, Field g a ~ g a) => GConvert (M1 S s (K1 R (f a))) (M1 S s (K1 R (g a))) where- gConvert (M1 (K1 inp)) =- let sel = selName (undefined :: M1 S s (K1 R (f a)) p)- in M1 . K1 $ genFieldFrom @f @g sel inp---class GenFieldFrom inp f a where- genFieldFrom :: String -> inp a -> Field f a----- instance GenFieldFrom Validated (FormField Validated) a where--- genFieldFrom s = FormField (FieldName $ pack s)-----instance GenFieldFrom val (FormField val) a where- genFieldFrom s = FormField (FieldName $ pack s)-----------------------------------------------------------------------------------class GCollect ra v where- gCollect :: ra p -> [v ()]---instance GCollect U1 v where- gCollect _ = []---instance (GCollect f v, GCollect g v) => GCollect (f :*: g) v where- gCollect (a :*: b) = gCollect a <> gCollect b---instance (Selector s, ValidationState v) => GCollect (M1 S s (K1 R (v a))) v where- gCollect (M1 (K1 val)) = [convert val]---instance (GCollect f v) => GCollect (M1 D d f) v where- gCollect (M1 f) = gCollect f---instance (GCollect f v) => GCollect (M1 C c f) v where- gCollect (M1 f) = gCollect f--------------------------------------------------------------------------------
+ src/Web/Hyperbole/View/Render.hs view
@@ -0,0 +1,17 @@+module Web.Hyperbole.View.Render+ ( renderText+ , renderLazyByteString+ ) where++import Data.ByteString.Lazy qualified as BL+import Data.Text (Text)+import Web.Atomic.Render qualified as Atomic+import Web.Hyperbole.View.Types (View, runView)+++renderText :: View () () -> Text+renderText = Atomic.renderText . runView ()+++renderLazyByteString :: View () () -> BL.ByteString+renderLazyByteString = Atomic.renderLazyByteString . runView ()
+ src/Web/Hyperbole/View/Tag.hs view
@@ -0,0 +1,255 @@+{-# LANGUAGE AllowAmbiguousTypes #-}++module Web.Hyperbole.View.Tag where++import Control.Monad (forM_)+import Data.ByteString (ByteString)+import Data.String.Conversions (cs)+import Data.Text (Text, pack)+import Data.Text qualified as T+import Effectful+import Effectful.State.Static.Local+import Web.Atomic.CSS+import Web.Atomic.Types+import Web.Hyperbole.Data.URI+import Web.Hyperbole.View.Types+++el :: View c () -> View c ()+el = tag "div"+++row :: View c () -> View c ()+row = tag "div" ~ flexRow+++col :: View c () -> View c ()+col = tag "div" ~ flexCol+++space :: View c ()+space = tag "div" none ~ grow+++pre :: Text -> View c ()+pre t = tag "pre" (text t)+++code :: Text -> View c ()+code t = tag "code" (text t)+++-- | A hyperlink to the given url+link :: URI -> View c () -> View c ()+link u = tag "a" @ att "href" (uriToText u)+++img :: Text -> View c ()+img sc = tag "img" @ src sc $ none+++-- * Inputs+++-- basic forms. See Web.Hyperbole.View.Forms+form :: View c () -> View c ()+form = tag "form"+++input :: View c ()+input = tag "input" @ att "type" "text" $ none+++name :: (Attributable h) => Text -> Attributes h -> Attributes h+name = att "name"+++value :: (Attributable h) => Text -> Attributes h -> Attributes h+value = att "value"+++label :: View c () -> View c ()+label = tag "label"+++placeholder :: (Attributable h) => Text -> Attributes h -> Attributes h+placeholder = att "placeholder"+++autofocus :: (Attributable h) => Attributes h -> Attributes h+autofocus = att "autofocus" ""+++-- * Document Metadata+++-- html :: View c () -> View c ()+-- html = tag "html"++-- head :: View c () -> View c ()+-- head = tag "head"++-- body :: View c () -> View c ()+-- body = tag "body"++meta :: View c ()+meta = tag "meta" none+++title :: Text -> View c ()+title = tag "title" . text+++content :: (Attributable h) => Text -> Attributes h -> Attributes h+content = att "content"+++httpEquiv :: (Attributable h) => Text -> Attributes h -> Attributes h+httpEquiv = att "httpEquiv"+++charset :: (Attributable h) => Text -> Attributes h -> Attributes h+charset = att "charset"+++type_ :: (Attributable h) => Text -> Attributes h -> Attributes h+type_ = att "type"+++src :: (Attributable h) => Text -> Attributes h -> Attributes h+src = att "src"+++script :: Text -> View c ()+script sc = tag "script" none @ src sc+++-- | Embed raw script, escape '</script>'+script' :: ByteString -> View c ()+script' dat = tag' True "script" $ raw $ T.replace "</" "\\u003C/" $ cs dat+++style :: ByteString -> View c ()+style cnt = tag "style" (raw $ cs cnt) @ type_ "text/css"+++stylesheet :: Text -> View c ()+stylesheet href = tag "link" @ att "rel" "stylesheet" . att "href" href $ none+++-- * Navigation+++nav :: View c () -> View c ()+nav = tag "nav"+++-- * Tables+++{- | Create a type safe data table by specifying columns++> data User = User {name :: Text, email :: Text}+>+> usersTable :: [User] -> View c ()+> usersTable us = do+> table us $ do+> tcol (th "Name" ~ hd) $ \u -> td ~ cell $ text u.name+> tcol (th "Email" ~ hd) $ \u -> td ~ cell $ text u.email+> where+> hd = cell . bold+> cell :: (Styleable h) => CSS h -> CSS h+> cell = pad 4 . border 1+-}+table :: [dt] -> TableColumns c dt () -> View c ()+table dts (TableColumns wcs) = do+ let cols = runPureEff . execState [] $ wcs+ tag "table" $ do+ tag "thead" $ do+ tag "tr" $ do+ forM_ cols $ \tc -> do+ let TableHead hd = tc.headCell+ hd+ tag "tbody" $ do+ forM_ dts $ \dt -> do+ tag "tr" $ do+ forM_ cols $ \tc -> do+ tc.dataCell dt+++usersTable :: View c ()+usersTable = do+ table items $ do+ tcol (th "Index" ~ bold) $ \u -> td ~ cell $ text $ pack $ show $ fst u+ tcol (th "Item" ~ bold) $ \u -> td ~ cell $ text $ snd u+ where+ items :: [(Int, Text)]+ items = zip [0 ..] ["one", "two", "three"]+ cell :: (Styleable h) => CSS h -> CSS h+ cell = pad 4 . border 1+++newtype Table c a = Table (View c a)+ deriving newtype (Functor, Applicative, Monad, Styleable)+++tcol :: forall dt c. TableHead c () -> (dt -> View c ()) -> TableColumns c dt ()+tcol hd cell = TableColumns $ do+ modify @[TableColumn c dt] $ \cols -> cols <> [TableColumn hd cell]+++th :: View c () -> TableHead c ()+th cnt = do+ TableHead $ tag "th" cnt+++td :: View c () -> View c ()+td = tag "td"+++instance {-# OVERLAPS #-} Styleable (TableColumns c dt () -> View c ()) where+ modCSS frr parent eff = modCSS frr (parent eff)+++newtype TableHead id a = TableHead (View id a)+ deriving newtype (Functor, Applicative, Monad, Styleable)+++newtype TableColumns c dt a = TableColumns (Eff '[State [TableColumn c dt]] a)+ deriving newtype (Functor, Applicative, Monad)+++data TableColumn c dt = TableColumn+ { headCell :: TableHead c ()+ , dataCell :: dt -> View c ()+ }+++-- * Lists+++{- | List elements do not include any inherent styling but are useful for accessibility. See 'Web.Atomic.CSS.list'.++> ol id $ do+> let nums = list Decimal+> li nums "one"+> li nums "two"+> li nums "three"+-}+ol :: ListItem c () -> View c ()+ol (ListItem cnt) = do+ tag "ol" cnt+++ul :: ListItem c () -> View c ()+ul (ListItem cnt) = do+ tag "ul" cnt+++li :: View c () -> ListItem c ()+li cnt = ListItem $ do+ tag "li" cnt+++newtype ListItem c a = ListItem (View c a)+ deriving newtype (Functor, Applicative, Monad, Styleable)
+ src/Web/Hyperbole/View/Types.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE AllowAmbiguousTypes #-}++module Web.Hyperbole.View.Types where++import Data.Kind (Type)+import Data.String (IsString (..))+import Data.Text (Text, pack)+import Effectful+import Effectful.Reader.Static+import Web.Atomic.Html (Html (..))+import Web.Atomic.Html qualified as Atomic+import Web.Atomic.Types+++-- View ------------------------------------------------------------++{- | 'View's are HTML fragments with a 'context'++@+#EMBED Example/Docs/BasicPage.hs helloWorld+@+-}+newtype View c a = View {html :: Eff '[Reader c] (Html a)}+++instance IsString (View c ()) where+ fromString s = View $ pure $ Atomic.text (pack s)+++runView :: forall c a. c -> View c a -> Html a+runView c (View eff) = do+ runPureEff $ runReader c eff+++instance Functor (View c) where+ fmap f (View eff) = View $ do+ html <- eff+ pure $ fmap f html+instance Applicative (View ctx) where+ pure a = View $ pure $ pure a+ liftA2 :: (a -> b -> c) -> View ctx a -> View ctx b -> View ctx c+ liftA2 abc (View va) (View vb) = View $ do+ ha <- va+ hb <- vb+ pure $ liftA2 abc ha hb+ View va *> View vb = View $ do+ ha <- va+ hb <- vb+ pure $ ha *> hb+instance Monad (View ctx) where+ (>>) = (*>)+ (>>=) :: forall a b. View ctx a -> (a -> View ctx b) -> View ctx b+ -- TEST: appending Empty+ View ea >>= famb = View $ do+ a :: a <- (.value) <$> ea+ let View eb :: View ctx b = famb a+ hb <- eb+ pure $ hb+++-- Context -----------------------------------------++type family ViewContext (v :: Type) where+ ViewContext (View c x) = c+ ViewContext (View c x -> View c x) = c+++-- TEST: appending Empty+context :: forall c. View c c+context = View $ do+ c <- ask @c+ pure $ pure c+++addContext :: ctx -> View ctx () -> View c ()+addContext c (View eff) = View $ do+ pure $ runPureEff $ runReader c eff+++modifyContext+ :: forall ctx0 ctx1. (ctx0 -> ctx1) -> View ctx1 () -> View ctx0 ()+modifyContext f (View eff) = View $ do+ ctx0 <- ask @ctx0+ pure $ runPureEff $ runReader (f ctx0) eff+++-- Html ---------------------------------------------++tag :: Text -> View c () -> View c ()+tag = tag' False+++tag' :: Bool -> Text -> View c () -> View c ()+tag' inline n (View eff) = View $ do+ content <- eff+ pure $ Atomic.tag' inline n content+++text :: Text -> View c ()+text t = View $ pure $ Atomic.text t+++none :: View c ()+none = View $ pure Atomic.none+++raw :: Text -> View c ()+raw t = View $ pure $ Atomic.raw t+++-- Attributes -----------------------------------------++instance Attributable (View c a) where+ modAttributes f (View eff) = View $ do+ h <- eff+ pure $ modAttributes f h+++instance Styleable (View c a) where+ modCSS f (View eff) = View $ do+ h <- eff+ pure $ modCSS f h
+ test/Test/EncodedSpec.hs view
@@ -0,0 +1,237 @@+{-# LANGUAGE OverloadedLists #-}++module Test.EncodedSpec where++import Data.Aeson (FromJSON (..), ToJSON (..), Value (..))+import Data.Text (Text)+import GHC.Generics (Generic)+import Skeletest+import Web.Hyperbole.Data.Encoded+import Web.Hyperbole.Data.Param+++-- TEST: QueryData underscores vs spaces++data One = One+ -- toJSON automatically delegates to the child's ToJSON instance+ -- when it ought to be enought to delegate to the Generic instance!+ deriving (Generic, Eq, ToEncoded, FromEncoded, ToParam, FromParam)+++data Tag = A | B | C | D+ deriving (Generic, Eq, ToEncoded, ToParam, FromParam)+++data Two = Two | Two2 Int+ deriving (Generic, Eq, ToJSON, FromJSON, ToEncoded, FromEncoded)+++-- Custom Param Encoding+instance ToParam Two where+ toParam Two = "Two"+ toParam other = genericToParam other+instance FromParam Two where+ parseParam "Two" = pure Two+ parseParam other = genericParseParam other+++data Sum+ = Sumthing+ | Num Int+ | Str Text+ | COne One+ | CTwo Two+ | List [Text]+ deriving (Generic, Eq, ToEncoded, FromEncoded)+++data Nested+ = Gogo One+ | RecordN Record+ | RecordEx Record Int+ | Tag Tag+ deriving (Generic, ToEncoded, FromEncoded, Eq)+++data Product+ = Product Text Int Bool+ deriving (Generic, Eq, ToEncoded, FromEncoded)+++data Record = Record+ { one :: Int+ , two :: Text+ }+ deriving (Generic, Show, ToJSON, FromJSON, Eq, ToEncoded, FromEncoded, ToParam, FromParam)+++data Product4 = Product4 Text Text Text Text deriving (Generic, Show, Eq, Read, FromEncoded, ToEncoded)+++spec :: Spec+spec = withMarkers ["encoded"] $ do+ describe "genericToEncoded" $ do+ it "should encode single tags" $ do+ genericToEncoded One `shouldBe` Encoded "One" []++ it "should encode multi tags" $ do+ genericToEncoded Two `shouldBe` Encoded "Two" []+ genericToEncoded (Two2 3) `shouldBe` Encoded "Two2" [jsonParam $ Number 3]+ genericToEncoded (Gogo One) `shouldBe` Encoded "Gogo" [toParam One]++ it "should encode sum tags" $ do+ genericToEncoded (CTwo Two) `shouldBe` Encoded "CTwo" [toParam Two]++ it "basic" $ do+ genericToEncoded (Gogo One) `shouldBe` Encoded "Gogo" [toParam One]++ it "product" $ do+ genericToEncoded (Product "one" 2 True) `shouldBe` Encoded "Product" [toParam @Text "one", toParam @Int 2, toParam True]++ it "product4" $ do+ let prod = Product4 "one" "two" "three" "four"+ genericToEncoded prod `shouldBe` Encoded "Product4" (fmap toParam ["one" :: Text, "two", "three", "four"])++ describe "genericParseEncoded" $ do+ it "product4" $ do+ genericParseEncoded (Encoded "Product4" (fmap toParam ["one" :: Text, "two", "three", "four"])) `shouldBe` Right (Product4 "one" "two" "three" "four")++ it "sum" $ do+ genericParseEncoded @Sum (Encoded "Sumthing" []) `shouldBe` Right Sumthing+ genericParseEncoded @Sum (Encoded "Num" [toParam @Int 2]) `shouldBe` Right (Num 2)+ genericParseEncoded @Sum (Encoded "Str" [toParam @Text "OK"]) `shouldBe` Right (Str "OK")++ genericParseEncoded @Sum (Encoded "COne" [toParam One]) `shouldBe` Right (COne One)+ genericParseEncoded @Sum (Encoded "CTwo" [toParam Two]) `shouldBe` Right (CTwo Two)++ describe "toEncoded" $ do+ it "encodes numbers as text" $ do+ -- no, this is right, but when we go to decode, we pick up the json instance...+ toEncoded (Num 1) `shouldBe` Encoded "Num" [jsonParam $ Number 1]++ describe "toText" $ do+ it "should encode single tags" $ do+ encodedToText (Encoded "One" []) `shouldBe` "One"++ describe "parseText" $ do+ it "should decode single tags" $ do+ encodedParseText "One" `shouldBe` Right (Encoded "One" [])++ it "parses numbers" $ do+ encodedParseText "Num 1" `shouldBe` Right (Encoded "Num" [jsonParam $ Number 1])++ describe "encode" $ do+ it "should encode single tags" $ do+ encode One `shouldBe` "One"++ it "encodes strings" $ do+ encode (Str "hello world") `shouldBe` "Str hello_world"+ -- but then how is it going to know the difference between the two?+ encode (Str " ") `shouldBe` "Str _"+ encode (Str "") `shouldBe` "Str |"+ encode (Str "_") `shouldBe` "Str \\_"+ encode (Str "hello_world") `shouldBe` "Str hello\\_world"+ encode (Str "hello+world") `shouldBe` "Str hello+world"++ it "should encode records`" $ do+ -- no field names for ourselves+ encode (Record 1 "two") `shouldBe` "Record 1 two"+ -- but if it is nested it uses the JSON instance, obviously+ let r2 = Record 1 "two"+ encode (RecordN r2) `shouldBe` "RecordN " <> encodeParam (jsonParam r2)++ it "no special case for nested constructors`" $ do+ encode A `shouldBe` "A"+ encode (Tag A) `shouldBe` "Tag A"++ it "should encode sum" $ do+ encode (Num 1) `shouldBe` "Num 1"+ encode (Str "hello world") `shouldBe` "Str hello_world"++ it "should encode prodcuts" $ do+ encode (Product "hello world" 2 True) `shouldBe` "Product hello_world 2 true"++ it "encodes more constructors" $ do+ encode (CTwo (Two2 3)) `shouldBe` "CTwo [\"Two2\",3]"+ encode (CTwo Two) `shouldBe` "CTwo Two" -- uses the custom toparam instance+ encode (COne One) `shouldBe` "COne []"++ describe "decode" $ do+ it "should encode single tags" $ do+ decode "One" `shouldBe` Just One++ it "should decode nested sum" $ do+ decodeEither "Num 1" `shouldBe` Right (Num 1)+ decodeEither "Str str" `shouldBe` Right (Str "str")+ decodeEither "Str hello_world" `shouldBe` Right (Str "hello world")++ it "no special case for nested constructors`" $ do+ decode "Tag A" `shouldBe` Just (Tag A)++ it "decodes strings" $ do+ decode "Str |" `shouldBe` pure (Str "")++ describe "params" $ do+ it "sanitizeText" $ do+ encodeParam "hello world" `shouldBe` "hello_world"+ encodeParam "hello_world" `shouldBe` "hello\\_world"++ it "desanitizeText" $ do+ decodeParam "hello_world" `shouldBe` "hello world"+ decodeParam "hello\\_world" `shouldBe` "hello_world"++ it "edge cases" $ do+ encodeParam "" `shouldBe` "|"+ encodeParam " " `shouldBe` "_"+ encodeParam " " `shouldBe` "__"++ encodeParam "_" `shouldBe` "\\_"+ encodeParam "__" `shouldBe` "\\_\\_"++ decodeParam "|" `shouldBe` ""+ decodeParam "_" `shouldBe` " "+ decodeParam "\\_" `shouldBe` "_"+ decodeParam "\\_\\_" `shouldBe` "__"++ describe "round trip" $ do+ it "records" $ do+ let enc = genericToEncoded (Record 1 "two")+ genericParseEncoded enc `shouldBe` Right (Record 1 "two")++ it "product" $ do+ decode (encode (Product "hello world" 2 False)) `shouldBe` Just (Product "hello world" 2 False)+ decode (encode (Product "bob" (-2) True)) `shouldBe` Just (Product "bob" (-2) True)++ it "nested product with records" $ do+ let r = RecordEx (Record 2 "three") 33+ let t = encode r+ decode t `shouldBe` Just r++ it "special case constructors" $ do+ decode (encode (CTwo Two)) `shouldBe` Just (CTwo Two)+ decode (encode (Tag B)) `shouldBe` Just (Tag B)++ it "big product" $ do+ let p = Product4 "hello world" "two_times" "three" "four"+ decode (encode p) `shouldBe` Just p++ it "empty strings" $ do+ decode (encode $ Str "") `shouldBe` Just (Str "")++ it "special characters" $ do+ let str = "hello+world \"bob_lives\""+ decode (encode $ Str str) `shouldBe` Just (Str str)++ it "encodes lists`" $ do+ let l = List ["hello, world", "", "+,|<[]"]+ print $ encode l+ decode @Sum (encode l) `shouldBe` Just l++ it "strings" $ do+ decode @Sum (encode (Str "")) `shouldBe` pure (Str "")+ decode @Sum (encode (Str " ")) `shouldBe` pure (Str " ")+ decode @Sum (encode (Str "_")) `shouldBe` pure (Str "_")+ decode @Sum (encode (Str "~")) `shouldBe` pure (Str "~")+ decode @Sum (encode (Str "+")) `shouldBe` pure (Str "+")+ decode @Sum (encode (Str "hello world")) `shouldBe` pure (Str "hello world")+ decode @Sum (encode (Str "hello_world")) `shouldBe` pure (Str "hello_world")
test/Test/FormSpec.hs view
@@ -1,28 +1,71 @@+{-# LANGUAGE DerivingVia #-} {-# LANGUAGE OverloadedLists #-} module Test.FormSpec where import Data.Text (Text) import Skeletest-import Web.Hyperbole.View.Forms+import Web.Hyperbole.HyperView.Forms data Example f = Example { message :: Field f Text , age :: Field f Int , whatever :: Field f (Maybe Float)+ , maybeMessage :: Field f (Maybe Text) }- deriving (Generic)-instance Form Example Maybe+ deriving (Generic, FromFormF, GenFields Maybe)+instance Show (Example Identity) where+ show (Example m a w mm) = "Example " <> show m <> " " <> show a <> " " <> show w <> " " <> show mm+instance Eq (Example Identity) where+ Example m a w mm == Example m2 a2 w2 mm2 = m == m2 && a == a2 && w == w2 && mm == mm2 +data Flags = Flags+ { a :: Bool+ , b :: Bool+ }+ deriving (Generic, FromForm, Show, Eq)+++data Todo = Todo+ {msg :: Text}+ deriving (Generic, FromForm, Show, Eq)++ spec :: Spec-spec = do+spec = withMarkers ["param"] $ do describe "forms" $ do it "should parse a form" $ do- case formParse @Example [("message", "hello"), ("age", "23"), ("whatever", "")] of+ case fromForm @(Example Identity) [("message", "hello"), ("age", "23"), ("whatever", "")] of Left e -> fail $ show e Right a -> do a.message `shouldBe` "hello" a.age `shouldBe` 23 a.whatever `shouldBe` Nothing++ it "should parse a form with a number for the text" $ do+ let res = fromForm @(Example Identity) [("message", "30"), ("age", "0"), ("whatever", "2"), ("maybeMessage", "hello")]+ res `shouldBe` Right (Example "30" 0 (Just 2) (Just "hello"))++ it "parses missing Maybes" $ do+ let res = fromForm @(Example Identity) [("message", "30"), ("age", "0")]+ res `shouldBe` Right (Example "30" 0 Nothing Nothing)++ it "parses Maybe Text empty string" $ do+ let res = fromForm @(Example Identity) [("message", "30"), ("age", "0"), ("maybeMessage", "")]+ res `shouldBe` Right (Example "30" 0 Nothing (Just ""))++ it "parses weird" $ do+ fromForm @Flags [("a", "true"), ("b", "off")] `shouldBe` Right (Flags True False)+ fromForm @Flags [("a", "on"), ("b", "false")] `shouldBe` Right (Flags True False)+ fromForm @Flags [("a", "on")] `shouldBe` Right (Flags True False)++ it "parses missing bools as false" $ do+ fromForm @Flags [("a", "true")] `shouldBe` Right (Flags True False)++ it "parses underscores" $ do+ fromForm @Todo [("msg", "test")] `shouldBe` Right (Todo "test")+ fromForm @Todo [("msg", "hello world")] `shouldBe` Right (Todo "hello world")+ fromForm @Todo [("msg", "hello+world")] `shouldBe` Right (Todo "hello+world")+ fromForm @Todo [("msg", "hello_world")] `shouldBe` Right (Todo "hello_world")
+ test/Test/ParamSpec.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE OverloadedLists #-}++module Test.ParamSpec where++import Data.Aeson+import Data.String.Conversions (cs)+import Data.Text (Text)+import GHC.Generics+import Skeletest+import Web.Hyperbole.Data.Param+++spec :: Spec+spec = withMarkers ["param"] $ do+ describe "param" paramSpec+++data Record = Record+ { age :: Int+ , msg :: Text+ }+ deriving (Generic, ToJSON, FromJSON, ToParam, FromParam, Eq)+++data Tag = A | B+ deriving (Generic, ToParam, FromParam, Eq, Show)+++data Tag2 = C | Tag Text+ deriving (Generic, ToParam, FromParam, Eq, Show)+instance ToJSON Tag2 where+ toJSON = genericToJSON jsonOptions+++paramSpec :: Spec+paramSpec = do+ describe "ToParam" $ do+ it "should encode basics" $ do+ toParam @Text "hello" `shouldBe` "hello"+ toParam @Int 23 `shouldBe` ParamValue "23"++ it "should encode Maybe" $ do+ toParam @(Maybe Int) Nothing `shouldBe` ParamValue "~"+ toParam @(Maybe Int) (Just 23) `shouldBe` ParamValue "23"++ it "encodes simple constructors" $ do+ toParam A `shouldBe` ParamValue "A"+ toParam B `shouldBe` ParamValue "B"++ it "encodes complex constructors as json" $ do+ toParam C `shouldBe` jsonParam C+ toParam (Tag "hello world") `shouldBe` jsonParam (Tag "hello world")++ -- it "should encode lists with spaces = plusses" $ do+ -- toParam @[Int] [1, 2, 3] `shouldBe` ParamValue ("1+2+3")+ -- toParam @[Text] ["one", "two"] `shouldBe` ParamValue ("one+two")+ -- toParam @[Text] ["hello world", "friend"] `shouldBe` ParamValue ("hello%20world+friend")++ it "should not escape text" $ do+ toParam @Text "hello world" `shouldBe` ParamValue "hello world"+ toParam @Text "hello_world" `shouldBe` ParamValue "hello_world"+ toParam @Text "hello+world" `shouldBe` ParamValue "hello+world"++ it "encodes json" $ do+ let r = Record 10 "hello world"+ toParam r `shouldBe` jsonParam (toJSON r)++ let r2 = Record 10 "hello_world"+ toParam r2 `shouldBe` jsonParam (toJSON r2)+ toParam r2 `shouldBe` ParamValue (cs (encode r2))++ describe "FromParam" $ do+ it "should parse basics" $ do+ parseParam @Text "hello" `shouldBe` Right "hello"+ parseParam @Int "3" `shouldBe` Right 3++ it "decodes json" $ do+ let r2 = Record 10 "hello_world"+ parseParam (jsonParam r2) `shouldBe` Right r2+ parseParam (ParamValue $ cs $ encode r2) `shouldBe` Right r2++ it "can decode numbers as text" $ do+ parseParam @Text "3" `shouldBe` Right "3"++ it "should not escape text" $ do+ parseParam @Text "hello world" `shouldBe` Right "hello world"+ parseParam @Text "hello_world" `shouldBe` Right "hello_world"+ parseParam @Text "hello+world" `shouldBe` Right "hello+world"++ describe "RoundTrip" $ do+ it "round trips constructors" $ do+ parseParam (toParam A) `shouldBe` Right A+ parseParam (toParam B) `shouldBe` Right B+ parseParam (toParam C) `shouldBe` Right C+ let t = Tag "woo hoo"+ parseParam (toParam t) `shouldBe` Right t
test/Test/QuerySpec.hs view
@@ -1,78 +1,55 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE RecordWildCards #-} module Test.QuerySpec where import Data.Function ((&))-import Data.String.Conversions (cs) import Data.Text (Text)-import Network.HTTP.Types (urlEncode) import Skeletest-import Web.Hyperbole+import Skeletest.Predicate qualified as P+import Web.Hyperbole hiding (Number) import Web.Hyperbole.Data.QueryData as QueryData-import Web.Hyperbole.Data.Session as Session-import Web.Hyperbole.Effect.Server spec :: Spec-spec = do- describe "param" paramSpec+spec = withMarkers ["param"] $ do describe "render" renderSpec+ describe "class" classSpec describe "multi" multiSpec- describe "session" sessionSpec data Woot = Woot Text- deriving (Generic, Show, ToParam)-instance Session Woot where- cookiePath = Just ["somepage"]+ deriving (Generic, Show) -sessionSpec :: Spec-sessionSpec = do- it "should create cookie" $ do- let woot = Woot "hello"- sessionCookie woot `shouldBe` Cookie "Woot" (Just (toParam woot)) (Just ["somepage"])-- it "should render cookie with root path" $ do- let cookie = Cookie "Woot" (Just "Woot") Nothing- renderCookie [] cookie `shouldBe` "Woot=Woot; SameSite=None; secure; path=/"-- it "should render complex cookie with included path" $ do- let woot = Woot "hello"- let cookie = sessionCookie woot- renderCookie [] cookie `shouldBe` "Woot=" <> urlEncode True (cs (show woot)) <> "; SameSite=None; secure; path=/somepage"-- it "should parse cookies" $ do- cookiesFromHeader [("Woot", "Woot")] `shouldBe` Session.fromList [Cookie "Woot" (Just "Woot") Nothing]+data Record = Record+ { age :: Int+ , msg :: Text+ }+ deriving (Generic, ToJSON, FromJSON, ToParam, FromParam, Eq, FromQuery, ToQuery) -paramSpec :: Spec-paramSpec = do- describe "ToParam" $ do- it "should encode text flat" $ do- toParam @Text "hello" `shouldBe` "hello"-- it "should encode int" $ do- toParam @Int 23 `shouldBe` "23"-- it "should encode Maybe" $ do- toParam @(Maybe Int) Nothing `shouldBe` ""- toParam @(Maybe Int) (Just 23) `shouldBe` "23"-- it "should encode lists as show" $ do- let items = ["one", "two"]- toParam @[Text] items `shouldBe` ParamValue (cs (show items))+classSpec :: Spec+classSpec = do+ describe "FromQuery" $ do+ it "decodes record" $ do+ let qd = QueryData.parse "age=20&msg=hello_world"+ parseQuery @Record qd `shouldSatisfy` P.right P.anything - describe "FromParam" $ do- it "should parse text" $ do- parseParam @Text "hello" `shouldBe` Right "hello"+ it "decodes numbers as text if needed" $ do+ let qd = QueryData.parse "age=20&msg=30"+ parseQuery @Record qd `shouldBe` Right (Record 20 "30") - it "should parse int" $ do- parseParam @Int "3" `shouldBe` Right 3+ describe "ToQuery" $ do+ it "encodes record" $ do+ let r = Record 20 "hello world"+ QueryData.render (toQuery r) `shouldBe` "age=20&msg=hello_world" - it "should handle lists" $ do- let items = ["one", "two", "three"] :: [Text]- parseParam (toParam items) `shouldBe` Right items+ describe "roundtrip" $ do+ it "round trips" $ do+ let r = Record 20 "hello world"+ parseQuery (toQuery r) `shouldBe` Right r renderSpec :: Spec@@ -93,6 +70,10 @@ let q = mempty & QueryData.insert @Text "msg" "bob&henry=fast" QueryData.render q `shouldBe` "msg=bob%26henry%3Dfast" + -- it "handles underscores" $ do+ -- QueryData.render [(Param "msg", ParamValue "hello_world" $ String "hello_world")] `shouldBe` "msg=hello%5C_world"+ -- QueryData.render [(Param "msg", ParamValue "hello world" $ String "hello world")] `shouldBe` "msg=hello_world"+ it "should roundtrip special characters" $ do let msg = "bob&henry=fast" let q = mempty & QueryData.insert @Text "msg" msg@@ -100,18 +81,18 @@ let q' = QueryData.parse out QueryData.lookup "msg" q' `shouldBe` Just msg - it "should render lists" $ do- let items = ["one", "two"]- let q = mempty & QueryData.insert @[Text] "items" items- QueryData.render q `shouldBe` "items=" <> urlEncode True (cs $ show items) +-- it "should preserve plusses" $ do+-- let QueryData q = QueryData $ M.fromList [("items", "one+two")]+-- print $ HTTP.toQuery $ M.toList q+-- QueryData.render (QueryData q) `shouldBe` "items=one+two" data Filters = Filters { term :: Text , isActive :: Bool , another :: Maybe Text }- deriving (Eq, Show, ToParam)+ deriving (Eq, Show) instance ToQuery Filters where@@ -130,31 +111,41 @@ pure Filters{..} -data Nested = Nested- { filters :: Filters+data Filters' = Filters'+ { term :: Text+ , isActive :: Bool }+ deriving (Generic, Eq, ToJSON, FromJSON, FromParam, ToParam)+instance Default Filters' where+ def = Filters' "" False -instance ToQuery Nested where- toQuery n =- mempty & QueryData.insert "filters" n.filters+data Nested = Nested+ { filters :: Filters'+ }+ deriving (Generic, ToQuery, FromQuery) +-- instance ToQuery Nested where+-- toQuery n =+-- mempty & QueryData.insert "filters" (JSON n.filters)+--+--+-- instance FromQuery Nested where+-- parseQuery q =+-- mempty & QueryData.insert "filters" (JSON n.filters)+ multiSpec :: Spec multiSpec = do- it "should convert to querydata" $ do- let f = Filters "woot" False Nothing- QueryData.render (toQuery f) `shouldBe` "another=&isActive=false&term=woot"-- it "should parse from querydata" $ do- let f = Filters "woot" False Nothing- let out = QueryData.render (toQuery f)- let q = QueryData.parse out- parseQuery q `shouldBe` Right f+ describe "Roundtrip" $ do+ it "should parse from querydata" $ do+ let f = Filters "hello world" False Nothing+ let out = QueryData.render (toQuery f)+ let q = QueryData.parse out+ parseQuery q `shouldBe` Right f - it "should work with Just" $ do- let f = Filters "woot" False (Just "hello")- let out = QueryData.render (toQuery f)- print out- let q = QueryData.parse out- parseQuery q `shouldBe` Right f+ it "should work with Just" $ do+ let f = Filters "hello_world" False (Just "hello")+ let out = QueryData.render (toQuery f)+ let q = QueryData.parse out+ parseQuery q `shouldBe` Right f
test/Test/RouteSpec.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE OverloadedLists #-}+ module Test.RouteSpec where import Data.Text (Text)@@ -61,7 +63,7 @@ describe "matchRoute" $ do it "basic" $ matchRoute ["goodbye"] `shouldBe` Just Goodbye- it "default empty string" $ matchRoute [""] `shouldBe` Just MainPage+ -- it "default empty string" $ matchRoute [""] `shouldBe` Just MainPage it "default empty" $ matchRoute [] `shouldBe` Just MainPage it "compound" $ matchRoute ["hello", "world"] `shouldBe` Just (Hello World) it "compound default" $ matchRoute ["hello"] `shouldBe` Just (Hello MainHello)
+ test/Test/SessionSpec.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE OverloadedLists #-}++module Test.SessionSpec where++import Data.String.Conversions (cs)+import Data.Text (Text)+import Network.HTTP.Types (urlEncode)+import Skeletest+import Web.Hyperbole+import Web.Hyperbole.Data.Cookie as Cookie+import Web.Hyperbole.Data.Encoded qualified as Encoded+import Web.Hyperbole.Data.URI+import Web.Hyperbole.Effect.Session (sessionCookie)+++-- import Skeletest.Predicate qualified as P++data Woot = Woot Text+ deriving (Generic, Show, ToEncoded, FromEncoded)+instance Session Woot where+ cookiePath = Just $ Path ["somepage"]+++spec :: Spec+spec = do+ describe "Session" $ do+ it "should encode cookie" $ do+ let woot = Woot "hello"+ toCookie woot `shouldBe` CookieValue (cs $ Encoded.encode woot)++ describe "sessionCookie" $ do+ it "should create cookie" $ do+ let woot = Woot "hello"+ sessionCookie woot `shouldBe` Cookie (sessionKey @Woot) (cookiePath @Woot) (Just (toCookie woot))++ describe "render" $ do+ it "should parse cookies" $ do+ Cookie.parse [("Woot", "Woot")] `shouldBe` Right (Cookie.fromList [Cookie "Woot" Nothing (Just (CookieValue "Woot"))])++ it "should render cookie with root path" $ do+ let cookie = Cookie "Woot" Nothing (Just (CookieValue "Woot"))+ Cookie.render [] cookie `shouldBe` "Woot=Woot; SameSite=None; secure; path=/"++ it "should render complex cookie with included path" $ do+ let woot = Woot "hello world"+ let cookie = sessionCookie woot+ Cookie.render [] cookie `shouldBe` "Woot=" <> urlEncode True (cs $ Encoded.encode woot) <> "; SameSite=None; secure; path=/somepage"++ describe "Session class" $ do+ it "should encode class" $ do+ let prefs = Preferences "hello" Warning+ let cooks = Cookie.insert (sessionCookie prefs) mempty+ Cookie.lookup (sessionKey @Preferences) cooks `shouldBe` Just (CookieValue $ cs $ Encoded.encode prefs)++ it "should decode class" $ do+ let prefs = Preferences "hello" Warning+ let cooks = Cookie.insert (sessionCookie prefs) mempty+ Just val <- pure $ Cookie.lookup (sessionKey @Preferences) cooks+ parseCookie val `shouldBe` Right prefs+++data Preferences = Preferences+ { message :: Text+ , color :: AppColor+ }+ deriving (Generic, Eq, Show, ToEncoded, FromEncoded, Session)+instance Default Preferences where+ def = Preferences "_" White+++data AppColor+ = White+ | Light+ | GrayLight+ | GrayDark+ | Dark+ | DarkHighlight+ | Success+ | Danger+ | Warning+ | Primary+ | PrimaryLight+ | Secondary+ | SecondaryLight+ deriving (Show, Eq, Generic, ToParam, FromParam)
+ test/Test/URISpec.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE QuasiQuotes #-}++module Test.URISpec where++import Skeletest+import Web.Hyperbole+import Web.Hyperbole.Data.URI+++spec :: Spec+spec = do+ describe "URI" $ do+ it "should preserve slashes" $ do+ let u = [uri|http://example.com|] ./. "hello"+ u.uriPath `shouldBe` "/hello"++ it "should render with path slashes" $ do+ let u = [uri|http://example.com/test|]+ uriToText (u ./. ["wahoo"]) `shouldBe` "http://example.com/test/wahoo"+ uriToText (u ./. ["/wahoo"]) `shouldBe` "http://example.com/test/wahoo"+ uriToText (u ./. []) `shouldBe` "http://example.com/test"+ uriToText (u ./. "/") `shouldBe` "http://example.com/test"++ describe "Path" $ do+ it "handles edge cases" $ do+ path "" `shouldBe` []+ path "/" `shouldBe` []++ it "normal paths" $ do+ path "woot" `shouldBe` ["woot"]+ path "woot/hello" `shouldBe` ["woot", "hello"]+ path "/woot/hello" `shouldBe` ["woot", "hello"]+ path "/woot/hello/" `shouldBe` ["woot", "hello"]
test/Test/ViewActionSpec.hs view
@@ -3,20 +3,24 @@ import Data.Text (Text) import GHC.Generics import Skeletest-import Web.Hyperbole.HyperView-import Web.Hyperbole.View.Event (toActionInput)+import Skeletest.Predicate qualified as P+import Web.Hyperbole (FromJSON, ToJSON)+import Web.Hyperbole.Data.Encoded+import Web.Hyperbole.Data.Param+import Web.Hyperbole.HyperView (ViewAction (..))+import Web.Hyperbole.HyperView.Event (toActionInput) data Simple = Simple- deriving (Generic, Eq, Show, Read, ViewAction)+ deriving (Generic, Eq, Show, Read, ViewAction, ToJSON, FromJSON, ToParam, FromParam) data Product = Product String Int- deriving (Generic, Show, Eq, ViewAction, Read)+ deriving (Generic, Show, Eq, ViewAction, Read, ToJSON, FromJSON, ToEncoded, FromEncoded, ToParam, FromParam) data Product' = Product' HasText Int- deriving (Generic, Show, Eq, ViewAction, Read)+ deriving (Generic, Show, Eq, ViewAction, Read, ToJSON, FromJSON, ToEncoded, FromEncoded) data Sum@@ -24,69 +28,72 @@ | SumB Int | SubC Text | SubD (Maybe Text)- deriving (Generic, Show, Eq, Read, ViewAction)+ | SubE Term+ | SubF Simple+ deriving (Generic, Show, Read, Eq, ViewAction) data Compound = Compound Product- deriving (Generic, Show, Eq, Read, ViewAction)+ deriving (Generic, Show, Eq, Read, ToJSON, FromEncoded, ToEncoded, FromJSON, ViewAction) data HasText = HasText Text- deriving (Generic, Show, Eq, Read, ViewAction)+ deriving (Generic, Show, Eq, Read, ViewAction, ToJSON, FromJSON, FromEncoded, ToEncoded, ToParam, FromParam) --- data Compound--- = One--- | Two Thing--- | WithId (Id Thing)--- deriving (Generic, Param, Show, Eq)--------- newtype Id a = Id {fromId :: Text}--- deriving newtype (Param, Eq, Ord)--- deriving (Generic, Show)--------- instance Param Custom where--- toParam Custom = "something"--- parseParam "something" = Just Custom--- parseParam _ = Nothing+newtype Term = Term Text+ deriving newtype (Eq, Show, ToJSON, FromJSON, Read, ToParam, FromParam) + spec :: Spec-spec = do+spec = withMarkers ["encoded"] $ do describe "ViewAction" $ do describe "toAction" $ do- it "simple" $ toAction Simple `shouldBe` "Simple"- it "has text" $ toAction (HasText "hello world") `shouldBe` "HasText \"hello world\""- it "product" $ toAction (Product "hello world" 123) `shouldBe` "Product \"hello world\" 123"- it "sum" $ toAction (SumB 123) `shouldBe` "SumB 123"- it "compound" $ toAction (Compound (Product "hello world" 123)) `shouldBe` "Compound (Product \"hello world\" 123)"+ it "simple" $ toAction Simple `shouldBe` Encoded "Simple" []+ it "has text" $ toAction (HasText "hello world") `shouldBe` Encoded "HasText" ["hello world"]+ it "product" $ toAction (Product "hello world" 123) `shouldBe` Encoded "Product" ["hello world", toParam @Int 123]+ it "sum" $ toAction (SumB 123) `shouldBe` Encoded "SumB" [toParam @Int 123]+ it "compound" $ do+ let p = Product "hello world" 123+ toAction (Compound p) `shouldBe` Encoded "Compound" [toParam p] - describe "toActionInput" $ withMarkers ["focus"] $ do+ describe "toActionInput" $ do it "Constructor Text" $ do- toActionInput SubC `shouldBe` "SubC \"%HYP-INP%\""+ toActionInput SubC `shouldBe` Encoded "SubC" [] it "Constructor (Maybe Text)" $ do- toActionInput (SubD . Just) `shouldBe` "SubD (Just \"%HYP-INP%\")"+ toActionInput (SubD . Just) `shouldBe` Encoded "SubD" [] + it "Constructor newtype Term" $ do+ toActionInput (SubE . Term) `shouldBe` Encoded "SubE" []++ it "renders data constructors" $ do+ toActionInput SubF `shouldBe` Encoded "SubF" []+ describe "parseAction" $ do- it "simple" $ parseAction "Simple" `shouldBe` Just Simple+ it "simple" $ parseAction (Encoded "Simple" []) `shouldBe` pure Simple + it "parse product" $ do+ parseAction @Product (Encoded "Product" ["woot", toParam @Int 1234]) `shouldSatisfy` P.right P.anything++ it "parse product with spaces" $ do+ parseAction @Product (Encoded "Product" ["hello world", toParam @Int 1234]) `shouldSatisfy` P.right P.anything+ describe "roundTrip" $ do it "simple" $ do- parseAction (toAction Simple) `shouldBe` Just Simple+ parseAction (toAction Simple) `shouldBe` pure Simple it "has text multiple words" $ do let a = HasText "hello world"- parseAction (toAction a) `shouldBe` Just a+ parseAction (toAction a) `shouldBe` pure a it "product" $ do let a = Product "hello world" 123- parseAction (toAction a) `shouldBe` Just a+ parseAction @Product (toAction a) `shouldBe` pure a it "product'" $ do let a = Product' (HasText "hello world") 123- parseAction (toAction a) `shouldBe` Just a+ parseAction (toAction a) `shouldBe` pure a it "compound" $ do let a = Compound (Product "hello world" 123)- parseAction (toAction a) `shouldBe` Just a+ parseAction (toAction a) `shouldBe` pure a it "sum" $ do let a = SumB 123- parseAction (toAction a) `shouldBe` Just a+ parseAction (toAction a) `shouldBe` pure a
test/Test/ViewIdSpec.hs view
@@ -2,17 +2,17 @@ module Test.ViewIdSpec where -import Data.Text (Text, pack)+import Data.Text (Text) import Data.Text qualified as T import GHC.Generics import Skeletest+import Web.Hyperbole+import Web.Hyperbole.Data.Encoded import Web.Hyperbole.HyperView-import Web.View (att)-import Web.View.Types data Thing = Thing- deriving (Generic, Show, Read, Eq, ViewId)+ deriving (Generic, Show, Eq, ToJSON, FromJSON, ToEncoded, FromEncoded, ViewId, ToParam, FromParam) data Custom = Custom@@ -28,7 +28,7 @@ | Two Thing | WithId (Id Thing) | Compound Text Compound- deriving (Generic, Show, Eq, Read, ViewId)+ deriving (Generic, Show, Eq, ToJSON, FromJSON, ToEncoded, FromEncoded, ViewId, ToParam, FromParam) data Product4 = Product4 Text Text Text Text@@ -36,71 +36,71 @@ newtype Id a = Id {fromId :: Text}- deriving newtype (Eq, Ord, Show, Read)+ deriving newtype (Eq, ToJSON, FromJSON, Ord, Show, ToParam, FromParam) deriving (Generic) instance ViewId Custom where- toViewId Custom = "something"- parseViewId "something" = Just Custom- parseViewId _ = Nothing+ toViewId Custom = Encoded "something" []+ parseViewId (Encoded "something" []) = pure Custom+ parseViewId _ = Left "NOPE" spec :: Spec-spec = do- describe "ViewId" $ do+spec = withMarkers ["encoded"] $ do+ describe "ViewId Encoded" $ do describe "toViewId" $ do- it "basic" $ toViewId Thing `shouldBe` "Thing"- it "custom" $ toViewId Custom `shouldBe` "something"+ it "basic" $ encodeViewId Thing `shouldBe` "Thing"+ it "custom" $ encodeViewId Custom `shouldBe` "something" describe "parseViewId" $ do- it "basic lowercase" $ parseViewId @Thing "thing" `shouldBe` Nothing- it "basic" $ parseViewId @Thing "Thing" `shouldBe` Just Thing- it "custom" $ parseViewId "something" `shouldBe` Just Custom- it "custom other" $ parseViewId @Thing "custom" `shouldBe` Nothing+ it "basic lowercase" $ decodeViewId @Thing "thing" `shouldBe` Nothing+ it "basic" $ decodeViewId @Thing "Thing" `shouldBe` pure Thing+ it "custom" $ decodeViewId @Custom "something" `shouldBe` pure Custom+ it "custom other" $ decodeViewId @Thing "custom" `shouldBe` Nothing describe "has-string" $ do it "should not contain single quotes" $ do- toViewId (HasString "woot") `shouldBe` "HasString \"woot\""- containsSingleQuotes (toViewId (HasString "woot")) `shouldBe` False+ encodeViewId (HasString "woot") `shouldBe` "HasString woot"+ containsSingleQuotes (encodeViewId (HasString "woot")) `shouldBe` False it "should roundtrip" $ do let inp = HasString "woot"- parseViewId (toViewId inp) `shouldBe` Just inp+ decodeViewId (encodeViewId inp) `shouldBe` pure inp describe "compound" $ do- it "should toparam" $ toViewId (Two Thing) `shouldBe` "Two Thing"- it "double roundtrip" $ parseViewId (toViewId (Two Thing)) `shouldBe` Just (Two Thing)+ it "double roundtrip" $ decodeViewId (encodeViewId (Two Thing)) `shouldBe` pure (Two Thing) describe "nested" $ do let nest = Compound "one" $ Compound "two" (Two Thing)- it "should roundtrip" $ parseViewId (toViewId nest) `shouldBe` Just nest+ it "should roundtrip" $ decodeViewId (encodeViewId nest) `shouldBe` pure nest describe "big product" $ do let p = Product4 "one" "two" "three" "four"- it "should roundtrip" $ parseViewId (toViewId p) `shouldBe` Just p+ it "should roundtrip" $ do+ let vid = encodeViewId p+ decodeViewId vid `shouldBe` pure p - describe "Param Attributes" $ do- it "should serialize basic id" $ do- let atts = mempty :: Attributes id- (setId "woot" atts).other `shouldBe` [("id", "woot")] - it "should serialize compound id" $ do- let atts = mempty :: Attributes id- (setId (toViewId $ Two Thing) atts).other `shouldBe` [("id", pack $ show $ Two Thing)]-- it "should serialize stringy id" $ do- let atts = mempty :: Attributes id- (setId (toViewId $ HasString "woot") atts).other `shouldBe` [("id", pack $ show $ HasString "woot")]-- it "should serialize with Id" $ do- let atts = mempty :: Attributes id- (setId (toViewId $ WithId (Id "woot")) atts).other `shouldBe` [("id", "WithId \"woot\"")]-+-- describe "Param Attributes" $ do+-- it "should serialize basic id" $ do+-- let atts = mempty :: Attributes id+-- (setId "woot" atts).other `shouldBe` [("id", "woot")]+--+-- it "should serialize compound id" $ do+-- let atts = mempty :: Attributes id+-- (setId (toViewId $ Two Thing) atts).other `shouldBe` [("id", toViewId $ Two Thing)]+--+-- it "should serialize stringy id" $ do+-- let atts = mempty :: Attributes id+-- (setId (toViewId $ HasString "woot") atts).other `shouldBe` [("id", pack $ show $ HasString "woot")]+--+-- it "should serialize with Id" $ do+-- let atts = mempty :: Attributes id+-- (setId (toViewId $ WithId (Id "woot")) atts).other `shouldBe` [("id", "WithId \"woot\"")] containsSingleQuotes :: Text -> Bool containsSingleQuotes = T.elem '\'' --setId :: Text -> Mod id-setId = att "id"+-- setId :: Text -> Mod id+-- setId = att "id"