on master help sign in
Merge pull request #286 from obsidiansystems/aa-routetourl
5b29463Luigy Leon
lib/backend/src/Obelisk/Backend.hs+22−13
@@ -5,6 +5,7 @@
5{-# LANGUAGE LambdaCase #-}
5{-# LANGUAGE LambdaCase #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
7{-# LANGUAGE OverloadedStrings #-}
7{-# LANGUAGE OverloadedStrings #-}
8+{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE TemplateHaskell #-}
9{-# LANGUAGE TemplateHaskell #-}
9module Obelisk.Backend
10module Obelisk.Backend
10 ( Backend (..)
11 ( Backend (..)
@@ -17,6 +18,7 @@ module Obelisk.Backend
17 , prettifyOutput
18 , prettifyOutput
18 , runBackend
19 , runBackend
19 , staticRenderContentType
20 , staticRenderContentType
21+ , mkRouteToUrl
20 ) where
22 ) where
21
23
22import Prelude hiding (id, (.))
24import Prelude hiding (id, (.))
@@ -57,8 +59,8 @@ data GhcjsApp route = GhcjsApp
57
59
58-- | Serve a frontend, which must be the same frontend that Obelisk has built and placed in the default location
60-- | Serve a frontend, which must be the same frontend that Obelisk has built and placed in the default location
59--TODO: The frontend should be provided together with the asset paths so that this isn't so easily breakable; that will probably make this function obsolete
61--TODO: The frontend should be provided together with the asset paths so that this isn't so easily breakable; that will probably make this function obsolete
60−serveDefaultObeliskApp :: MonadSnap m => ([Text] -> m ()) -> Frontend (R appRoute) -> R (ObeliskRoute appRoute) -> m ()
62+serveDefaultObeliskApp :: MonadSnap m => (R appRoute -> Text) -> ([Text] -> m ()) -> Frontend (R appRoute) -> R (ObeliskRoute appRoute) -> m ()
61−serveDefaultObeliskApp serveStaticAsset frontend = serveObeliskApp serveStaticAsset frontendApp
63+serveDefaultObeliskApp urlEnc serveStaticAsset frontend = serveObeliskApp urlEnc serveStaticAsset frontendApp
62 where frontendApp = GhcjsApp
64 where frontendApp = GhcjsApp
63 { _ghcjsApp_compiled = defaultFrontendGhcjsAssets
65 { _ghcjsApp_compiled = defaultFrontendGhcjsAssets
64 , _ghcjsApp_value = frontend
66 , _ghcjsApp_value = frontend
@@ -109,12 +111,12 @@ getRouteWith e = do
109 pageName <- getPageName
111 pageName <- getPageName
110 return $ tryDecode e pageName
112 return $ tryDecode e pageName
111
113
112−serveObeliskApp :: MonadSnap m => ([Text] -> m ()) -> GhcjsApp (R appRoute) -> R (ObeliskRoute appRoute) -> m ()
114+serveObeliskApp :: MonadSnap m => (R appRoute -> Text) -> ([Text] -> m ()) -> GhcjsApp (R appRoute) -> R (ObeliskRoute appRoute) -> m ()
113−serveObeliskApp serveStaticAsset frontendApp = \case
115+serveObeliskApp urlEnc serveStaticAsset frontendApp = \case
114− ObeliskRoute_App appRouteComponent :=> Identity appRouteRest -> serveGhcjsApp frontendApp $ GhcjsAppRoute_App appRouteComponent :/ appRouteRest
116+ ObeliskRoute_App appRouteComponent :=> Identity appRouteRest -> serveGhcjsApp urlEnc frontendApp $ GhcjsAppRoute_App appRouteComponent :/ appRouteRest
115 ObeliskRoute_Resource resComponent :=> Identity resRest -> case resComponent :=> Identity resRest of
117 ObeliskRoute_Resource resComponent :=> Identity resRest -> case resComponent :=> Identity resRest of
116 ResourceRoute_Static :=> Identity pathSegments -> serveStaticAsset pathSegments
118 ResourceRoute_Static :=> Identity pathSegments -> serveStaticAsset pathSegments
117− ResourceRoute_Ghcjs :=> Identity pathSegments -> serveGhcjsApp frontendApp $ GhcjsAppRoute_Resource :/ pathSegments
119+ ResourceRoute_Ghcjs :=> Identity pathSegments -> serveGhcjsApp urlEnc frontendApp $ GhcjsAppRoute_Resource :/ pathSegments
118 ResourceRoute_JSaddleWarp :=> Identity _ -> do
120 ResourceRoute_JSaddleWarp :=> Identity _ -> do
119 let msg = "Error: Obelisk.Backend received jsaddle request"
121 let msg = "Error: Obelisk.Backend received jsaddle request"
120 liftIO $ putStrLn $ T.unpack msg
122 liftIO $ putStrLn $ T.unpack msg
@@ -137,11 +139,11 @@ staticRenderContentType :: ByteString
137staticRenderContentType = "text/html; charset=utf-8"
139staticRenderContentType = "text/html; charset=utf-8"
138
140
139--TODO: Don't assume we're being served at "/"
141--TODO: Don't assume we're being served at "/"
140−serveGhcjsApp :: MonadSnap m => GhcjsApp (R appRouteComponent) -> R (GhcjsAppRoute appRouteComponent) -> m ()
142+serveGhcjsApp :: MonadSnap m => (R appRouteComponent -> Text) -> GhcjsApp (R appRouteComponent) -> R (GhcjsAppRoute appRouteComponent) -> m ()
141−serveGhcjsApp app = \case
143+serveGhcjsApp urlEnc app = \case
142 GhcjsAppRoute_App appRouteComponent :=> Identity appRouteRest -> do
144 GhcjsAppRoute_App appRouteComponent :=> Identity appRouteRest -> do
143 modifyResponse $ setContentType staticRenderContentType
145 modifyResponse $ setContentType staticRenderContentType
144− writeBS <=< liftIO $ renderGhcjsFrontend (appRouteComponent :/ appRouteRest) $ _ghcjsApp_value app
146+ writeBS <=< liftIO $ renderGhcjsFrontend urlEnc (appRouteComponent :/ appRouteRest) $ _ghcjsApp_value app
145 GhcjsAppRoute_Resource :=> Identity pathSegments -> serveStaticAssets (_ghcjsApp_compiled app) pathSegments
147 GhcjsAppRoute_Resource :=> Identity pathSegments -> serveStaticAssets (_ghcjsApp_compiled app) pathSegments
146
148
147runBackend :: Backend fullRoute frontendRoute -> Frontend (R frontendRoute) -> IO ()
149runBackend :: Backend fullRoute frontendRoute -> Frontend (R frontendRoute) -> IO ()
@@ -152,10 +154,17 @@ runBackend backend frontend = case checkEncoder $ _backend_routeEncoder backend
152 getRouteWith validFullEncoder >>= \case
154 getRouteWith validFullEncoder >>= \case
153 Identity r -> case r of
155 Identity r -> case r of
154 InL backendRoute :=> Identity a -> serveRoute $ backendRoute :/ a
156 InL backendRoute :=> Identity a -> serveRoute $ backendRoute :/ a
157+ InR obeliskRoute :=> Identity a ->
155− InR obeliskRoute :=> Identity a -> serveDefaultObeliskApp (serveStaticAssets defaultStaticAssets) frontend $ obeliskRoute :/ a
158+ serveDefaultObeliskApp (mkRouteToUrl validFullEncoder) (serveStaticAssets defaultStaticAssets) frontend $ obeliskRoute :/ a
156
159
160+mkRouteToUrl :: Encoder Identity parse (R (Sum f (ObeliskRoute r))) PageName -> R r -> Text
161+mkRouteToUrl validFullEncoder =
162+ let pageNameEncoder' :: Encoder Identity (Either Text) PageName PathQuery = pageNameEncoder
163+ in \(k :/ v) -> T.pack . uncurry (<>) . encode pageNameEncoder' . encode validFullEncoder $ (InR $ ObeliskRoute_App k) :/ v
164+
165+
157−renderGhcjsFrontend :: route -> Frontend route -> IO ByteString
166+renderGhcjsFrontend :: MonadIO m => (route -> Text) -> route -> Frontend route -> m ByteString
158−renderGhcjsFrontend route f = do
167+renderGhcjsFrontend urlEnc route f = do
159 let baseTag = elAttr "base" ("href" =: "/") blank --TODO: Figure out the base URL from the routes
168 let baseTag = elAttr "base" ("href" =: "/") blank --TODO: Figure out the base URL from the routes
160 ghcjsScript = elAttr "script" ("language" =: "javascript" <> "src" =: "ghcjs/all.js" <> "defer" =: "defer") blank
169 ghcjsScript = elAttr "script" ("language" =: "javascript" <> "src" =: "ghcjs/all.js" <> "defer" =: "defer") blank
161− renderFrontendHtml route (_frontend_head f >> injectExecutableConfigs >> baseTag) (_frontend_body f >> ghcjsScript)
170+ liftIO $ renderFrontendHtml urlEnc route (_frontend_head f >> injectExecutableConfigs >> baseTag) (_frontend_body f >> ghcjsScript)
lib/frontend/src/Obelisk/Frontend.hs+11−8
@@ -32,6 +32,7 @@ import Data.Dependent.Sum (DSum (..))
32import Data.Functor.Sum
32import Data.Functor.Sum
33import Data.IORef
33import Data.IORef
34import Data.Monoid ((<>))
34import Data.Monoid ((<>))
35+import Data.Text (Text)
35import GHCJS.DOM hiding (bracket, catch)
36import GHCJS.DOM hiding (bracket, catch)
36import GHCJS.DOM.Document
37import GHCJS.DOM.Document
37import GHCJS.DOM.Node
38import GHCJS.DOM.Node
@@ -64,6 +65,7 @@ type ObeliskWidget t x route m =
64 , PrimMonad m
65 , PrimMonad m
65 , Prerender x m
66 , Prerender x m
66 , SetRoute t route m
67 , SetRoute t route m
68+ , RouteToUrl route m
67 )
69 )
68
70
69data Frontend route = Frontend
71data Frontend route = Frontend
@@ -143,22 +145,23 @@ runFrontend validFullEncoder frontend = do
143 , MonadFix m
145 , MonadFix m
144 , MonadFix (Performable m)
146 , MonadFix (Performable m)
145 )
147 )
146− => RoutedT t (R route) (SetRouteT t (R route) m) a
148+ => RoutedT t (R route) (SetRouteT t (R route) (RouteToUrlT (R route) m)) a
147 -> m a
149 -> m a
148 runMyRouteViewT = runRouteViewT ve
150 runMyRouteViewT = runRouteViewT ve
149 runWithHeadAndBody $ \appendHead appendBody -> runMyRouteViewT $ do
151 runWithHeadAndBody $ \appendHead appendBody -> runMyRouteViewT $ do
150− mapRoutedT (mapSetRouteT appendHead) $ _frontend_head frontend
152+ mapRoutedT (mapSetRouteT (mapRouteToUrlT appendHead)) $ _frontend_head frontend
151− mapRoutedT (mapSetRouteT appendBody) $ _frontend_body frontend
153+ mapRoutedT (mapSetRouteT (mapRouteToUrlT appendBody)) $ _frontend_body frontend
152
154
153renderFrontendHtml
155renderFrontendHtml
154 :: (t ~ DomTimeline)
156 :: (t ~ DomTimeline)
155− => r
157+ => (r' -> Text)
158+ -> r
156− -> RoutedT t r (SetRouteT t r' (PostBuildT DomTimeline (StaticDomBuilderT DomTimeline (PerformEventT DomTimeline DomHost)))) ()
159+ -> RoutedT t r (SetRouteT t r' (RouteToUrlT r' (PostBuildT DomTimeline (StaticDomBuilderT DomTimeline (PerformEventT DomTimeline DomHost))))) ()
157− -> RoutedT t r (SetRouteT t r' (PostBuildT DomTimeline (StaticDomBuilderT DomTimeline (PerformEventT DomTimeline DomHost)))) ()
160+ -> RoutedT t r (SetRouteT t r' (RouteToUrlT r' (PostBuildT DomTimeline (StaticDomBuilderT DomTimeline (PerformEventT DomTimeline DomHost))))) ()
158 -> IO ByteString
161 -> IO ByteString
159−renderFrontendHtml route headWidget bodyWidget = do
162+renderFrontendHtml urlEnc route headWidget bodyWidget = do
160 --TODO: We should probably have a "NullEventWriterT" or a frozen reflex timeline
163 --TODO: We should probably have a "NullEventWriterT" or a frozen reflex timeline
161− html <- fmap snd $ renderStatic $ fmap fst $ runSetRouteT $ flip runRoutedT (pure route) $
164+ html <- fmap snd $ renderStatic $ fmap fst $ flip runRouteToUrlT urlEnc $ runSetRouteT $ flip runRoutedT (pure route) $
162 el "html" $ do
165 el "html" $ do
163 el "head" headWidget
166 el "head" headWidget
164 el "body" bodyWidget
167 el "body" bodyWidget
lib/route/src/Obelisk/Route/Frontend.hs+108−2
@@ -41,6 +41,11 @@ module Obelisk.Route.Frontend
41 , SetRoute(..)
41 , SetRoute(..)
42 , runSetRouteT
42 , runSetRouteT
43 , mapSetRouteT
43 , mapSetRouteT
44+ , RouteToUrl(..)
45+ , RouteToUrlT(..)
46+ , runRouteToUrlT
47+ , mapRouteToUrlT
48+ , routeLink
44 ) where
49 ) where
45
50
46import Prelude hiding ((.), id)
51import Prelude hiding ((.), id)
@@ -61,6 +66,8 @@ import Data.Constraint (Dict (..))
61import Data.Dependent.Sum (DSum (..))
66import Data.Dependent.Sum (DSum (..))
62import Data.GADT.Compare
67import Data.GADT.Compare
63import Data.Monoid
68import Data.Monoid
69+import Data.Proxy
70+import Data.Text (Text)
64import qualified Data.Text as T
71import qualified Data.Text as T
65import Data.Functor.Compose
72import Data.Functor.Compose
66import Reflex.Class
73import Reflex.Class
@@ -290,6 +297,85 @@ instance (Monad m, MonadQuery t vs m) => MonadQuery t vs (SetRouteT t r m) where
290 askQueryResult = lift askQueryResult
297 askQueryResult = lift askQueryResult
291 queryIncremental = lift . queryIncremental
298 queryIncremental = lift . queryIncremental
292
299
300+class RouteToUrl r m | m -> r where
301+ askRouteToUrl :: m (r -> Text)
302+
303+newtype RouteToUrlT r m a = RouteToUrlT { unRouteToUrlT :: ReaderT (r -> Text) m a }
304+ deriving (Functor, Applicative, Monad, MonadFix, MonadTrans, NotReady t, MonadHold t, MonadSample t, PostBuild t, TriggerEvent t, MonadIO, MonadReflexCreateTrigger t, HasDocument)
305+
306+runRouteToUrlT
307+ :: RouteToUrlT r m a
308+ -> (r -> Text)
309+ -> m a
310+runRouteToUrlT a = runReaderT (unRouteToUrlT a)
311+
312+mapRouteToUrlT :: (forall x. m x -> n x) -> RouteToUrlT r m a -> RouteToUrlT r n a
313+mapRouteToUrlT f (RouteToUrlT m) = RouteToUrlT $ mapReaderT f m
314+
315+instance Monad m => RouteToUrl r (RouteToUrlT r m) where
316+ askRouteToUrl = RouteToUrlT ask
317+
318+instance (Monad m, RouteToUrl r m) => RouteToUrl r (SetRouteT t r' m) where
319+ askRouteToUrl = lift askRouteToUrl
320+
321+instance (Monad m, RouteToUrl r m) => RouteToUrl r (RoutedT t r' m) where
322+ askRouteToUrl = lift askRouteToUrl
323+
324+instance HasJSContext m => HasJSContext (RouteToUrlT r m) where
325+ type JSContextPhantom (RouteToUrlT r m) = JSContextPhantom m
326+ askJSContext = lift askJSContext
327+
328+instance Prerender js m => Prerender js (RouteToUrlT r m) where
329+ prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
330+
331+instance Requester t m => Requester t (RouteToUrlT r m) where
332+ type Request (RouteToUrlT r m) = Request m
333+ type Response (RouteToUrlT r m) = Response m
334+ requesting = RouteToUrlT . requesting
335+ requesting_ = RouteToUrlT . requesting_
336+
337+#ifndef ghcjs_HOST_OS
338+deriving instance MonadJSM m => MonadJSM (RouteToUrlT r m)
339+#endif
340+
341+instance PerformEvent t m => PerformEvent t (RouteToUrlT r m) where
342+ type Performable (RouteToUrlT r m) = Performable m
343+ performEvent = lift . performEvent
344+ performEvent_ = lift . performEvent_
345+
346+instance MonadRef m => MonadRef (RouteToUrlT r m) where
347+ type Ref (RouteToUrlT r m) = Ref m
348+ newRef = lift . newRef
349+ readRef = lift . readRef
350+ writeRef r = lift . writeRef r
351+
352+instance HasJS x m => HasJS x (RouteToUrlT r m) where
353+ type JSX (RouteToUrlT r m) = JSX m
354+ liftJS = lift . liftJS
355+
356+instance MonadTransControl (RouteToUrlT r) where
357+ type StT (RouteToUrlT r) a = StT (ReaderT (r -> Text)) a
358+ liftWith = defaultLiftWith RouteToUrlT unRouteToUrlT
359+ restoreT = defaultRestoreT RouteToUrlT
360+
361+instance PrimMonad m => PrimMonad (RouteToUrlT r m ) where
362+ type PrimState (RouteToUrlT r m) = PrimState m
363+ primitive = lift . primitive
364+
365+instance DomBuilder t m => DomBuilder t (RouteToUrlT r m) where
366+ type DomBuilderSpace (RouteToUrlT r m) = DomBuilderSpace m
367+
368+instance Adjustable t m => Adjustable t (RouteToUrlT r m) where
369+ runWithReplace a0 a' = RouteToUrlT $ runWithReplace (coerce a0) $ coerceEvent a'
370+ traverseIntMapWithKeyWithAdjust f a0 a' = RouteToUrlT $ traverseIntMapWithKeyWithAdjust (coerce f) (coerce a0) $ coerce a'
371+ traverseDMapWithKeyWithAdjust f a0 a' = RouteToUrlT $ traverseDMapWithKeyWithAdjust (\k v -> coerce $ f k v) (coerce a0) $ coerce a'
372+ traverseDMapWithKeyWithAdjustWithMove f a0 a' = RouteToUrlT $ traverseDMapWithKeyWithAdjustWithMove (\k v -> coerce $ f k v) (coerce a0) $ coerce a'
373+
374+instance (Monad m, MonadQuery t vs m) => MonadQuery t vs (RouteToUrlT r m) where
375+ tellQueryIncremental = lift . tellQueryIncremental
376+ askQueryResult = lift askQueryResult
377+ queryIncremental = lift . queryIncremental
378+
293runRouteViewT
379runRouteViewT
294 :: forall t m r a.
380 :: forall t m r a.
295 ( TriggerEvent t m
381 ( TriggerEvent t m
@@ -300,7 +386,7 @@ runRouteViewT
300 , MonadFix m
386 , MonadFix m
301 )
387 )
302 => (Encoder Identity Identity r PageName)
388 => (Encoder Identity Identity r PageName)
303− -> RoutedT t r (SetRouteT t r m) a
389+ -> RoutedT t r (SetRouteT t r (RouteToUrlT r m)) a
304 -> m a
390 -> m a
305runRouteViewT routeEncoder a = do
391runRouteViewT routeEncoder a = do
306 rec historyState <- manageHistory $ HistoryCommand_PushState <$> setState
392 rec historyState <- manageHistory $ HistoryCommand_PushState <$> setState
@@ -313,7 +399,7 @@ runRouteViewT routeEncoder a = do
313 where
399 where
314 errorLeft (Left e) = error (T.unpack e)
400 errorLeft (Left e) = error (T.unpack e)
315 errorLeft (Right x) = x
401 errorLeft (Right x) = x
316− (result, changeState) <- runSetRouteT $ runRoutedT a route
402+ (result, changeState) <- runRouteToUrlT (runSetRouteT $ runRoutedT a route) $ (\(p, q) -> T.pack $ p <> q) . encode theEncoder
317 let f (currentHistoryState, oldRoute) change =
403 let f (currentHistoryState, oldRoute) change =
318 let newRoute = appEndo change oldRoute
404 let newRoute = appEndo change oldRoute
319 (newPath, newQuery) = encode theEncoder newRoute
405 (newPath, newQuery) = encode theEncoder newRoute
@@ -337,6 +423,26 @@ runRouteViewT routeEncoder a = do
337 setState = attachWith f ((,) <$> current historyState <*> current route) changeState
423 setState = attachWith f ((,) <$> current historyState <*> current route) changeState
338 return result
424 return result
339
425
426+-- | A link widget that, when clicked, sets the route to the provided route. In non-javascript
427+-- contexts, this widget falls back to using @href@s to control navigation
428+routeLink
429+ :: forall t m a route.
430+ ( DomBuilder t m
431+ , RouteToUrl (R route) m
432+ , SetRoute t (R route) m
433+ )
434+ => R route -- ^ Target route
435+ -> m a -- ^ Child widget
436+ -> m a
437+routeLink r w = do
438+ enc <- askRouteToUrl
439+ let cfg = (def :: ElementConfig EventResult t (DomBuilderSpace m))
440+ & elementConfig_eventSpec %~ addEventSpecFlags (Proxy :: Proxy (DomBuilderSpace m)) Click (\_ -> preventDefault)
441+ & elementConfig_initialAttributes .~ "href" =: enc r
442+ (e, a) <- element "a" cfg w
443+ setRoute $ r <$ domEvent Click e
444+ return a
445+
340-- On ios due to sandboxing when loading the page from a file adapt the
446-- On ios due to sandboxing when loading the page from a file adapt the
341-- path to be based on the hash.
447-- path to be based on the hash.
342
448
lib/run/src/Obelisk/Run.hs+6−5
@@ -82,7 +82,8 @@ run port serveStaticAsset backend frontend = do
82 getRouteWith validFullEncoder >>= \case
82 getRouteWith validFullEncoder >>= \case
83 Identity r -> case r of
83 Identity r -> case r of
84 InL backendRoute :=> Identity a -> serveRoute $ backendRoute :/ a
84 InL backendRoute :=> Identity a -> serveRoute $ backendRoute :/ a
85+ InR obeliskRoute :=> Identity a ->
85− InR obeliskRoute :=> Identity a -> serveDefaultObeliskApp serveStaticAsset frontend $ obeliskRoute :/ a
86+ serveDefaultObeliskApp (mkRouteToUrl validFullEncoder) serveStaticAsset frontend $ obeliskRoute :/ a
86 let conf = defRunConfig { _runConfig_redirectPort = port }
87 let conf = defRunConfig { _runConfig_redirectPort = port }
87 runWidget conf frontend validFullEncoder `finally` killThread backendTid
88 runWidget conf frontend validFullEncoder `finally` killThread backendTid
88
89
@@ -142,14 +143,14 @@ obeliskApp opts frontend validFullEncoder uri backend = do
142 { W.pathInfo = fst $ encode jsaddleWarpRouteValidEncoder jsaddleRoute
143 { W.pathInfo = fst $ encode jsaddleWarpRouteValidEncoder jsaddleRoute
143 }
144 }
144 InR (ObeliskRoute_App appRouteComponent) :=> Identity appRouteRest -> do
145 InR (ObeliskRoute_App appRouteComponent) :=> Identity appRouteRest -> do
145− html <- renderJsaddleFrontend (appRouteComponent :/ appRouteRest) frontend
146+ html <- renderJsaddleFrontend (mkRouteToUrl validFullEncoder) (appRouteComponent :/ appRouteRest) frontend
146 sendResponse $ W.responseLBS H.status200 [("Content-Type", staticRenderContentType)] $ BSLC.fromStrict html
147 sendResponse $ W.responseLBS H.status200 [("Content-Type", staticRenderContentType)] $ BSLC.fromStrict html
147 _ -> backend req sendResponse
148 _ -> backend req sendResponse
148
149
149−renderJsaddleFrontend :: route -> Frontend route -> IO ByteString
150+renderJsaddleFrontend :: (route -> Text) -> route -> Frontend route -> IO ByteString
150−renderJsaddleFrontend r f =
151+renderJsaddleFrontend urlEnc r f =
151 let jsaddleScript = elAttr "script" ("src" =: "/jsaddle/jsaddle.js") blank
152 let jsaddleScript = elAttr "script" ("src" =: "/jsaddle/jsaddle.js") blank
152− in renderFrontendHtml r (_frontend_head f >> injectExecutableConfigs) (_frontend_body f >> jsaddleScript)
153+ in renderFrontendHtml urlEnc r (_frontend_head f >> injectExecutableConfigs) (_frontend_body f >> jsaddleScript)
153
154
154-- | like 'bindPortTCP' but reconnects on exception
155-- | like 'bindPortTCP' but reconnects on exception
155bindPortTCPRetry :: Settings
156bindPortTCPRetry :: Settings