on master help sign in
Merge pull request #659 from obsidiansystems/release/0.6.0.0

Release/0.6.0.0

commitfc876df4ddc235e5c8f6d996d3275e3406bab82f authorElliot Cameron <3noch@users.noreply.github.com> dateThu 20 Feb 2020, 11:22 (7 years ago) parent57fd8557e79fe5
9 files changed+166−89
.github/pull_request_template.md+1−1 ChangeLog.md+12 lib/backend/src/Obelisk/Backend.hs+27−24 lib/command/obelisk-command.cabal+1−2 lib/command/src/Obelisk/Command/Project.hs+1−1 lib/command/src/Obelisk/Command/Run.hs+33−27 lib/route/src/Obelisk/Route/Frontend.hs+80−16 lib/run/src/Obelisk/Run.hs+11−6 lib/selftest/src/Obelisk/SelfTest.hs−12
.github/pull_request_template.md+1−1
@@ -1,4 +1,4 @@
1−[Provide a clear overview of your changes.]
1+<!-- Provide a clear overview of your changes. -->
22
33I have:
44
ChangeLog.md+12
@@ -2,6 +2,18 @@
22
33This project's release branch is `master`. This log is written from the perspective of the release branch: when changes hit `master`, they are considered released, and the date should reflect that release.
44
5+## v0.6.0.0 - 2020-02-21
6+
7+* Fix a bug in `Obelisk.Route.Frontend` where `routeLink`, `routeLinkDynAttr`, and `dynRouteLink` would not behave exactly like `<a href="...">` when run by JavaScript. These functions now scroll to the top of the page when the link is clicked. ([#540](https://github.com/obsidiansystems/obelisk/pull/540))
8+* Fix a bug in `ob run`/`ob watch`/`ob repl` where nested Obelisk projects would also be loaded into the session. These are now ignored. ([#652](https://github.com/obsidiansystems/obelisk/pull/652))
9+* Improve behavior of `ob run`/`ob watch`/`ob repl` when multiple packages with the same name are encountered. Obelisk now issues a warning and tells you which one it will use. ([#653](https://github.com/obsidiansystems/obelisk/pull/653))
10+* Removed `Obelisk.Backend.mkRouteToUrl` since it is easily written in terms of `Obelisk.Route.renderObeliskRoute`:
11+
12+ mkRouteToUrl validFullEncoder (k :/ v) = renderObeliskRoute validFullEncoder (FullRoute_Frontend (ObeliskRoute_App k) :/ v)
13+
14+* Add `Obelisk.Backend.renderAllJsPath` to expose URL path to `ghcjs/all.js`. ([#545](https://github.com/obsidiansystems/obelisk/pull/545))
15+* Add argument to `serveDefaultObeliskApp`, `serveObeliskApp`, and `serveGhcjsApp` to take the path to `all.js` instead of hard-coding it. ([#545](https://github.com/obsidiansystems/obelisk/pull/545))
16+
517## v0.5.0.0 - 2020-02-07
618
719* Add `Obelisk.Route.(?/)`, a convenience function for constructing routes nested in `Maybe`. ([#457](https://github.com/obsidiansystems/obelisk/pull/457))
lib/backend/src/Obelisk/Backend.hs+27−24
@@ -16,14 +16,12 @@ module Obelisk.Backend
1616 , runSnapWithCommandLineArgs
1717 , serveDefaultObeliskApp
1818 , prettifyOutput
19+ , renderAllJsPath
1920 , runBackend
2021 , staticRenderContentType
21− , mkRouteToUrl
2222 , getPublicConfigs
2323 ) where
2424
25−import Prelude hiding (id, (.))
26−import Control.Category
2725import Control.Monad
2826import Control.Monad.Except
2927import Control.Monad.Fail (MonadFail)
@@ -64,15 +62,15 @@ data GhcjsApp route = GhcjsApp
6462--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
6563serveDefaultObeliskApp
6664 :: (MonadSnap m, HasCookies m, MonadFail m)
67− => (R appRoute
68− -> Text)
69− -> ([Text]
70− -> m ())
65+ => (R appRoute -> Text)
66+ -> Text -- ^ URL to GHCJS app JavaScript
67+ -> ([Text] -> m ())
7168 -> Frontend (R appRoute)
7269 -> Map Text ByteString
7370 -> R (ObeliskRoute appRoute)
7471 -> m ()
75−serveDefaultObeliskApp urlEnc serveStaticAsset frontend configs = serveObeliskApp urlEnc serveStaticAsset frontendApp configs
72+serveDefaultObeliskApp urlEnc allJsUrl serveStaticAsset frontend =
73+ serveObeliskApp urlEnc allJsUrl serveStaticAsset frontendApp
7674 where frontendApp = GhcjsApp
7775 { _ghcjsApp_compiled = defaultFrontendGhcjsAssets
7876 , _ghcjsApp_value = frontend
@@ -119,19 +117,24 @@ getRouteWith e = do
119117 pageName <- getPageName
120118 return $ tryDecode e pageName
121119
120+renderAllJsPath :: Encoder Identity Identity (R (FullRoute a b)) PageName -> Text
121+renderAllJsPath validFullEncoder =
122+ renderObeliskRoute validFullEncoder $ FullRoute_Frontend (ObeliskRoute_Resource ResourceRoute_Ghcjs) :/ ["all.js"]
123+
122124serveObeliskApp
123125 :: (MonadSnap m, HasCookies m, MonadFail m)
124126 => (R appRoute -> Text)
127+ -> Text -- ^ URL to GHCJS app JavaScript
125128 -> ([Text] -> m ())
126129 -> GhcjsApp (R appRoute)
127130 -> Map Text ByteString
128131 -> R (ObeliskRoute appRoute)
129132 -> m ()
130−serveObeliskApp urlEnc serveStaticAsset frontendApp config = \case
131− ObeliskRoute_App appRouteComponent :=> Identity appRouteRest -> serveGhcjsApp urlEnc frontendApp config $ GhcjsAppRoute_App appRouteComponent :/ appRouteRest
133+serveObeliskApp urlEnc allJsUrl serveStaticAsset frontendApp config = \case
134+ ObeliskRoute_App appRouteComponent :=> Identity appRouteRest -> serveGhcjsApp urlEnc allJsUrl frontendApp config $ GhcjsAppRoute_App appRouteComponent :/ appRouteRest
132135 ObeliskRoute_Resource resComponent :=> Identity resRest -> case resComponent :=> Identity resRest of
133136 ResourceRoute_Static :=> Identity pathSegments -> serveStaticAsset pathSegments
134− ResourceRoute_Ghcjs :=> Identity pathSegments -> serveGhcjsApp urlEnc frontendApp config $ GhcjsAppRoute_Resource :/ pathSegments
137+ ResourceRoute_Ghcjs :=> Identity pathSegments -> serveGhcjsApp urlEnc allJsUrl frontendApp config $ GhcjsAppRoute_Resource :/ pathSegments
135138 ResourceRoute_JSaddleWarp :=> Identity _ -> do
136139 let msg = "Error: Obelisk.Backend received jsaddle request"
137140 liftIO $ putStrLn $ T.unpack msg
@@ -158,15 +161,16 @@ staticRenderContentType = "text/html; charset=utf-8"
158161serveGhcjsApp
159162 :: (MonadSnap m, HasCookies m, MonadFail m)
160163 => (R appRouteComponent -> Text)
164+ -> Text -- ^ URL to GHCJS app JavaScript
161165 -> GhcjsApp (R appRouteComponent)
162166 -> Map Text ByteString
163167 -> R (GhcjsAppRoute appRouteComponent)
164168 -> m ()
165−serveGhcjsApp urlEnc app config = \case
169+serveGhcjsApp urlEnc allJsUrl app config = \case
166170 GhcjsAppRoute_App appRouteComponent :=> Identity appRouteRest -> do
167171 modifyResponse $ setContentType staticRenderContentType
168172 modifyResponse $ setHeader "Cache-Control" "no-store private"
169− writeBS <=< renderGhcjsFrontend urlEnc (appRouteComponent :/ appRouteRest) config $ _ghcjsApp_value app
173+ writeBS <=< renderGhcjsFrontend urlEnc allJsUrl (appRouteComponent :/ appRouteRest) config $ _ghcjsApp_value app
170174 GhcjsAppRoute_Resource :=> Identity pathSegments -> serveStaticAssets (_ghcjsApp_compiled app) pathSegments
171175
172176runBackend :: Backend backendRoute frontendRoute -> Frontend (R frontendRoute) -> IO ()
@@ -174,30 +178,29 @@ runBackend backend frontend = case checkEncoder $ _backend_routeEncoder backend
174178 Left e -> fail $ "backend error:\n" <> T.unpack e
175179 Right validFullEncoder -> do
176180 publicConfigs <- getPublicConfigs
177− _backend_run backend $ \serveRoute -> do
178− runSnapWithCommandLineArgs $ do
181+ _backend_run backend $ \serveRoute ->
182+ runSnapWithCommandLineArgs $
179183 getRouteWith validFullEncoder >>= \case
180184 Identity r -> case r of
181185 FullRoute_Backend backendRoute :/ a -> serveRoute $ backendRoute :/ a
182186 FullRoute_Frontend obeliskRoute :/ a ->
183− serveDefaultObeliskApp (mkRouteToUrl validFullEncoder) (serveStaticAssets defaultStaticAssets) frontend publicConfigs $
187+ serveDefaultObeliskApp routeToUrl allJsUrl (serveStaticAssets defaultStaticAssets) frontend publicConfigs $
184188 obeliskRoute :/ a
185−
186−mkRouteToUrl :: Encoder Identity parse (R (FullRoute br fr)) PageName -> R fr -> Text
187−mkRouteToUrl validFullEncoder =
188− let pageNameEncoder' :: Encoder Identity (Either Text) PageName PathQuery = pageNameEncoder
189− in \(k :/ v) -> T.pack . uncurry (<>) . encode pageNameEncoder' . encode validFullEncoder $ (FullRoute_Frontend $ ObeliskRoute_App k) :/ v
189+ where
190+ routeToUrl (k :/ v) = renderObeliskRoute validFullEncoder $ FullRoute_Frontend (ObeliskRoute_App k) :/ v
191+ allJsUrl = renderAllJsPath validFullEncoder
190192
191193renderGhcjsFrontend
192194 :: (MonadSnap m, HasCookies m)
193195 => (route -> Text)
196+ -> Text -- ^ URL to GHCJS app JavaScript
194197 -> route
195198 -> Map Text ByteString
196199 -> Frontend route
197200 -> m ByteString
198−renderGhcjsFrontend urlEnc route configs f = do
199− let ghcjsPreload = elAttr "link" ("rel" =: "preload" <> "as" =: "script" <> "href" =: "ghcjs/all.js") blank
200− ghcjsScript = elAttr "script" ("language" =: "javascript" <> "src" =: "ghcjs/all.js" <> "defer" =: "defer") blank
201+renderGhcjsFrontend urlEnc allJsUrl route configs f = do
202+ let ghcjsPreload = elAttr "link" ("rel" =: "preload" <> "as" =: "script" <> "href" =: allJsUrl) blank
203+ ghcjsScript = elAttr "script" ("language" =: "javascript" <> "src" =: allJsUrl <> "defer" =: "defer") blank
201204 cookies <- askCookies
202205 renderFrontendHtml configs cookies urlEnc route f ghcjsPreload ghcjsScript
203206
lib/command/obelisk-command.cabal+1−2
@@ -1,5 +1,5 @@
11name: obelisk-command
2−version: 0.5
2+version: 0.6
33cabal-version: >= 1.8
44build-type: Simple
55
@@ -20,7 +20,6 @@ library
2020 , either
2121 , errors
2222 , exceptions
23− , extra
2423 , filepath
2524 , git
2625 , github
lib/command/src/Obelisk/Command/Project.hs+1−1
@@ -186,7 +186,7 @@ findProjectRoot target = do
186186 targetStat <- liftIO $ getFileStatus target
187187 umask <- liftIO getUmask
188188 (result, _) <- liftIO $ runStateT (walkToProjectRoot target targetStat umask myUid) []
189− return result
189+ return $ makeRelative "." <$> result
190190
191191withProjectRoot :: MonadObelisk m => FilePath -> (FilePath -> m a) -> m a
192192withProjectRoot target f = findProjectRoot target >>= \case
lib/command/src/Obelisk/Command/Run.hs+33−27
@@ -8,6 +8,7 @@ module Obelisk.Command.Run where
88
99import Control.Arrow ((&&&))
1010import Control.Exception (Exception, bracket)
11+import Control.Lens (ifor)
1112import Control.Monad (filterM, unless)
1213import Control.Monad.Except (runExceptT, throwError)
1314import Control.Monad.IO.Class (liftIO)
@@ -15,7 +16,6 @@ import Control.Monad.Reader (MonadIO)
1516import Data.Coerce (coerce)
1617import Data.Either
1718import Data.Foldable (for_, toList)
18−import Data.List.Extra (dropPrefix)
1919import qualified Data.List.NonEmpty as NE
2020import Data.Map (Map)
2121import qualified Data.Map as Map
@@ -47,8 +47,8 @@ import qualified System.Info
4747import System.IO.Temp (withSystemTempDirectory)
4848
4949import Obelisk.App (MonadObelisk)
50−import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode, readProcessAndLogStderr)
51−import Obelisk.Command.Project (obeliskDirName, toObeliskDir, withProjectRoot, nixShellWithPkgs, toNixPath)
50+import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode, readProcessAndLogStderr, setCwd)
51+import Obelisk.Command.Project (nixShellWithPkgs, toImplDir, withProjectRoot)
5252import Obelisk.Command.Thunk (attrCacheFileName)
5353import Obelisk.Command.Utils (findExePath, ghcidExePath, nixBuildExePath, nixExePath)
5454
@@ -75,29 +75,26 @@ run :: MonadObelisk m => m ()
7575run = withProjectRoot "." $ \root -> do
7676 pkgs <- fmap toList . parsePackagesOrFail =<< getLocalPkgs root
7777 withGhciScript pkgs root $ \dotGhciPath -> do
78− freePort <- getFreePort
7978 assets <- do
80− let importableRoot = toNixPath root
81− isDerivation <- readProcessAndLogStderr Debug $
79+ isDerivation <- readProcessAndLogStderr Debug $ setCwd (Just root) $
8280 proc nixExePath
8381 [ "eval"
84− , "-f"
85− , root
86− , "(let a = import " <> importableRoot <> " {}; in toString (a.reflex.nixpkgs.lib.isDerivation a.passthru.staticFilesImpure))"
82+ , "(let a = import ./. {}; in toString (a.reflex.nixpkgs.lib.isDerivation a.passthru.staticFilesImpure))"
8783 , "--raw"
8884 -- `--raw` is not available with old nix-instantiate. It drops quotation
8985 -- marks and trailing newline, so is very convenient for shelling out.
9086 ]
9187 -- Check whether the impure static files are a derivation (and so must be built)
9288 if isDerivation == "1"
93− then fmap T.strip $ readProcessAndLogStderr Debug $ -- Strip whitespace here because nix-build has no --raw option
89+ then fmap T.strip $ readProcessAndLogStderr Debug $ setCwd (Just root) $ -- Strip whitespace here because nix-build has no --raw option
9490 proc nixBuildExePath
9591 [ "--no-out-link"
96− , "-E", "(import " <> importableRoot <> "{}).passthru.staticFilesImpure"
92+ , "-E", "(import ./. {}).passthru.staticFilesImpure"
9793 ]
98− else readProcessAndLogStderr Debug $
99− proc nixExePath ["eval", "-f", root, "passthru.staticFilesImpure", "--raw"]
94+ else readProcessAndLogStderr Debug $ setCwd (Just root) $
95+ proc nixExePath ["eval", "-f", ".", "passthru.staticFilesImpure", "--raw"]
10096 putLog Debug $ "Assets impurely loaded from: " <> assets
97+ freePort <- getFreePort
10198 runGhcid root True dotGhciPath pkgs $ Just $ unwords
10299 [ "Obelisk.Run.run"
103100 , show freePort
@@ -125,20 +122,19 @@ runWatch = withProjectRoot "." $ \root -> do
125122-- to the Nix @project@ function.
126123getLocalPkgs :: MonadObelisk m => FilePath -> m [FilePath]
127124getLocalPkgs root = do
128− (_exitCode, out, err) <- readCreateProcessWithExitCode $
129− proc findExePath ["-L", root, "(", "-name", "*.cabal", "-o", "-name", Hpack.packageConfig, ")", "-a", "-type", "f"]
130− putLog Debug $ T.strip $ T.pack err
131−
132− let
133− -- We ignore any path that has ".obelisk" in it, but keep the root ".obelisk" paths
134− packagePaths = filter (not . isIgnored) $ map T.unpack $ T.lines $ T.strip $ T.pack out
135− isIgnored path =
136− obeliskDirName `elem` dropPrefix (splitPath $ toObeliskDir root) pathSegments
137− && attrCacheFileName `notElem` pathSegments
138− where
139− pathSegments = splitPath path
125+ obeliskPaths <- runFind ["-L", root, "-name", ".obelisk", "-type", "d"]
140126
141− pure packagePaths
127+ -- We do not want to find packages that are embedded inside other obelisk projects, unless that
128+ -- obelisk project is our own.
129+ let exclusions = filter (/= root) $ map takeDirectory obeliskPaths
130+ fmap (map (makeRelative ".")) $ runFind $
131+ ["-L", root, "(", "-name", "*.cabal", "-o", "-name", Hpack.packageConfig, ")", "-a", "-type", "f"]
132+ <> concat [["-not", "-path", p </> "*"] | p <- (toImplDir "*" </> attrCacheFileName) : exclusions]
133+ where
134+ runFind args = do
135+ (_exitCode, out, err) <- readCreateProcessWithExitCode $ proc findExePath args
136+ putLog Debug $ T.strip $ T.pack err
137+ pure $ map T.unpack $ T.lines $ T.strip $ T.pack out
142138
143139data GuessPackageFileError = GuessPackageFileError_Ambiguous [FilePath] | GuessPackageFileError_NotFound
144140 deriving (Eq, Ord, Show)
@@ -248,7 +244,17 @@ parsePackagesOrFail dirs = do
248244 | _cabalPackageInfo_buildable packageInfo -> Right packageInfo
249245 _ -> Left dir
250246
251− packageInfos <- case NE.nonEmpty packageInfos' of
247+ let packagesByName = Map.fromListWith (<>) [(_cabalPackageInfo_packageName p, p NE.:| []) | p <- packageInfos']
248+ unambiguous <- ifor packagesByName $ \packageName ps -> case ps of
249+ p NE.:| [] -> pure p -- No ambiguity here
250+ p NE.:| _ -> do
251+ putLog Warning $ T.pack $
252+ "Packages named '" <> T.unpack packageName <> "' appear in " <> show (length ps) <> " different locations: "
253+ <> intercalate ", " (map _cabalPackageInfo_packageFile $ toList ps)
254+ <> "; Picking " <> _cabalPackageInfo_packageFile p
255+ pure p
256+
257+ packageInfos <- case NE.nonEmpty $ toList unambiguous of
252258 Nothing -> failWith $ T.pack $ "No valid, buildable packages found in " <> intercalate ", " dirs
253259 Just xs -> pure xs
254260
lib/route/src/Obelisk/Route/Frontend.hs+80−16
@@ -7,15 +7,11 @@
77{-# LANGUAGE FlexibleContexts #-}
88{-# LANGUAGE FlexibleInstances #-}
99{-# LANGUAGE GADTs #-}
10−{-# LANGUAGE KindSignatures #-}
1110{-# LANGUAGE LambdaCase #-}
12−{-# LANGUAGE MultiParamTypeClasses #-}
1311{-# LANGUAGE OverloadedStrings #-}
1412{-# LANGUAGE PatternSynonyms #-}
1513{-# LANGUAGE RankNTypes #-}
1614{-# LANGUAGE ScopedTypeVariables #-}
17−{-# LANGUAGE TemplateHaskell #-}
18−{-# LANGUAGE TupleSections #-}
1915{-# LANGUAGE TypeFamilies #-}
2016{-# LANGUAGE TypeOperators #-}
2117{-# LANGUAGE ViewPatterns #-}
@@ -64,7 +60,7 @@ import Prelude hiding ((.), id)
6460import Obelisk.Route
6561
6662import Control.Category (Category (..), (.))
67−import Control.Category.Cartesian
63+import Control.Category.Cartesian ((&&&))
6864import Control.Lens hiding (Bifunctor, bimap, universe, element)
6965import Control.Monad ((<=<))
7066import Control.Monad.Fix
@@ -94,7 +90,9 @@ import Reflex.Dom.Builder.Class
9490import Data.Type.Coercion
9591import Language.Javascript.JSaddle --TODO: Get rid of this - other platforms can also be routed
9692import Reflex.Dom.Core
93+import qualified GHCJS.DOM as DOM
9794import qualified GHCJS.DOM.Types as DOM
95+import qualified GHCJS.DOM.Window as Window
9896import Network.URI
9997import Data.Maybe (fromMaybe)
10098import qualified Data.List as L
@@ -511,59 +509,125 @@ runRouteViewT routeEncoder switchover useHash a = do
511509-- | A link widget that, when clicked, sets the route to the provided route. In non-javascript
512510-- contexts, this widget falls back to using @href@s to control navigation
513511routeLink
514− :: forall t m a route.
512+ :: forall t m a route js.
515513 ( DomBuilder t m
516514 , RouteToUrl route m
517515 , SetRoute t route m
516+ , Prerender js t m
518517 )
519518 => route -- ^ Target route
520519 -> m a -- ^ Child widget
521520 -> m a
522521routeLink r w = do
522+ (e, a) <- routeLinkImpl r w
523+ scrollToTop e
524+ return a
525+
526+-- | Raw implementation of 'routeLink'. Does not scroll to the top of the page on clicks.
527+routeLinkImpl
528+ :: forall t m a route.
529+ ( DomBuilder t m
530+ , RouteToUrl route m
531+ , SetRoute t route m
532+ )
533+ => route -- ^ Target route
534+ -> m a -- ^ Child widget
535+ -> m (Event t (), a)
536+routeLinkImpl r w = do
523537 enc <- askRouteToUrl
524538 let cfg = (def :: ElementConfig EventResult t (DomBuilderSpace m))
525539 & elementConfig_eventSpec %~ addEventSpecFlags (Proxy :: Proxy (DomBuilderSpace m)) Click (\_ -> preventDefault)
526540 & elementConfig_initialAttributes .~ "href" =: enc r
527541 (e, a) <- element "a" cfg w
528542 setRoute $ r <$ domEvent Click e
529− return a
543+ return (domEvent Click e, a)
544+
545+scrollToTop :: forall m t js. (Prerender js t m, Monad m) => Event t () -> m ()
546+scrollToTop e = prerender_ blank $ performEvent_ $ ffor e $ \_ -> liftJSM $ DOM.currentWindow >>= \case
547+ Nothing -> pure ()
548+ Just win -> Window.scrollTo win 0 0
530549
531550-- | Like 'routeLinkDynAttr' but without custom attributes.
532551dynRouteLink
533− :: forall t m a route.
552+ :: forall t m a route js.
534553 ( DomBuilder t m
535554 , PostBuild t m
536− , RouteToUrl (R route) m
537− , SetRoute t (R route) m
555+ , RouteToUrl route m
556+ , SetRoute t route m
557+ , Prerender js t m
538558 )
539− => Dynamic t (R route) -- ^ Target route
540− -> m a -- ^ Child widget of the @a@ element
559+ => Dynamic t route -- ^ Target route
560+ -> m a -- ^ Child widget
541561 -> m a
542−dynRouteLink = routeLinkDynAttr mempty
562+dynRouteLink r w = do
563+ (e, a) <- dynRouteLinkImpl r w
564+ scrollToTop e
565+ return a
566+
567+-- | Raw implementation of 'dynRouteLink'. Does not scroll to the top of the page on clicks.
568+dynRouteLinkImpl
569+ :: forall t m a route.
570+ ( DomBuilder t m
571+ , PostBuild t m
572+ , RouteToUrl route m
573+ , SetRoute t route m
574+ )
575+ => Dynamic t route -- ^ Target route
576+ -> m a -- ^ Child widget
577+ -> m (Event t (), a)
578+dynRouteLinkImpl dr w = do
579+ enc <- askRouteToUrl
580+ er <- dynamicAttributesToModifyAttributes $ ("href" =:) . enc <$> dr
581+ let cfg = (def :: ElementConfig EventResult t (DomBuilderSpace m))
582+ & elementConfig_eventSpec %~ addEventSpecFlags (Proxy :: Proxy (DomBuilderSpace m)) Click (\_ -> preventDefault)
583+ & elementConfig_modifyAttributes .~ er
584+ (e, a) <- element "a" cfg w
585+ let clk = domEvent Click e
586+ setRoute $ tag (current dr) clk
587+ return (clk, a)
543588
544589-- | An @a@-tag link widget that, when clicked, sets the route to current value of the
545590-- provided dynamic route. In non-JavaScript contexts the value of the dynamic post
546591-- build is used so the link still works like 'routeLink'.
547592routeLinkDynAttr
548− :: forall t m a route.
593+ :: forall t m a route js.
549594 ( DomBuilder t m
550595 , PostBuild t m
551596 , RouteToUrl (R route) m
552597 , SetRoute t (R route) m
598+ , Prerender js t m
553599 )
554600 => Dynamic t (Map AttributeName Text) -- ^ Attributes for @a@ element. Note that if @href@ is present it will be ignored
555601 -> Dynamic t (R route) -- ^ Target route
556602 -> m a -- ^ Child widget of the @a@ element
557603 -> m a
558604routeLinkDynAttr dAttr dr w = do
605+ (e, a) <- routeLinkDynAttrImpl dAttr dr w
606+ scrollToTop e
607+ return a
608+
609+-- | Raw implementation of 'routeLinkDynAttr'. Does not scroll to the top of the page on clicks.
610+routeLinkDynAttrImpl
611+ :: forall t m a route.
612+ ( DomBuilder t m
613+ , PostBuild t m
614+ , RouteToUrl (R route) m
615+ , SetRoute t (R route) m
616+ )
617+ => Dynamic t (Map AttributeName Text) -- ^ Attributes for @a@ element. Note that if @href@ is present it will be ignored
618+ -> Dynamic t (R route) -- ^ Target route
619+ -> m a -- ^ Child widget of the @a@ element
620+ -> m (Event t (), a)
621+routeLinkDynAttrImpl dAttr dr w = do
559622 enc <- askRouteToUrl
560623 er <- dynamicAttributesToModifyAttributes $ zipDynWith (<>) (("href" =:) . enc <$> dr) dAttr
561624 let cfg = (def :: ElementConfig EventResult t (DomBuilderSpace m))
562625 & elementConfig_eventSpec %~ addEventSpecFlags (Proxy :: Proxy (DomBuilderSpace m)) Click (\_ -> preventDefault)
563626 & elementConfig_modifyAttributes .~ er
564627 (e, a) <- element "a" cfg w
565− setRoute $ tag (current dr) $ domEvent Click e
566− return a
628+ let clk = domEvent Click e
629+ setRoute $ tag (current dr) clk
630+ return (clk, a)
567631
568632-- On ios due to sandboxing when loading the page from a file adapt the
569633-- path to be based on the hash.
lib/run/src/Obelisk/Run.hs+11−6
@@ -79,7 +79,7 @@ import qualified System.Which
7979run
8080 :: Int -- ^ Port to run the backend
8181 -> ([Text] -> Snap ()) -- ^ Static asset handler
82− -> Backend fullRoute frontendRoute -- ^ Backend
82+ -> Backend backendRoute frontendRoute -- ^ Backend
8383 -> Frontend (R frontendRoute) -- ^ Frontend
8484 -> IO ()
8585run port serveStaticAsset backend frontend = do
@@ -90,14 +90,18 @@ run port serveStaticAsset backend frontend = do
9090 Left e -> hPutStrLn stderr $ "backend error:\n" <> T.unpack e
9191 Right validFullEncoder -> do
9292 publicConfigs <- getPublicConfigs
93− backendTid <- forkIO $ handle handleBackendErr $ withArgs ["--quiet", "--port", show port] $ do
94− _backend_run backend $ \serveRoute -> do
95− runSnapWithCommandLineArgs $ do
93+ backendTid <- forkIO $ handle handleBackendErr $ withArgs ["--quiet", "--port", show port] $
94+ _backend_run backend $ \serveRoute ->
95+ runSnapWithCommandLineArgs $
9696 getRouteWith validFullEncoder >>= \case
9797 Identity r -> case r of
9898 FullRoute_Backend backendRoute :/ a -> serveRoute $ backendRoute :/ a
9999 FullRoute_Frontend obeliskRoute :/ a ->
100− serveDefaultObeliskApp (mkRouteToUrl validFullEncoder) serveStaticAsset frontend publicConfigs $ obeliskRoute :/ a
100+ serveDefaultObeliskApp appRouteToUrl allJsUrl serveStaticAsset frontend publicConfigs $ obeliskRoute :/ a
101+ where
102+ appRouteToUrl (k :/ v) = renderObeliskRoute validFullEncoder (FullRoute_Frontend (ObeliskRoute_App k) :/ v)
103+ allJsUrl = renderAllJsPath validFullEncoder
104+
101105 let conf = defRunConfig { _runConfig_redirectPort = port }
102106 runWidget conf publicConfigs frontend validFullEncoder `finally` killThread backendTid
103107
@@ -189,7 +193,8 @@ obeliskApp configs opts frontend validFullEncoder uri backend = do
189193 }
190194 FullRoute_Frontend (ObeliskRoute_App appRouteComponent) :/ appRouteRest -> do
191195 let cookies = maybe [] parseCookies $ lookup (fromString "Cookie") (W.requestHeaders req)
192− html <- renderJsaddleFrontend configs cookies (mkRouteToUrl validFullEncoder) (appRouteComponent :/ appRouteRest) frontend
196+ routeToUrl (k :/ v) = renderObeliskRoute validFullEncoder $ FullRoute_Frontend (ObeliskRoute_App k) :/ v
197+ html <- renderJsaddleFrontend configs cookies routeToUrl (appRouteComponent :/ appRouteRest) frontend
193198 sendResponse $ W.responseLBS H.status200 [("Content-Type", staticRenderContentType)] $ BSLC.fromStrict html
194199 _ -> backend req sendResponse
195200
lib/selftest/src/Obelisk/SelfTest.hs−12
@@ -27,7 +27,6 @@ import Shelly hiding (cp)
2727import System.Directory (getCurrentDirectory, withCurrentDirectory, getDirectoryContents)
2828import System.Environment
2929import System.Exit (ExitCode (..))
30−import System.FilePath (replaceBaseName, takeBaseName)
3130import System.Which (staticWhich)
3231import qualified System.Info
3332import System.IO (Handle, hClose, hIsEOF, hGetContents)
@@ -217,9 +216,6 @@ main = do
217216 describe "ob run" $ {- NOT parallel $ -} do
218217 it "works in root directory" $ inTmpObInit $ \_ -> testObRunInDir' Nothing httpManager
219218 it "works in sub directory" $ inTmpObInit $ \_ -> testObRunInDir' (Just "frontend") httpManager
220− it "works with differently named cabal files" $ inTmpObInit $ \_ -> do
221− changeCabalPackageName "backend/backend.cabal" "new-backend"
222− testObRunInDir' Nothing httpManager
223219
224220 describe "obelisk project" $ parallel $ do
225221 it "can build obelisk command" $ inTmpObInit $ \_ -> nixBuild ["-A", "command" , toTextIgnore obeliskImplDirty]
@@ -426,14 +422,6 @@ obRunCheck httpManager _stdout frontendUri = do
426422 req frontendUri >>= \r -> when (HTTP.responseStatus r /= HTTP.ok200) $ errorExit $
427423 "Request to frontend server failed: " <> T.pack (show r)
428424
429−-- | Rename a cabal file and do a really dumb, brittle search/replace in its content to update the name.
430−changeCabalPackageName :: FilePath -> Text -> Sh ()
431−changeCabalPackageName cabalFile newName = do
432− contents <- readfile cabalFile
433− writefile (replaceBaseName cabalFile (T.unpack newName)) $
434− T.replace (" " <> T.pack (takeBaseName cabalFile)) (" " <> newName) contents -- WARNING: Super brittle
435− rm cabalFile
436−
437425getFreePorts :: Int -> IO [Socket.PortNumber]
438426getFreePorts 0 = pure []
439427getFreePorts n = Socket.withSocketsDo $ do