Logout
+$nothing
+
+ Login
+|]
+
+instance Yesod BID where
+ approot _ = "http://localhost:3000"
+
+instance YesodAuth BID where
+ type AuthId BID = Text
+ loginDest _ = AfterLoginR
+ logoutDest _ = AuthR LoginR
+ getAuthId = return . Just . credsIdent
+ authPlugins = [authOpenId]
+
+instance RenderMessage BID FormMessage where
+ renderMessage _ _ = defaultFormMessage
+
+main :: IO ()
+main = toWaiApp BID >>= run 3000
+
diff --git a/yesod-auth/yesod-auth.cabal b/yesod-auth/yesod-auth.cabal
index 331da47b..8f3d1e67 100644
--- a/yesod-auth/yesod-auth.cabal
+++ b/yesod-auth/yesod-auth.cabal
@@ -1,5 +1,5 @@
name: yesod-auth
-version: 0.7.3
+version: 0.7.8
license: BSD3
license-file: LICENSE
author: Michael Snoyman, Patrick Brisbin
@@ -17,33 +17,34 @@ flag ghc7
library
if flag(ghc7)
- build-depends: base >= 4.3 && < 5
+ build-depends: base >= 4.3 && < 5
cpp-options: -DGHC7
else
- build-depends: base >= 4 && < 4.3
- build-depends: authenticate >= 0.10 && < 0.11
+ build-depends: base >= 4 && < 4.3
+ build-depends: authenticate >= 0.10.4 && < 0.11
, bytestring >= 0.9.1.4 && < 0.10
- , yesod-core >= 0.9 && < 0.10
+ , yesod-core >= 0.9.3.4 && < 0.10
, wai >= 0.4 && < 0.5
, template-haskell
- , pureMD5 >= 1.1 && < 2.2
- , random >= 1.0 && < 1.1
+ , pureMD5 >= 2.0 && < 2.2
+ , random >= 1.0.0.2 && < 1.1
, control-monad-attempt >= 0.3.0 && < 0.4
, text >= 0.7 && < 0.12
- , mime-mail >= 0.3 && < 0.4
- , blaze-html >= 0.4 && < 0.5
+ , mime-mail >= 0.3 && < 0.5
+ , blaze-html >= 0.4.1.3 && < 0.5
, yesod-persistent >= 0.2 && < 0.3
, hamlet >= 0.10 && < 0.11
, shakespeare-css >= 0.10 && < 0.11
, yesod-json >= 0.2 && < 0.3
- , containers >= 0.2 && < 0.5
+ , containers
+ , unordered-containers
, yesod-form >= 0.3 && < 0.4
- , transformers >= 0.2 && < 0.3
+ , transformers >= 0.2.2 && < 0.3
, persistent >= 0.6 && < 0.7
, persistent-template >= 0.6 && < 0.7
, SHA >= 1.4.1.3 && < 1.6
, http-enumerator >= 0.6 && < 0.8
- , aeson-native >= 0.3.2.11 && < 0.4
+ , aeson >= 0.3
, pwstore-fast >= 2.2 && < 3
exposed-modules: Yesod.Auth
@@ -57,9 +58,10 @@ library
Yesod.Auth.HashDB
Yesod.Auth.Message
Yesod.Auth.Kerberos
+ Yesod.Auth.GoogleEmail
ghc-options: -Wall
include-dirs: include
source-repository head
type: git
- location: git://github.com/snoyberg/yesod-auth.git
+ location: git://github.com/yesodweb/yesod.git
diff --git a/yesod-core/Yesod/Config.hs b/yesod-core/Yesod/Config.hs
new file mode 100644
index 00000000..e7bd4a11
--- /dev/null
+++ b/yesod-core/Yesod/Config.hs
@@ -0,0 +1,113 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Yesod.Config
+ {-# DEPRECATED "This code has been moved to yesod-default. This module will be removed in the next major version bump." #-}
+ ( AppConfig(..)
+ , loadConfig
+ , withYamlEnvironment
+ ) where
+
+import Control.Monad (join)
+import Data.Maybe (fromMaybe)
+import Data.Object
+import Data.Object.Yaml
+
+import Data.Text (Text)
+import qualified Data.Text as T
+
+-- | Dynamic per-environment configuration which can be loaded at
+-- run-time negating the need to recompile between environments.
+data AppConfig e = AppConfig
+ { appEnv :: e
+ , appPort :: Int
+ , appRoot :: Text
+ } deriving (Show)
+
+-- | Load an @'AppConfig'@ from @config\/settings.yml@.
+--
+-- Some examples:
+--
+-- > -- typical local development
+-- > Development:
+-- > host: localhost
+-- > port: 3000
+-- >
+-- > -- ssl: will default false
+-- > -- approot: will default to "http://localhost:3000"
+--
+-- > -- typical outward-facing production box
+-- > Production:
+-- > host: www.example.com
+-- >
+-- > -- ssl: will default false
+-- > -- port: will default 80
+-- > -- approot: will default "http://www.example.com"
+--
+-- > -- maybe you're reverse proxying connections to the running app
+-- > -- on some other port
+-- > Production:
+-- > port: 8080
+-- > approot: "http://example.com"
+-- >
+-- > -- approot is specified so that the non-80 port is not appended
+-- > -- automatically.
+--
+loadConfig :: Show e => e -> IO (AppConfig e)
+loadConfig env = withYamlEnvironment "config/settings.yml" env $ \e' -> do
+ e <- maybe (fail "Expected map") return $ fromMapping e'
+ let mssl = lookupScalar "ssl" e
+ let mhost = lookupScalar "host" e
+ let mport = lookupScalar "port" e
+ let mapproot = lookupScalar "approot" e
+
+ -- set some default arguments
+ let ssl = maybe False toBool mssl
+ port <- safeRead "port" $ fromMaybe (if ssl then "443" else "80") mport
+
+ approot <- case (mhost, mapproot) of
+ (_ , Just ar) -> return ar
+ (Just host, _ ) -> return $ T.concat
+ [ if ssl then "https://" else "http://"
+ , host
+ , addPort ssl port
+ ]
+ _ -> fail "You must supply either a host or approot"
+
+ return $ AppConfig
+ { appEnv = env
+ , appPort = port
+ , appRoot = approot
+ }
+
+ where
+ toBool :: Text -> Bool
+ toBool = (`elem` ["true", "TRUE", "yes", "YES", "Y", "1"])
+
+ addPort :: Bool -> Int -> Text
+ addPort True 443 = ""
+ addPort False 80 = ""
+ addPort _ p = T.pack $ ':' : show p
+
+-- | Loads the configuration block in the passed file named by the
+-- passed environment, yeilds to the passed function as a mapping.
+--
+-- Errors in the case of a bad load or if your function returns
+-- @Nothing@.
+withYamlEnvironment :: Show e
+ => FilePath -- ^ the yaml file
+ -> e -- ^ the environment you want to load
+ -> (TextObject -> IO a) -- ^ what to do with the mapping
+ -> IO a
+withYamlEnvironment fp env f = do
+ obj <- join $ decodeFile fp
+ envs <- fromMapping obj
+ conf <- maybe (fail $ "Could not find environment: " ++ show env) return
+ $ lookup (T.pack $ show env) envs
+ f conf
+
+-- | Returns 'fail' if read fails
+safeRead :: Monad m => String -> Text -> m Int
+safeRead name t = case reads s of
+ (i, _):_ -> return i
+ [] -> fail $ concat ["Invalid value for ", name, ": ", s]
+ where
+ s = T.unpack t
diff --git a/yesod-core/Yesod/Core.hs b/yesod-core/Yesod/Core.hs
index 9f137991..4a78bec7 100644
--- a/yesod-core/Yesod/Core.hs
+++ b/yesod-core/Yesod/Core.hs
@@ -1,4 +1,5 @@
{-# LANGUAGE TemplateHaskell #-}
+{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
module Yesod.Core
( -- * Type classes
Yesod (..)
@@ -33,6 +34,7 @@ module Yesod.Core
, module Yesod.Request
, module Yesod.Widget
, module Yesod.Message
+ , module Yesod.Config
) where
import Yesod.Internal.Core
@@ -42,6 +44,7 @@ import Yesod.Handler
import Yesod.Request
import Yesod.Widget
import Yesod.Message
+import Yesod.Config
import Language.Haskell.TH.Syntax
import Data.Text (Text)
diff --git a/yesod-core/Yesod/Dispatch.hs b/yesod-core/Yesod/Dispatch.hs
index 91147cb8..abe49064 100644
--- a/yesod-core/Yesod/Dispatch.hs
+++ b/yesod-core/Yesod/Dispatch.hs
@@ -4,7 +4,9 @@
module Yesod.Dispatch
( -- * Quasi-quoted routing
parseRoutes
+ , parseRoutesNoCheck
, parseRoutesFile
+ , parseRoutesFileNoCheck
, mkYesod
, mkYesodSub
-- ** More fine-grained
@@ -30,11 +32,10 @@ import Yesod.Internal.Dispatch
import Yesod.Widget (GWidget)
import Web.PathPieces (SinglePiece (..), MultiPiece (..))
-import Yesod.Internal.RouteParsing (THResource, Pieces (..), createRoutes, createRender, Resource (..), parseRoutes, parseRoutesFile)
+import Yesod.Internal.RouteParsing (THResource, Pieces (..), createRoutes, createRender, Resource (..), parseRoutes, parseRoutesNoCheck, parseRoutesFile, parseRoutesFileNoCheck)
import Language.Haskell.TH.Syntax
import qualified Network.Wai as W
-import Network.Wai.Middleware.Jsonp
import Network.Wai.Middleware.Gzip
import Network.Wai.Middleware.Autohead
@@ -172,11 +173,11 @@ thResourceFromResource (Resource n _ _) =
error $ "Invalid attributes for resource: " ++ n
-- | Convert the given argument into a WAI application, executable with any WAI
--- handler. This is the same as 'toWaiAppPlain', except it includes three
--- middlewares: GZIP compression, JSON-P and autohead. This is the
+-- handler. This is the same as 'toWaiAppPlain', except it includes two
+-- middlewares: GZIP compression and autohead. This is the
-- recommended approach for most users.
toWaiApp :: (Yesod y, YesodDispatch y y) => y -> IO W.Application
-toWaiApp y = gzip (gzipCompressFiles y) . jsonp . autohead <$> toWaiAppPlain y
+toWaiApp y = gzip (gzipCompressFiles y) . autohead <$> toWaiAppPlain y
-- | Convert the given argument into a WAI application, executable with any WAI
-- handler. This differs from 'toWaiApp' in that it uses no middlewares.
diff --git a/yesod-core/Yesod/Handler.hs b/yesod-core/Yesod/Handler.hs
index 8fe9ac22..ad410a97 100644
--- a/yesod-core/Yesod/Handler.hs
+++ b/yesod-core/Yesod/Handler.hs
@@ -8,6 +8,7 @@
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE DeriveDataTypeable #-}
---------------------------------------------------------
--
-- Module : Yesod.Handler
@@ -97,6 +98,12 @@ module Yesod.Handler
, liftIOHandler
-- * i18n
, getMessageRender
+ -- * Per-request caching
+ , CacheKey
+ , mkCacheKey
+ , cacheLookup
+ , cacheInsert
+ , cacheDelete
-- * Internal Yesod
, runHandler
, YesodApp (..)
@@ -119,17 +126,13 @@ import Yesod.Internal
import Data.Time (UTCTime)
import Control.Exception hiding (Handler, catch, finally)
-import qualified Control.Exception as E
import Control.Applicative
-import Control.Monad (liftM, join, MonadPlus)
+import Control.Monad (liftM)
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
-import Control.Monad.Trans.Writer
import Control.Monad.Trans.Reader
-import Control.Monad.Trans.State
-import Control.Monad.Trans.Error (throwError, ErrorT (..), Error (..))
import System.IO
import qualified Network.Wai as W
@@ -143,8 +146,6 @@ import Data.Text.Encoding (encodeUtf8, decodeUtf8With)
import Data.Text.Encoding.Error (lenientDecode)
import qualified Data.Text.Lazy as TL
-import Control.Monad.IO.Control (MonadControlIO)
-import Control.Monad.Trans.Control (MonadTransControl, liftControl)
import qualified Data.Map as Map
import qualified Data.ByteString as S
import Data.ByteString (ByteString)
@@ -154,7 +155,7 @@ import Network.Wai.Parse (parseHttpAccept)
import Yesod.Content
import Data.Maybe (fromMaybe)
import Web.Cookie (SetCookie (..), renderSetCookie)
-import Control.Arrow (second, (***))
+import Control.Arrow ((***))
import qualified Network.Wai.Parse as NWP
import Data.Monoid (mappend, mempty, Endo (..))
import qualified Data.ByteString.Char8 as S8
@@ -164,6 +165,12 @@ import Data.Text (Text)
import Yesod.Message (RenderMessage (..))
import Text.Blaze (toHtml, preEscapedText)
+import Yesod.Internal.TestApi (catchIter)
+
+import qualified Yesod.Internal.Cache as Cache
+import Yesod.Internal.Cache (mkCacheKey, CacheKey)
+import Data.Typeable (Typeable)
+import qualified Data.IORef as I
-- | The type-safe URLs associated with a site argument.
type family Route a
@@ -178,6 +185,7 @@ data HandlerData sub master = HandlerData
, handlerRoute :: Maybe (Route sub)
, handlerRender :: Route master -> [(Text, Text)] -> Text
, handlerToMaster :: Route sub -> Route master
+ , handlerState :: I.IORef GHState
}
handlerSubData :: (Route sub -> Route master)
@@ -198,6 +206,24 @@ handlerSubDataMaybe tm ts route hd = hd
, handlerRoute = route
}
+get :: MonadIO monad => GGHandler sub master monad GHState
+get = do
+ hd <- ask
+ liftIO $ I.readIORef $ handlerState hd
+
+put :: MonadIO monad => GHState -> GGHandler sub master monad ()
+put g = do
+ hd <- ask
+ liftIO $ I.writeIORef (handlerState hd) g
+
+modify :: MonadIO monad => (GHState -> GHState) -> GGHandler sub master monad ()
+modify f = do
+ hd <- ask
+ liftIO $ I.atomicModifyIORef (handlerState hd) $ \g -> (f g, ())
+
+tell :: MonadIO monad => Endo [Header] -> GGHandler sub master monad ()
+tell hs = modify $ \g -> g { ghsHeaders = ghsHeaders g `mappend` hs }
+
-- | Used internally for promoting subsite handler functions to master site
-- handler functions. Should not be needed by users.
toMasterHandler :: (Route sub -> Route master)
@@ -205,8 +231,7 @@ toMasterHandler :: (Route sub -> Route master)
-> Route sub
-> GGHandler sub master mo a
-> GGHandler sub' master mo a
-toMasterHandler tm ts route (GHandler h) =
- GHandler $ withReaderT (handlerSubData tm ts route) h
+toMasterHandler tm ts route = withReaderT (handlerSubData tm ts route)
toMasterHandlerDyn :: Monad mo
=> (Route sub -> Route master)
@@ -214,9 +239,9 @@ toMasterHandlerDyn :: Monad mo
-> Route sub
-> GGHandler sub master mo a
-> GGHandler sub' master mo a
-toMasterHandlerDyn tm getSub route (GHandler h) = do
+toMasterHandlerDyn tm getSub route h = do
sub <- getSub
- GHandler $ withReaderT (handlerSubData tm (const sub) route) h
+ withReaderT (handlerSubData tm (const sub) route) h
class SubsiteGetter g m s | g -> s where
runSubsiteGetter :: g -> m s
@@ -235,22 +260,14 @@ toMasterHandlerMaybe :: (Route sub -> Route master)
-> Maybe (Route sub)
-> GGHandler sub master mo a
-> GGHandler sub' master mo a
-toMasterHandlerMaybe tm ts route (GHandler h) =
- GHandler $ withReaderT (handlerSubDataMaybe tm ts route) h
+toMasterHandlerMaybe tm ts route = withReaderT (handlerSubDataMaybe tm ts route)
-- | A generic handler monad, which can have a different subsite and master
-- site. This monad is a combination of 'ReaderT' for basic arguments, a
-- 'WriterT' for headers and session, and an 'MEitherT' monad for handling
-- special responses. It is declared as a newtype to make compiler errors more
-- readable.
-newtype GGHandler sub master m a =
- GHandler
- { unGHandler :: GHInner sub master m a
- }
- deriving (Functor, Applicative, Monad, MonadIO, MonadControlIO, MonadPlus)
-
-instance MonadTrans (GGHandler s m) where
- lift = GHandler . lift . lift . lift . lift
+type GGHandler sub master = ReaderT (HandlerData sub master)
type GHandler sub master = GGHandler sub master (Iteratee ByteString IO)
@@ -258,16 +275,10 @@ data GHState = GHState
{ ghsSession :: SessionMap
, ghsRBC :: Maybe RequestBodyContents
, ghsIdent :: Int
+ , ghsCache :: Cache.Cache
+ , ghsHeaders :: Endo [Header]
}
-type GHInner s m monad = -- FIXME collapse the stack
- ReaderT (HandlerData s m) (
- ErrorT HandlerContents (
- WriterT (Endo [Header]) (
- StateT GHState (
- monad
- ))))
-
type SessionMap = Map.Map Text Text
-- | An extension of the basic WAI 'W.Application' datatype to provide extra
@@ -293,25 +304,27 @@ data HandlerContents =
| HCRedirect RedirectType Text
| HCCreated Text
| HCWai W.Response
+ deriving Typeable
-instance Error HandlerContents where
- strMsg = HCError . InternalError . T.pack
+instance Show HandlerContents where
+ show _ = "Cannot show a HandlerContents"
+instance Exception HandlerContents
getRequest :: Monad mo => GGHandler s m mo Request
-getRequest = handlerRequest `liftM` GHandler ask
+getRequest = handlerRequest `liftM` ask
-instance Monad monad => Failure ErrorResponse (GGHandler sub master monad) where
- failure = GHandler . lift . throwError . HCError
+instance MonadIO monad => Failure ErrorResponse (GGHandler sub master monad) where
+ failure = liftIO . throwIO . HCError
runRequestBody :: GHandler s m RequestBodyContents
runRequestBody = do
- x <- GHandler $ lift $ lift $ lift get
+ x <- get
case ghsRBC x of
Just rbc -> return rbc
Nothing -> do
rr <- waiRequest
rbc <- lift $ rbHelper rr
- GHandler $ lift $ lift $ lift $ put x { ghsRBC = Just rbc }
+ put x { ghsRBC = Just rbc }
return rbc
rbHelper :: W.Request -> Iteratee ByteString IO RequestBodyContents
@@ -326,33 +339,33 @@ rbHelper req =
-- | Get the sub application argument.
getYesodSub :: Monad m => GGHandler sub master m sub
-getYesodSub = handlerSub `liftM` GHandler ask
+getYesodSub = handlerSub `liftM` ask
-- | Get the master site appliation argument.
getYesod :: Monad m => GGHandler sub master m master
-getYesod = handlerMaster `liftM` GHandler ask
+getYesod = handlerMaster `liftM` ask
-- | Get the URL rendering function.
getUrlRender :: Monad m => GGHandler sub master m (Route master -> Text)
getUrlRender = do
- x <- handlerRender `liftM` GHandler ask
+ x <- handlerRender `liftM` ask
return $ flip x []
-- | The URL rendering function with query-string parameters.
getUrlRenderParams
:: Monad m
=> GGHandler sub master m (Route master -> [(Text, Text)] -> Text)
-getUrlRenderParams = handlerRender `liftM` GHandler ask
+getUrlRenderParams = handlerRender `liftM` ask
-- | Get the route requested by the user. If this is a 404 response- where the
-- user requested an invalid route- this function will return 'Nothing'.
getCurrentRoute :: Monad m => GGHandler sub master m (Maybe (Route sub))
-getCurrentRoute = handlerRoute `liftM` GHandler ask
+getCurrentRoute = handlerRoute `liftM` ask
-- | Get the function to promote a route for a subsite to a route for the
-- master site.
getRouteToMaster :: Monad m => GGHandler sub master m (Route sub -> Route master)
-getRouteToMaster = handlerToMaster `liftM` GHandler ask
+getRouteToMaster = handlerToMaster `liftM` ask
-- | Function used internally by Yesod in the process of converting a
-- 'GHandler' into an 'W.Application'. Should not be needed by users.
@@ -370,6 +383,13 @@ runHandler handler mrender sroute tomr ma sa =
case fromException e of
Just x -> x
Nothing -> InternalError $ T.pack $ show e
+ istate <- liftIO $ I.newIORef GHState
+ { ghsSession = initSession
+ , ghsRBC = Nothing
+ , ghsIdent = 1
+ , ghsCache = mempty
+ , ghsHeaders = mempty
+ }
let hd = HandlerData
{ handlerRequest = rr
, handlerSub = sa
@@ -377,16 +397,14 @@ runHandler handler mrender sroute tomr ma sa =
, handlerRoute = sroute
, handlerRender = mrender
, handlerToMaster = tomr
+ , handlerState = istate
}
- let initSession' = GHState initSession Nothing 1
- ((contents', headers), finalSession) <- catchIter (
- fmap (second ghsSession)
- $ flip runStateT initSession'
- $ runWriterT
- $ runErrorT
- $ flip runReaderT hd
- $ unGHandler handler
- ) (\e -> return ((Left $ HCError $ toErrorHandler e, mempty), initSession))
+ contents' <- catchIter (fmap Right $ runReaderT handler hd)
+ (\e -> return $ Left $ maybe (HCError $ toErrorHandler e) id
+ $ fromException e)
+ state <- liftIO $ I.readIORef istate
+ let finalSession = ghsSession state
+ let headers = ghsHeaders state
let contents = either id (HCContent H.status200 . chooseRep) contents'
let handleError e = do
yar <- unYesodApp (eh e) safeEh rr cts finalSession
@@ -420,12 +438,6 @@ runHandler handler mrender sroute tomr ma sa =
finalSession
HCWai r -> return $ YARWai r
-catchIter :: Exception e
- => Iteratee ByteString IO a
- -> (e -> Iteratee ByteString IO a)
- -> Iteratee ByteString IO a
-catchIter (Iteratee mstep) f = Iteratee $ mstep `E.catch` (runIteratee . f)
-
safeEh :: ErrorResponse -> YesodApp
safeEh er = YesodApp $ \_ _ _ session -> do
liftIO $ hPutStrLn stderr $ "Error handler errored out: " ++ show er
@@ -437,11 +449,11 @@ safeEh er = YesodApp $ \_ _ _ session -> do
session
-- | Redirect to the given route.
-redirect :: Monad mo => RedirectType -> Route master -> GGHandler sub master mo a
+redirect :: MonadIO mo => RedirectType -> Route master -> GGHandler sub master mo a
redirect rt url = redirectParams rt url []
-- | Redirects to the given route with the associated query-string parameters.
-redirectParams :: Monad mo
+redirectParams :: MonadIO mo
=> RedirectType -> Route master -> [(Text, Text)]
-> GGHandler sub master mo a
redirectParams rt url params = do
@@ -449,8 +461,8 @@ redirectParams rt url params = do
redirectString rt $ r url params
-- | Redirect to the given URL.
-redirectString, redirectText :: Monad mo => RedirectType -> Text -> GGHandler sub master mo a
-redirectText rt = GHandler . lift . throwError . HCRedirect rt
+redirectString, redirectText :: MonadIO mo => RedirectType -> Text -> GGHandler sub master mo a
+redirectText rt = liftIO . throwIO . HCRedirect rt
redirectString = redirectText
{-# DEPRECATED redirectString "Use redirectText instead" #-}
@@ -461,16 +473,16 @@ ultDestKey = "_ULT"
--
-- An ultimate destination is stored in the user session and can be loaded
-- later by 'redirectUltDest'.
-setUltDest :: Monad mo => Route master -> GGHandler sub master mo ()
+setUltDest :: MonadIO mo => Route master -> GGHandler sub master mo ()
setUltDest dest = do
render <- getUrlRender
setUltDestString $ render dest
-- | Same as 'setUltDest', but use the given string.
-setUltDestText :: Monad mo => Text -> GGHandler sub master mo ()
+setUltDestText :: MonadIO mo => Text -> GGHandler sub master mo ()
setUltDestText = setSession ultDestKey
-setUltDestString :: Monad mo => Text -> GGHandler sub master mo ()
+setUltDestString :: MonadIO mo => Text -> GGHandler sub master mo ()
setUltDestString = setSession ultDestKey
{-# DEPRECATED setUltDestString "Use setUltDestText instead" #-}
@@ -478,21 +490,21 @@ setUltDestString = setSession ultDestKey
--
-- If this is a 404 handler, there is no current page, and then this call does
-- nothing.
-setUltDest' :: Monad mo => GGHandler sub master mo ()
+setUltDest' :: MonadIO mo => GGHandler sub master mo ()
setUltDest' = do
route <- getCurrentRoute
case route of
Nothing -> return ()
Just r -> do
tm <- getRouteToMaster
- gets' <- reqGetParams `liftM` handlerRequest `liftM` GHandler ask
+ gets' <- reqGetParams `liftM` handlerRequest `liftM` ask
render <- getUrlRenderParams
setUltDestString $ render (tm r) gets'
-- | Sets the ultimate destination to the referer request header, if present.
--
-- This function will not overwrite an existing ultdest.
-setUltDestReferer :: Monad mo => GGHandler sub master mo ()
+setUltDestReferer :: MonadIO mo => GGHandler sub master mo ()
setUltDestReferer = do
mdest <- lookupSession ultDestKey
maybe
@@ -506,7 +518,7 @@ setUltDestReferer = do
-- value from the session.
--
-- The ultimate destination is set with 'setUltDest'.
-redirectUltDest :: Monad mo
+redirectUltDest :: MonadIO mo
=> RedirectType
-> Route master -- ^ default destination if nothing in session
-> GGHandler sub master mo a
@@ -516,7 +528,7 @@ redirectUltDest rt def = do
maybe (redirect rt def) (redirectText rt) mdest
-- | Remove a previously set ultimate destination. See 'setUltDest'.
-clearUltDest :: Monad mo => GGHandler sub master mo ()
+clearUltDest :: MonadIO mo => GGHandler sub master mo ()
clearUltDest = deleteSession ultDestKey
msgKey :: Text
@@ -525,13 +537,13 @@ msgKey = "_MSG"
-- | Sets a message in the user's session.
--
-- See 'getMessage'.
-setMessage :: Monad mo => Html -> GGHandler sub master mo ()
+setMessage :: MonadIO mo => Html -> GGHandler sub master mo ()
setMessage = setSession msgKey . T.concat . TL.toChunks . Text.Blaze.Renderer.Text.renderHtml
-- | Sets a message in the user's session.
--
-- See 'getMessage'.
-setMessageI :: (RenderMessage y msg, Monad mo) => msg -> GGHandler sub y mo ()
+setMessageI :: (RenderMessage y msg, MonadIO mo) => msg -> GGHandler sub y mo ()
setMessageI msg = do
mr <- getMessageRender
setMessage $ toHtml $ mr msg
@@ -540,7 +552,7 @@ setMessageI msg = do
-- variable.
--
-- See 'setMessage'.
-getMessage :: Monad mo => GGHandler sub master mo (Maybe Html)
+getMessage :: MonadIO mo => GGHandler sub master mo (Maybe Html)
getMessage = do
mmsg <- liftM (fmap preEscapedText) $ lookupSession msgKey
deleteSession msgKey
@@ -550,52 +562,52 @@ getMessage = do
--
-- For some backends, this is more efficient than reading in the file to
-- memory, since they can optimize file sending via a system call to sendfile.
-sendFile :: Monad mo => ContentType -> FilePath -> GGHandler sub master mo a
-sendFile ct fp = GHandler . lift . throwError $ HCSendFile ct fp Nothing
+sendFile :: MonadIO mo => ContentType -> FilePath -> GGHandler sub master mo a
+sendFile ct fp = liftIO . throwIO $ HCSendFile ct fp Nothing
-- | Same as 'sendFile', but only sends part of a file.
-sendFilePart :: Monad mo
+sendFilePart :: MonadIO mo
=> ContentType
-> FilePath
-> Integer -- ^ offset
-> Integer -- ^ count
-> GGHandler sub master mo a
sendFilePart ct fp off count =
- GHandler . lift . throwError $ HCSendFile ct fp $ Just $ W.FilePart off count
+ liftIO . throwIO $ HCSendFile ct fp $ Just $ W.FilePart off count
-- | Bypass remaining handler code and output the given content with a 200
-- status code.
-sendResponse :: (Monad mo, HasReps c) => c -> GGHandler sub master mo a
-sendResponse = GHandler . lift . throwError . HCContent H.status200
+sendResponse :: (MonadIO mo, HasReps c) => c -> GGHandler sub master mo a
+sendResponse = liftIO . throwIO . HCContent H.status200
. chooseRep
-- | Bypass remaining handler code and output the given content with the given
-- status code.
-sendResponseStatus :: (Monad mo, HasReps c) => H.Status -> c -> GGHandler s m mo a
-sendResponseStatus s = GHandler . lift . throwError . HCContent s
+sendResponseStatus :: (MonadIO mo, HasReps c) => H.Status -> c -> GGHandler s m mo a
+sendResponseStatus s = liftIO . throwIO . HCContent s
. chooseRep
-- | Send a 201 "Created" response with the given route as the Location
-- response header.
-sendResponseCreated :: Monad mo => Route m -> GGHandler s m mo a
+sendResponseCreated :: MonadIO mo => Route m -> GGHandler s m mo a
sendResponseCreated url = do
r <- getUrlRender
- GHandler $ lift $ throwError $ HCCreated $ r url
+ liftIO . throwIO $ HCCreated $ r url
-- | Send a 'W.Response'. Please note: this function is rarely
-- necessary, and will /disregard/ any changes to response headers and session
-- that you have already specified. This function short-circuits. It should be
-- considered only for very specific needs. If you are not sure if you need it,
-- you don't.
-sendWaiResponse :: Monad mo => W.Response -> GGHandler s m mo b
-sendWaiResponse = GHandler . lift . throwError . HCWai
+sendWaiResponse :: MonadIO mo => W.Response -> GGHandler s m mo b
+sendWaiResponse = liftIO . throwIO . HCWai
-- | Return a 404 not found page. Also denotes no handler available.
notFound :: Failure ErrorResponse m => m a
notFound = failure NotFound
-- | Return a 405 method not supported page.
-badMethod :: Monad mo => GGHandler s m mo a
+badMethod :: MonadIO mo => GGHandler s m mo a
badMethod = do
w <- waiRequest
failure $ BadMethod $ W.requestMethod w
@@ -605,7 +617,7 @@ permissionDenied :: Failure ErrorResponse m => Text -> m a
permissionDenied = failure . PermissionDenied
-- | Return a 403 permission denied page.
-permissionDeniedI :: (RenderMessage y msg, Monad mo) => msg -> GGHandler s y mo a
+permissionDeniedI :: (RenderMessage y msg, MonadIO mo) => msg -> GGHandler s y mo a
permissionDeniedI msg = do
mr <- getMessageRender
permissionDenied $ mr msg
@@ -615,14 +627,14 @@ invalidArgs :: Failure ErrorResponse m => [Text] -> m a
invalidArgs = failure . InvalidArgs
-- | Return a 400 invalid arguments page.
-invalidArgsI :: (RenderMessage y msg, Monad mo) => [msg] -> GGHandler s y mo a
+invalidArgsI :: (RenderMessage y msg, MonadIO mo) => [msg] -> GGHandler s y mo a
invalidArgsI msg = do
mr <- getMessageRender
invalidArgs $ map mr msg
------- Headers
-- | Set the cookie on the client.
-setCookie :: Monad mo
+setCookie :: MonadIO mo
=> Int -- ^ minutes to timeout
-> H.Ascii -- ^ key
-> H.Ascii -- ^ value
@@ -630,22 +642,22 @@ setCookie :: Monad mo
setCookie a b = addHeader . AddCookie a b
-- | Unset the cookie on the client.
-deleteCookie :: Monad mo => H.Ascii -> GGHandler sub master mo ()
+deleteCookie :: MonadIO mo => H.Ascii -> GGHandler sub master mo ()
deleteCookie = addHeader . DeleteCookie
-- | Set the language in the user session. Will show up in 'languages' on the
-- next request.
-setLanguage :: Monad mo => Text -> GGHandler sub master mo ()
+setLanguage :: MonadIO mo => Text -> GGHandler sub master mo ()
setLanguage = setSession langKey
-- | Set an arbitrary response header.
-setHeader :: Monad mo
+setHeader :: MonadIO mo
=> CI H.Ascii -> H.Ascii -> GGHandler sub master mo ()
setHeader a = addHeader . Header a
-- | Set the Cache-Control header to indicate this response should be cached
-- for the given number of seconds.
-cacheSeconds :: Monad mo => Int -> GGHandler s m mo ()
+cacheSeconds :: MonadIO mo => Int -> GGHandler s m mo ()
cacheSeconds i = setHeader "Cache-Control" $ S8.pack $ concat
[ "max-age="
, show i
@@ -654,16 +666,16 @@ cacheSeconds i = setHeader "Cache-Control" $ S8.pack $ concat
-- | Set the Expires header to some date in 2037. In other words, this content
-- is never (realistically) expired.
-neverExpires :: Monad mo => GGHandler s m mo ()
+neverExpires :: MonadIO mo => GGHandler s m mo ()
neverExpires = setHeader "Expires" "Thu, 31 Dec 2037 23:55:55 GMT"
-- | Set an Expires header in the past, meaning this content should not be
-- cached.
-alreadyExpired :: Monad mo => GGHandler s m mo ()
+alreadyExpired :: MonadIO mo => GGHandler s m mo ()
alreadyExpired = setHeader "Expires" "Thu, 01 Jan 1970 05:05:05 GMT"
-- | Set an Expires header to the given date.
-expiresAt :: Monad mo => UTCTime -> GGHandler s m mo ()
+expiresAt :: MonadIO mo => UTCTime -> GGHandler s m mo ()
expiresAt = setHeader "Expires" . encodeUtf8 . formatRFC1123
-- | Set a variable in the user's session.
@@ -671,22 +683,22 @@ expiresAt = setHeader "Expires" . encodeUtf8 . formatRFC1123
-- The session is handled by the clientsession package: it sets an encrypted
-- and hashed cookie on the client. This ensures that all data is secure and
-- not tampered with.
-setSession :: Monad mo
+setSession :: MonadIO mo
=> Text -- ^ key
-> Text -- ^ value
-> GGHandler sub master mo ()
-setSession k = GHandler . lift . lift . lift . modify . modSession . Map.insert k
+setSession k = modify . modSession . Map.insert k
-- | Unsets a session variable. See 'setSession'.
-deleteSession :: Monad mo => Text -> GGHandler sub master mo ()
-deleteSession = GHandler . lift . lift . lift . modify . modSession . Map.delete
+deleteSession :: MonadIO mo => Text -> GGHandler sub master mo ()
+deleteSession = modify . modSession . Map.delete
modSession :: (SessionMap -> SessionMap) -> GHState -> GHState
modSession f x = x { ghsSession = f $ ghsSession x }
-- | Internal use only, not to be confused with 'setHeader'.
-addHeader :: Monad mo => Header -> GGHandler sub master mo ()
-addHeader = GHandler . lift . lift . tell . Endo . (:)
+addHeader :: MonadIO mo => Header -> GGHandler sub master mo ()
+addHeader = tell . Endo . (:)
getStatus :: ErrorResponse -> H.Status
getStatus NotFound = H.status404
@@ -708,17 +720,17 @@ data RedirectType = RedirectPermanent
localNoCurrent :: Monad mo => GGHandler s m mo a -> GGHandler s m mo a
localNoCurrent =
- GHandler . local (\hd -> hd { handlerRoute = Nothing }) . unGHandler
+ local (\hd -> hd { handlerRoute = Nothing })
-- | Lookup for session data.
-lookupSession :: Monad mo => Text -> GGHandler s m mo (Maybe Text)
-lookupSession n = GHandler $ do
- m <- liftM ghsSession $ lift $ lift $ lift get
+lookupSession :: MonadIO mo => Text -> GGHandler s m mo (Maybe Text)
+lookupSession n = do
+ m <- liftM ghsSession get
return $ Map.lookup n m
-- | Get all session variables.
-getSession :: Monad mo => GGHandler s m mo SessionMap
-getSession = liftM ghsSession $ GHandler $ lift $ lift $ lift get
+getSession :: MonadIO mo => GGHandler s m mo SessionMap
+getSession = liftM ghsSession get
handlerToYAR :: (HasReps a, HasReps b)
=> m -- ^ master site foundation
@@ -808,8 +820,8 @@ headerToPair cp _ (DeleteCookie key) =
headerToPair _ _ (Header key value) = (key, value)
-- | Get a unique identifier.
-newIdent :: Monad mo => GGHandler sub master mo String -- FIXME use Text
-newIdent = GHandler $ lift $ lift $ lift $ do
+newIdent :: MonadIO mo => GGHandler sub master mo String -- FIXME use Text
+newIdent = do
x <- get
let i' = ghsIdent x + 1
put x { ghsIdent = i' }
@@ -818,42 +830,7 @@ newIdent = GHandler $ lift $ lift $ lift $ do
liftIOHandler :: MonadIO mo
=> GGHandler sub master IO a
-> GGHandler sub master mo a
-liftIOHandler m = GHandler $
- ReaderT $ \r ->
- ErrorT $
- WriterT $
- StateT $ \s ->
- liftIO $ runGGHandler m r s
-
-runGGHandler :: GGHandler sub master m a
- -> HandlerData sub master
- -> GHState
- -> m ( ( Either HandlerContents a
- , Endo [Header]
- )
- , GHState
- )
-runGGHandler m r s = runStateT
- (runWriterT
- (runErrorT
- (runReaderT
- (unGHandler m) r))) s
-
-instance MonadTransControl (GGHandler s m) where
- liftControl f =
- GHandler $
- liftControl $ \runRdr ->
- liftControl $ \runErr ->
- liftControl $ \runWrt ->
- liftControl $ \runSt ->
- f ( liftM ( GHandler
- . join . lift
- . join . lift
- . join . lift
- )
- . runSt . runWrt . runErr . runRdr
- . unGHandler
- )
+liftIOHandler (ReaderT m) = ReaderT $ \r -> liftIO $ m r
-- | Redirect to a POST resource.
--
@@ -861,7 +838,7 @@ instance MonadTransControl (GGHandler s m) where
-- POST form, and some Javascript to automatically submit the form. This can be
-- useful when you need to post a plain link somewhere that needs to cause
-- changes on the server.
-redirectToPost :: Monad mo => Route master -> GGHandler sub master mo a
+redirectToPost :: MonadIO mo => Route master -> GGHandler sub master mo a
redirectToPost dest = hamletToRepHtml
#if GHC7
[hamlet|
@@ -902,3 +879,16 @@ getMessageRender = do
m <- getYesod
l <- reqLangs `liftM` getRequest
return $ renderMessage m l
+
+cacheLookup :: MonadIO mo => CacheKey a -> GGHandler sub master mo (Maybe a)
+cacheLookup k = do
+ gs <- get
+ return $ Cache.lookup k $ ghsCache gs
+
+cacheInsert :: MonadIO mo => CacheKey a -> a -> GGHandler sub master mo ()
+cacheInsert k v = modify $ \gs ->
+ gs { ghsCache = Cache.insert k v $ ghsCache gs }
+
+cacheDelete :: MonadIO mo => CacheKey a -> GGHandler sub master mo ()
+cacheDelete k = modify $ \gs ->
+ gs { ghsCache = Cache.delete k $ ghsCache gs }
diff --git a/yesod-core/Yesod/Internal.hs b/yesod-core/Yesod/Internal.hs
index bd1adc91..9b97cd6b 100644
--- a/yesod-core/Yesod/Internal.hs
+++ b/yesod-core/Yesod/Internal.hs
@@ -29,7 +29,6 @@ module Yesod.Internal
) where
import Text.Hamlet (HtmlUrl, hamlet, Html)
-import Text.Cassius (CssUrl)
import Text.Julius (JavascriptUrl)
import Data.Monoid (Monoid (..), Last)
import Data.List (nub)
@@ -44,6 +43,7 @@ import qualified Network.HTTP.Types as A
import Data.CaseInsensitive (CI)
import Data.String (IsString)
import qualified Data.Map as Map
+import Data.Text.Lazy.Builder (Builder)
#if GHC7
#define HAMLET hamlet
@@ -107,12 +107,14 @@ nonceKey = "_NONCE"
sessionName :: IsString a => a
sessionName = "_SESSION"
+type CssBuilderUrl a = (a -> [(Text, Text)] -> Text) -> Builder
+
data GWData a = GWData
!(Body a)
!(Last Title)
!(UniqueList (Script a))
!(UniqueList (Stylesheet a))
- !(Map.Map (Maybe Text) (CssUrl a)) -- media type
+ !(Map.Map (Maybe Text) (CssBuilderUrl a)) -- media type
!(Maybe (JavascriptUrl a))
!(Head a)
instance Monoid (GWData a) where
diff --git a/yesod-core/Yesod/Internal/Cache.hs b/yesod-core/Yesod/Internal/Cache.hs
new file mode 100644
index 00000000..4aec0d29
--- /dev/null
+++ b/yesod-core/Yesod/Internal/Cache.hs
@@ -0,0 +1,38 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE TemplateHaskell #-}
+module Yesod.Internal.Cache
+ ( Cache
+ , CacheKey
+ , mkCacheKey
+ , lookup
+ , insert
+ , delete
+ ) where
+
+import Prelude hiding (lookup)
+import qualified Data.IntMap as Map
+import Language.Haskell.TH.Syntax (Q, Exp, runIO, Exp (LitE), Lit (IntegerL))
+import Language.Haskell.TH (appE)
+import Data.Unique (hashUnique, newUnique)
+import GHC.Exts (Any)
+import Unsafe.Coerce (unsafeCoerce)
+import Data.Monoid (Monoid)
+import Control.Applicative ((<$>))
+
+newtype Cache = Cache (Map.IntMap Any)
+ deriving Monoid
+
+newtype CacheKey a = CacheKey Int
+
+-- | Generate a new 'CacheKey'. Be sure to give a full type signature.
+mkCacheKey :: Q Exp
+mkCacheKey = [|CacheKey|] `appE` (LitE . IntegerL . fromIntegral . hashUnique <$> runIO newUnique)
+
+lookup :: CacheKey a -> Cache -> Maybe a
+lookup (CacheKey i) (Cache m) = unsafeCoerce <$> Map.lookup i m
+
+insert :: CacheKey a -> a -> Cache -> Cache
+insert (CacheKey k) v (Cache m) = Cache (Map.insert k (unsafeCoerce v) m)
+
+delete :: CacheKey a -> Cache -> Cache
+delete (CacheKey k) (Cache m) = Cache (Map.delete k m)
diff --git a/yesod-core/Yesod/Internal/Core.hs b/yesod-core/Yesod/Internal/Core.hs
index 4ba14405..29e1355a 100644
--- a/yesod-core/Yesod/Internal/Core.hs
+++ b/yesod-core/Yesod/Internal/Core.hs
@@ -35,8 +35,6 @@ import Yesod.Handler
import Control.Arrow ((***))
import Control.Monad (forM)
-import qualified Paths_yesod_core
-import Data.Version (showVersion)
import Yesod.Widget
import Yesod.Request
import qualified Network.Wai as W
@@ -48,11 +46,10 @@ import qualified Web.ClientSession as CS
import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString.Lazy as L
import Data.Monoid
-import Control.Monad.Trans.RWS
+import Control.Monad.Trans.Writer (runWriterT)
import Text.Hamlet
-import Text.Cassius
import Text.Julius
-import Text.Blaze ((!), customAttribute, textTag, toValue)
+import Text.Blaze ((!), customAttribute, textTag, toValue, unsafeLazyByteString)
import qualified Text.Blaze.Html5 as TBH
import Data.Text.Lazy.Builder (toLazyText)
import Data.Text.Lazy.Encoding (encodeUtf8)
@@ -75,6 +72,20 @@ import qualified Data.Text.Lazy.IO
import qualified Data.Text.Lazy.Builder as TB
import Language.Haskell.TH.Syntax (Loc (..), Lift (..))
import Text.Blaze (preEscapedLazyText)
+import Data.Aeson (Value (Array, String))
+import Data.Aeson.Encode (encode)
+import qualified Data.Vector as Vector
+
+-- mega repo can't access this
+#ifndef MEGA
+import qualified Paths_yesod_core
+import Data.Version (showVersion)
+yesodVersion :: String
+yesodVersion = showVersion Paths_yesod_core.version
+#else
+yesodVersion :: String
+yesodVersion = "0.9.3.2"
+#endif
#if GHC7
#define HAMLET hamlet
@@ -159,9 +170,9 @@ class RenderRoute (Route a) => Yesod a where
-- | Determine if a request is authorized or not.
--
- -- Return 'Nothing' is the request is authorized, 'Just' a message if
- -- unauthorized. If authentication is required, you should use a redirect;
- -- the Auth helper provides this functionality automatically.
+ -- Return 'Authorized' if the request is authorized,
+ -- 'Unauthorized' a message if unauthorized.
+ -- If authentication is required, return 'AuthenticationRequired'.
isAuthorized :: Route a
-> Bool -- ^ is this a write request?
-> GHandler s a AuthResult
@@ -264,6 +275,11 @@ class RenderRoute (Route a) => Yesod a where
gzipCompressFiles :: a -> Bool
gzipCompressFiles _ = False
+ -- | Location of yepnope.js, if any. If one is provided, then all
+ -- Javascript files will be loaded asynchronously.
+ yepnopeJs :: a -> Maybe (Either Text (Route a))
+ yepnopeJs _ = Nothing
+
messageLoggerHandler :: (Yesod m, MonadIO mo)
=> Loc -> LogLevel -> Text -> GGHandler s m mo ()
messageLoggerHandler loc level msg = do
@@ -327,12 +343,12 @@ defaultYesodRunner _ m toMaster _ murl _ req
[] -> Nothing
(x, _):_ -> Just x
defaultYesodRunner s master toMasterRoute mkey murl handler req = do
- now <- liftIO getCurrentTime
- let getExpires m = fromIntegral (m * 60) `addUTCTime` now
- let exp' = getExpires $ clientSessionDuration master
- let rh = takeWhile (/= ':') $ show $ W.remoteHost req
+ now <- {-# SCC "getCurrentTime" #-} liftIO getCurrentTime
+ let getExpires m = {-# SCC "getExpires" #-} fromIntegral (m * 60) `addUTCTime` now
+ let exp' = {-# SCC "exp'" #-} getExpires $ clientSessionDuration master
+ let rh = {-# SCC "rh" #-} takeWhile (/= ':') $ show $ W.remoteHost req
let host = if sessionIpAddress master then S8.pack rh else ""
- let session' =
+ let session' = {-# SCC "session'" #-}
case mkey of
Nothing -> []
Just key -> fromMaybe [] $ do
@@ -340,7 +356,7 @@ defaultYesodRunner s master toMasterRoute mkey murl handler req = do
val <- lookup sessionName $ parseCookies raw
decodeSession key now host val
rr <- liftIO $ parseWaiRequest req session' mkey
- let h = do
+ let h = {-# SCC "h" #-} do
case murl of
Nothing -> handler
Just url -> do
@@ -361,7 +377,8 @@ defaultYesodRunner s master toMasterRoute mkey murl handler req = do
$ filter (\(x, _) -> x /= nonceKey) session'
yar <- handlerToYAR master s toMasterRoute (yesodRender master) errorHandler rr murl sessionMap h
let mnonce = reqNonce rr
- iv <- liftIO CS.randomIV
+ -- FIXME should we be caching this IV value and reusing it for efficiency?
+ iv <- {-# SCC "iv" #-} maybe (return $ error "Should not be used") (const $ liftIO CS.randomIV) mkey
return $ yarToResponse (hr iv mnonce getExpires host exp') yar
where
hr iv mnonce getExpires host exp' hs ct sm =
@@ -472,18 +489,22 @@ maybeAuthorized r isWrite = do
x <- isAuthorized r isWrite
return $ if x == Authorized then Just r else Nothing
+jsToHtml :: Javascript -> Html
+jsToHtml (Javascript b) = preEscapedLazyText $ toLazyText b
+
+jelper :: JavascriptUrl url -> HtmlUrl url
+jelper = fmap jsToHtml
+
-- | Convert a widget to a 'PageContent'.
widgetToPageContent :: (Eq (Route master), Yesod master)
=> GWidget sub master ()
-> GHandler sub master (PageContent (Route master))
widgetToPageContent (GWidget w) = do
- ((), _, GWData (Body body) (Last mTitle) scripts' stylesheets' style jscript (Head head')) <- runRWST w () 0
+ master <- getYesod
+ ((), GWData (Body body) (Last mTitle) scripts' stylesheets' style jscript (Head head')) <- runWriterT w
let title = maybe mempty unTitle mTitle
let scripts = runUniqueList scripts'
let stylesheets = runUniqueList stylesheets'
- let jsToHtml (Javascript b) = preEscapedLazyText $ toLazyText b
- jelper :: JavascriptUrl url -> HtmlUrl url
- jelper = fmap jsToHtml
render <- getUrlRenderParams
let renderLoc x =
@@ -492,7 +513,7 @@ widgetToPageContent (GWidget w) = do
Just (Left s) -> Just s
Just (Right (u, p)) -> Just $ render u p
css <- forM (Map.toList style) $ \(mmedia, content) -> do
- let rendered = renderCssUrl render content
+ let rendered = toLazyText $ content render
x <- addStaticContent "css" "text/css; charset=utf-8"
$ encodeUtf8 rendered
return (mmedia,
@@ -536,19 +557,54 @@ $forall s <- css
"].join(""),k.id=i,k.innerHTML+=f,g.appendChild(k),h=c(k,a),k.parentNode.removeChild(k);return!!h},w=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=C(e[d],"function"),C(e[d],c)||(e[d]=c),e.removeAttribute(d))),e=null;return f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),x,y={}.hasOwnProperty,z;!C(y,c)&&!C(y.call,c)?z=function(a,b){return y.call(a,b)}:z=function(a,b){return b in a&&C(a.constructor.prototype[b],c)};var G=function(c,d){var f=c.join(""),g=d.length;v(f,function(c,d){var f=b.styleSheets[b.styleSheets.length-1],h=f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"",i=c.childNodes,j={};while(g--)j[i[g].id]=i[g];e.touch="ontouchstart"in a||j.touch.offsetTop===9,e.csstransforms3d=j.csstransforms3d.offsetLeft===9,e.generatedcontent=j.generatedcontent.offsetHeight>=1,e.fontface=/src/i.test(h)&&h.indexOf(d.split(" ")[0])===0},g,d)}(['@font-face {font-family:"font";src:url("https://")}',["@media (",o.join("touch-enabled),("),i,")","{#touch{top:9px;position:absolute}}"].join(""),["@media (",o.join("transform-3d),("),i,")","{#csstransforms3d{left:9px;position:absolute}}"].join(""),['#generatedcontent:after{content:"',m,'";visibility:hidden}'].join("")],["fontface","touch","csstransforms3d","generatedcontent"]);r.flexbox=function(){function c(a,b,c,d){a.style.cssText=o.join(b+":"+c+";")+(d||"")}function a(a,b,c,d){b+=":",a.style.cssText=(b+o.join(c+";"+b)).slice(0,-b.length)+(d||"")}var d=b.createElement("div"),e=b.createElement("div");a(d,"display","box","width:42px;padding:0;"),c(e,"box-flex","1","width:10px;"),d.appendChild(e),g.appendChild(d);var f=e.offsetWidth===42;d.removeChild(e),g.removeChild(d);return f},r.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},r.canvastext=function(){return!!e.canvas&&!!C(b.createElement("canvas").getContext("2d").fillText,"function")},r.webgl=function(){return!!a.WebGLRenderingContext},r.touch=function(){return e.touch},r.geolocation=function(){return!!navigator.geolocation},r.postmessage=function(){return!!a.postMessage},r.websqldatabase=function(){var b=!!a.openDatabase;return b},r.indexedDB=function(){for(var b=-1,c=p.length;++b7)},r.history=function(){return!!a.history&&!!history.pushState},r.draganddrop=function(){return w("dragstart")&&w("drop")},r.websockets=function(){for(var b=-1,c=p.length;++b";return(a.firstChild&&a.firstChild.namespaceURI)==q.svg},r.smil=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"animate")))},r.svgclippaths=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"clipPath")))};for(var I in r)z(r,I)&&(x=I.toLowerCase(),e[x]=r[I](),u.push((e[x]?"":"no-")+x));e.input||H(),A(""),j=l=null,a.attachEvent&&function(){var a=b.createElement("div");a.innerHTML="";return a.childNodes.length!==1}()&&function(a,b){function s(a){var b=-1;while(++b
+\
+\
+\
+\
+
+
+
+
+ #{pageTitle pc}
+
+
+
+
+
+ ^{pageHead pc}
+
+ \
+
+