@@ -27,16 +27,27 @@ import Control.Monad.IO.Class
2727import Control.Monad.Primitive
2828import Control.Monad.Reader
2929import Control.Monad.Ref
30+import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT)
3031import Data.ByteString (ByteString)
32+import Data.Foldable (for_)
3133import Data.Functor.Sum
34+import qualified Data.Map as Map
35+import Data.Maybe (catMaybes, isJust)
3236import Data.Monoid ((<>))
3337import Data.Text (Text)
38+import GHCJS.DOM (currentDocument)
39+import GHCJS.DOM.Document (getHead)
40+import GHCJS.DOM.Element (getAttribute, getElementsByTagName)
41+import GHCJS.DOM.HTMLCollection (item, getLength)
42+import GHCJS.DOM.Node (removeChild_)
3443import Language.Javascript.JSaddle (JSM)
44+import Obelisk.Frontend.Config
3545import Obelisk.Frontend.Cookie
3646import Obelisk.Route.Frontend
3747import Reflex.Dom.Core
3848import Reflex.Host.Class
3949import Obelisk.ExecutableConfig.Inject (injectExecutableConfigs)
50+import Obelisk.ExecutableConfig.Internal (getFrontendConfigs)
4051import Web.Cookie
4152
4253makePrisms ''Sum
@@ -60,6 +71,7 @@ type ObeliskWidget js t route m =
6071 , Prerender js t m
6172 , PrebuildAgnostic t route m
6273 , PrebuildAgnostic t route (Client m)
74+ , HasConfigs m
6375 , HasCookies m
6476 -- TODO Remove these. Probably requires a new class to allow executable-configs to work without being inside a `prerender`
6577 , MonadIO m
@@ -77,6 +89,23 @@ data Frontend route = Frontend
7789 , _frontend_body :: !(forall js t m. ObeliskWidget js t route m => RoutedT t route m ())
7890 }
7991
92+baseTag :: forall route js t m. ObeliskWidget js t route m => RoutedT t route m ()
93+baseTag = elAttr "base" ("href" =: "/") blank --TODO: Figure out the base URL from the routes
94+
95+removeHTMLConfigs :: JSM ()
96+removeHTMLConfigs = void $ runMaybeT $ do
97+ doc <- MaybeT currentDocument
98+ hd <- MaybeT $ getHead doc
99+ es <- collToList =<< getElementsByTagName hd ("script" :: Text)
100+ for_ es $ \e -> do
101+ isConfig <- (isJust :: Maybe Text -> Bool) <$> getAttribute e ("data-executable-config-inject" :: Text)
102+ when isConfig $ removeChild_ hd e
103+ where
104+ collToList es = do
105+ len <- getLength es
106+ lst <- traverse (item es) $ take (fromIntegral len) $ [0..] -- fun with unsigned types ...
107+ pure $ catMaybes lst
108+
80109runFrontend
81110 :: forall backendRoute route
82111 . Encoder Identity Identity (R (Sum backendRoute (ObeliskRoute route))) PageName
@@ -87,14 +116,18 @@ runFrontend validFullEncoder frontend = do
87116 errorLeft = \case
88117 Left _ -> error "runFrontend: Unexpected non-app ObeliskRoute reached the frontend. This shouldn't happen."
89118 Right x -> Identity x
119+ configs <- liftIO $ Map.fromList <$> getFrontendConfigs
120+ removeHTMLConfigs
90121 runHydrationWidgetWithHeadAndBody (pure ()) $ \appendHead appendBody -> do
91122 rec switchover <- runRouteViewT ve switchover $ do
92123 (switchover'', fire) <- newTriggerEvent
93− mapRoutedT (mapSetRouteT (mapRouteToUrlT appendHead)) $ do
124+ mapRoutedT (mapSetRouteT (mapRouteToUrlT (appendHead . runConfigsT configs))) $ do
125+ -- The order here is important - baseTag has to be before headWidget!
126+ baseTag
94127 _frontend_head frontend
95− mapRoutedT (mapSetRouteT (mapRouteToUrlT appendBody)) $ do
128+ mapRoutedT (mapSetRouteT (mapRouteToUrlT (appendBody . runConfigsT configs))) $ do
96129 _frontend_body frontend
97− switchover' <- lift $ lift $ lift $ HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_switchover
130+ switchover' <- lift $ lift $ lift $ lift $ HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_switchover
98131 performEvent_ $ liftIO (fire ()) <$ switchover'
99132 pure switchover''
100133 pure ()
@@ -102,21 +135,26 @@ runFrontend validFullEncoder frontend = do
102135renderFrontendHtml
103136 :: ( t ~ DomTimeline
104137 , MonadIO m
105− , widget ~ RoutedT t r (SetRouteT t r' (RouteToUrlT r' (CookiesT (PostBuildT t (StaticDomBuilderT t (PerformEventT t DomHost))))))
138+ , widget ~ RoutedT t r (SetRouteT t r (RouteToUrlT r (ConfigsT (CookiesT (PostBuildT t (StaticDomBuilderT t (PerformEventT t DomHost)))))))
106139 )
107140 => Cookies
108− -> (r' -> Text)
141+ -> (r -> Text)
109142 -> r
143+ -> Frontend r
110144 -> widget ()
111145 -> widget ()
112146 -> m ByteString
113−renderFrontendHtml cookies urlEnc route headWidget bodyWidget = do
147+renderFrontendHtml cookies urlEnc route frontend headExtra bodyExtra = do
114148 --TODO: We should probably have a "NullEventWriterT" or a frozen reflex timeline
115− html <- fmap snd $ liftIO $ renderStatic $ fmap fst $ runCookiesT cookies $ flip runRouteToUrlT urlEnc $ runSetRouteT $ flip runRoutedT (pure route) $
149+ configs <- liftIO $ Map.fromList <$> getFrontendConfigs
150+ html <- fmap snd $ liftIO $ renderStatic $ fmap fst $ runCookiesT cookies $ runConfigsT configs $ flip runRouteToUrlT urlEnc $ runSetRouteT $ flip runRoutedT (pure route) $
116151 el "html" $ do
117152 el "head" $ do
118− let baseTag = elAttr "base" ("href" =: "/") blank --TODO: Figure out the base URL from the routes
119− -- The order here is important - baseTag has to be before headWidget!
120− baseTag >> injectExecutableConfigs >> headWidget
121− el "body" bodyWidget
153+ baseTag
154+ injectExecutableConfigs
155+ _frontend_head frontend
156+ headExtra
157+ el "body" $ do
158+ _frontend_body frontend
159+ bodyExtra
122160 return $ "<!DOCTYPE html>" <> html