diff --git a/CHANGES b/CHANGES
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,53 @@
-Version 0.7.2.1 released 20 Mar 2010
+Version 0.7.3 released 20 Mar 2010
 
-* Version bump to 0.7.2.1, depend on pandoc < 1.5.
+* Added PDF export option and pdf-export config field.
+  (Based on a patch by gwern.)
+
+* Added markdown export.
+
+* Use pandoc's new MathML math mode for more efficient
+  MathML.
+
+* Improved multi-wiki example code in haddocks.
+
+* Added session-timeout config setting.
+
+* Config module:  Added readSize (recognizing K,M,G suffix).
+  Previously readNumber always recognized K,M,G suffixes,
+  but these only make sense in some contexts (not e.g. for
+  times).
+
+* Added Subst plugin (thanks to gwern).
+
+* Added notes on PDF caching and idle.
+
+* Fixed table of contents in wiki pages (resolving Issue #91).
+
+* Added pandoc-user-data config option, allowing the user
+  to specify a directory with e.g. templates that override
+  the defaults used for exported pages.
+
+* Fix filesToClean GHC panic when loading plugins on GHC HEAD
+
+* Fixed problem with doubled // in updir links.
+  Resolves Issue #88.
+
+* Updated interwiki plugin.
+
+* Fixed caching for feeds. Thanks to brian.sniffen for pointing
+  out the need to normalize the time diff. Resolves Issue #87.
+
+* Improved Feed module (gwern).
+
+* Use line anchors from highlighting-source, so that you can link
+  directly to a particular line in a source file.
+
+* Disable upload functionality if maxUploadSize is 0.
+
+* Exported queryGititState, updateGititState, Network.Gitit.Layout.
+  Exported filledPageTemplate. (Thanks to tphyahoo.)
+  Split off and expose createDefaultPages.
+  Exposed compilePageTemplate.
 
 * Use charset=utf-8 on output from Layout.
 
diff --git a/Network/Gitit.hs b/Network/Gitit.hs
--- a/Network/Gitit.hs
+++ b/Network/Gitit.hs
@@ -38,7 +38,6 @@
 > import Control.Monad
 > import Text.XHtml hiding (dir)
 > import Happstack.Server.SimpleHTTP
-> import My.Auth.System (myGetUser, myLoginUser, myLogoutUser)
 > 
 > type WikiSpec = (String, FileStoreType, PageType)
 > 
@@ -46,14 +45,16 @@
 >         , ("latexWiki", Darcs, LaTeX) ]
 > 
 > -- custom authentication
-> withUser :: Handler -> Handler
-> withUser handler = do
->   user <- myGetUser
+> myWithUser :: Handler -> Handler
+> myWithUser handler = do
+>   -- replace the following with a function that retrieves
+>   -- the logged in user for your happstack app:
+>   user <- return "testuser"
 >   localRq (setHeader "REMOTE_USER" user) handler
 >
 > myAuthHandler = msum
->   [ dir "_login" myLoginUser
->   , dir "_logout" myLogoutUser ]
+>   [ dir "_login"  $ seeOther "/your/login/url"  $ toResponse ()
+>   , dir "_logout" $ seeOther "/your/logout/url" $ toResponse () ]
 >
 > handlerFor :: Config -> WikiSpec -> ServerPart Response
 > handlerFor conf (path', fstype, pagetype) = dir path' $
@@ -68,7 +69,7 @@
 > 
 > main = do
 >   conf <- getDefaultConfig
->   let conf' = conf{authHandler = myAuthHandler}
+>   let conf' = conf{authHandler = myAuthHandler, withUser = myWithUser}
 >   forM wikis $ \(path', fstype, pagetype) -> do
 >     let conf'' = conf'{ repositoryPath = path'
 >                       , repositoryType = fstype
@@ -81,7 +82,6 @@
 >   simpleHTTP nullConf{port = 5001} $
 >     (nullDir >> indexPage) `mplus` msum (map (handlerFor conf') wikis)
 
-
 -}
 
 module Network.Gitit (
@@ -98,10 +98,13 @@
                      , module Network.Gitit.Types
                      -- * Tools for building handlers
                      , module Network.Gitit.Framework
+                     , module Network.Gitit.Layout
                      , module Network.Gitit.ContentTransformer
                      , getFileStore
                      , getUser
                      , getConfig
+                     , queryGititState
+                     , updateGititState
                      )
 where
 import Network.Gitit.Types
@@ -110,7 +113,9 @@
 import Network.Gitit.Handlers
 import Network.Gitit.Initialize
 import Network.Gitit.Config
-import Network.Gitit.State (getFileStore, getUser, getConfig)
+import Network.Gitit.Layout
+import Network.Gitit.State
+        (getFileStore, getUser, getConfig, queryGititState, updateGititState)
 import Network.Gitit.ContentTransformer
 import Network.Gitit.Authentication (loginUserForm)
 import Paths_gitit (getDataFileName)
@@ -160,8 +165,9 @@
   , dir "_activity" showActivity
   , dir "_go"       goToPage
   , dir "_search"   searchResults
-  , dir "_upload"   $ methodOnly GET  >> requireUser uploadForm 
-  , dir "_upload"   $ methodOnly POST >> requireUser uploadFile
+  , dir "_upload"   $  do guard =<< return . uploadsAllowed =<< getConfig
+                          msum [ methodOnly GET  >> requireUser uploadForm 
+                                 , methodOnly POST >> requireUser uploadFile ]
   , dir "_random"   $ methodOnly GET  >> randomPage
   , dir "_index"    indexPage
   , dir "_feed"     feedHandler
diff --git a/Network/Gitit/Authentication.hs b/Network/Gitit/Authentication.hs
--- a/Network/Gitit/Authentication.hs
+++ b/Network/Gitit/Authentication.hs
@@ -362,10 +362,11 @@
   let pword = pPassword params
   let destination = pDestination params
   allowed <- authUser uname pword
+  cfg <- getConfig
   if allowed
     then do
       key <- newSession (SessionData uname)
-      addCookie sessionTime (mkCookie "sid" (show key))
+      addCookie (sessionTimeout cfg) (mkCookie "sid" (show key))
       seeOther (encUrl destination) $ toResponse $ p << ("Welcome, " ++ uname)
     else
       withMessages ["Invalid username or password."] loginUserForm
diff --git a/Network/Gitit/Cache.hs b/Network/Gitit/Cache.hs
--- a/Network/Gitit/Cache.hs
+++ b/Network/Gitit/Cache.hs
@@ -34,6 +34,7 @@
 import Control.Monad.Trans (liftIO)
 
 -- | Expire a cached file, identified by its filename in the filestore.
+-- If there is an associated exported PDF, expire it too.
 -- Returns () after deleting a file from the cache, fails if no cached file.
 expireCachedFile :: String -> GititServerPart ()
 expireCachedFile file = do
@@ -41,9 +42,18 @@
   let target = cacheDir cfg </> file
   exists <- liftIO $ doesFileExist target
   if exists
-     then liftIO (removeFile target)
+     then liftIO $ do
+       removeFile target
+       expireCachedPDF (cacheDir cfg </> file)
      else mzero
 
+expireCachedPDF :: String -> IO ()
+expireCachedPDF file =
+  when (takeExtension file == ".page") $ do
+     let pdfname = file ++ ".export.pdf"
+     exists <- doesFileExist pdfname
+     when exists $ removeFile pdfname
+
 lookupCache :: String -> GititServerPart (Maybe (ClockTime, B.ByteString))
 lookupCache file = do
   cfg <- getConfig
@@ -64,3 +74,4 @@
   liftIO $ do
     createDirectoryIfMissing True targetDir
     B.writeFile (cacheDir cfg </> file) contents
+    expireCachedPDF (cacheDir cfg </> file)
diff --git a/Network/Gitit/Config.hs b/Network/Gitit/Config.hs
--- a/Network/Gitit/Config.hs
+++ b/Network/Gitit/Config.hs
@@ -24,7 +24,6 @@
                             , getDefaultConfig
                             , readMimeTypesFile )
 where
-import Safe
 import Network.Gitit.Types
 import Network.Gitit.Server (mimeTypes)
 import Network.Gitit.Framework
@@ -46,7 +45,7 @@
 import System.IO.UTF8
 #endif
 import System.FilePath ((</>))
-import Text.Pandoc
+import Text.Pandoc hiding (MathML)
 
 forceEither :: Show e => Either e a -> a
 forceEither = either (error . show) id
@@ -76,6 +75,7 @@
       cfShowLHSBirdTracks <- get cp "DEFAULT" "show-lhs-bird-tracks"
       cfAuthenticationMethod <- get cp "DEFAULT" "authentication-method"
       cfUserFile <- get cp "DEFAULT" "user-file"
+      cfSessionTimeout <- get cp "DEFAULT" "session-timeout"
       cfTemplatesDir <- get cp "DEFAULT" "templates-dir"
       cfLogFile <- get cp "DEFAULT" "log-file"
       cfLogLevel <- get cp "DEFAULT" "log-level"
@@ -105,6 +105,8 @@
       cfWikiTitle <- get cp "DEFAULT" "wiki-title"
       cfFeedDays <- get cp "DEFAULT" "feed-days"
       cfFeedRefreshTime <- get cp "DEFAULT" "feed-refresh-time"
+      cfPDFExport <- get cp "DEFAULT" "pdf-export"
+      cfPandocUserData <- get cp "DEFAULT" "pandoc-user-data"
       let (pt, lhs) = parsePageType cfDefaultPageType
       let markupHelpFile = show pt ++ if lhs then "+LHS" else ""
       markupHelpPath <- liftIO $ getDataFileName $ "data" </> "markupHelp" </> markupHelpFile
@@ -139,6 +141,7 @@
                                       "http"     -> msum httpAuthHandlers
                                       _          -> mzero
         , userFile             = cfUserFile
+        , sessionTimeout       = readNumber "session-timeout" cfSessionTimeout * 60  -- convert minutes -> seconds
         , templatesDir         = cfTemplatesDir
         , logFile              = cfLogFile
         , logLevel             = let levelString = map toUpper cfLogLevel
@@ -150,7 +153,7 @@
         , staticDir            = cfStaticDir
         , pluginModules        = splitCommaList cfPlugins
         , tableOfContents      = cfTableOfContents
-        , maxUploadSize        = readNumber "max-upload-size" cfMaxUploadSize
+        , maxUploadSize        = readSize "max-upload-size" cfMaxUploadSize
         , portNumber           = readNumber "port" cfPort
         , debugMode            = cfDebugMode
         , frontPage            = cfFrontPage
@@ -174,7 +177,11 @@
         , baseUrl              = stripTrailingSlash cfBaseUrl
         , wikiTitle            = cfWikiTitle
         , feedDays             = readNumber "feed-days" cfFeedDays
-        , feedRefreshTime      = readNumber "feed-refresh-time" cfFeedRefreshTime }
+        , feedRefreshTime      = readNumber "feed-refresh-time" cfFeedRefreshTime
+        , pdfExport            = cfPDFExport
+        , pandocUserData       = if null cfPandocUserData
+                                    then Nothing
+                                    else Just cfPandocUserData }
   case config' of
         Left (ParseError e, e') -> error $ "Parse error: " ++ e ++ "\n" ++ e'
         Left e                  -> error (show e)
@@ -187,17 +194,17 @@
         dropGt ('>':xs) = xs
         dropGt x = x
 
-readNumber :: (Read a) => String -> String -> a
-readNumber opt "" = error $ opt ++ " must be a number."
-readNumber opt x  =
-  let x' = case lastNote "readNumber" x of
-                'K'  -> init x ++ "000"
-                'M'  -> init x ++ "000000"
-                'G'  -> init x ++ "000000000"
-                _    -> x
-  in if all isDigit x'
-        then read x'
-        else error $ opt ++ " must be a number."
+readNumber :: (Num a, Read a) => String -> String -> a
+readNumber _   x | all isDigit x = read x
+readNumber opt _ = error $ opt ++ " must be a number."
+
+readSize :: (Num a, Read a) => String -> String -> a
+readSize opt x =
+  case reverse x of
+       ('K':_) -> readNumber opt (init x) * 1000
+       ('M':_) -> readNumber opt (init x) * 1000000
+       ('G':_) -> readNumber opt (init x) * 1000000000
+       _       -> readNumber opt x
 
 splitCommaList :: String -> [String]
 splitCommaList l =
diff --git a/Network/Gitit/ContentTransformer.hs b/Network/Gitit/ContentTransformer.hs
--- a/Network/Gitit/ContentTransformer.hs
+++ b/Network/Gitit/ContentTransformer.hs
@@ -79,7 +79,8 @@
 import Network.Gitit.Cache (lookupCache, cacheContents)
 import qualified Data.FileStore as FS
 import Data.Maybe (mapMaybe)
-import Text.Pandoc
+import Text.Pandoc hiding (MathML)
+import qualified Text.Pandoc as Pandoc
 import Text.Pandoc.Shared (ObfuscationMethod(..))
 import Text.XHtml hiding ( (</>), dir, method, password, rev )
 import Text.Highlighting.Kate
@@ -91,8 +92,6 @@
 import Network.URI (isUnescapedInURI, escapeURIString)
 import qualified Data.ByteString as S (concat) 
 import qualified Data.ByteString.Lazy as L (toChunks, fromChunks)
-import Text.XML.Light
-import Text.TeXMath
 
 --
 -- ContentTransformer runners
@@ -289,8 +288,9 @@
 exportPandoc doc = do
   params <- getParams
   page <- getPageName
+  cfg <- lift getConfig
   let format = pFormat params
-  case lookup format exportFormats of
+  case lookup format (exportFormats cfg) of
        Nothing     -> error $ "Unknown export format: " ++ format
        Just writer -> lift (writer page doc)
 
@@ -336,10 +336,15 @@
   base' <- lift getWikiBase
   toc <- liftM ctxTOC get
   bird <- liftM ctxBirdTracks get
+  cfg <- lift getConfig
   return $ writeHtml defaultWriterOptions{
-                        writerStandalone = False
-                      , writerHTMLMathMethod = JsMath
-                               (Just $ base' ++ "/js/jsMath/easy/load.js")
+                        writerStandalone = True
+                      , writerTemplate = "$if(toc)$\n$toc$\n$endif$\n$body$"
+                      , writerHTMLMathMethod =
+                            case mathMethod cfg of
+                                 MathML -> Pandoc.MathML Nothing
+                                 _      -> JsMath (Just $ base' ++
+                                                      "/js/jsMath/easy/load.js")
                       , writerTableOfContents = toc
                       , writerLiterateHaskell = bird
                       -- note: javascript obfuscation gives problems on preview
@@ -351,13 +356,12 @@
 highlightSource Nothing = mzero
 highlightSource (Just source) = do
   file <- getFileName
-  -- let lang' = head $ languagesByExtension $ takeExtension file
-  let lang' = case languagesByExtension $ takeExtension file of
-               []    -> error "highlightSource, no lang'"
-               (l:_) -> l
-  case highlightAs lang' (filter (/='\r') source) of
-       Left _       -> mzero
-       Right res    -> return $ formatAsXHtml [OptNumberLines] lang' $! res
+  let formatOpts = [OptNumberLines, OptLineAnchors]
+  case languagesByExtension $ takeExtension file of
+        []    -> mzero
+        (l:_) -> case highlightAs l (filter (/='\r') source) of
+                   Left _       -> mzero
+                   Right res    -> return $ formatAsXHtml formatOpts l $! res
 
 --
 -- Plugin combinators
@@ -400,12 +404,7 @@
 applyPageTransforms :: Pandoc -> ContentTransformer Pandoc 
 applyPageTransforms c = do
   xforms <- getPageTransforms
-  cfg <- lift getConfig
-  params <- getParams
-  let xforms' = case mathMethod cfg of
-                      MathML -> mathMLTransform (pFormat params) : xforms
-                      _      -> xforms
-  foldM applyTransform c (wikiLinksTransform : xforms')
+  foldM applyTransform c (wikiLinksTransform : xforms)
 
 -- | Applies all the pre-parse transform plugins to a Page object.
 applyPreParseTransforms :: Page -> ContentTransformer Page
@@ -447,9 +446,7 @@
   updateLayout $ \l ->
     case mathMethod conf of
          JsMathScript -> addScripts l ["jsMath/easy/load.js"]
-         -- for MathML, the script is added by mathMLTransform, only
-         -- if the page contains math:
-         MathML       -> l
+         MathML       -> addScripts l ["MathMLInHTML.js"]
          RawTeX       -> l
   return c
 
@@ -507,28 +504,6 @@
 convertWikiLinks (Link ref ("", "")) =
   Link ref (inlinesToURL ref, "Go to wiki page")
 convertWikiLinks x = x
-
-mathMLTransform :: String -> Pandoc -> PluginM Pandoc
-mathMLTransform format inp | format `elem` ["","S5"] = do
-  let (Pandoc m blks, mathUsed) = runState (processWithM convertTeXMathToMathML inp) False
-  let scriptLink = RawHtml "<script type=\"text/javascript\" src=\"/js/MathMLinHTML.js\"></script>"
-  let blks' = if mathUsed
-                 then blks ++ [scriptLink]
-                 else blks
-  return $ Pandoc m blks'
-mathMLTransform _ inp = return inp
-
--- | Convert math to MathML.  We put this in a Writer monad
--- to keep track of whether we've actually got any MathML; if not,
--- we can avoid linking a script.
-convertTeXMathToMathML :: Inline -> State Bool Inline
-convertTeXMathToMathML (Math t x) = do
-  case texMathToMathML t' x of
-       Left _  -> return $ Math t x
-       Right v -> put True >> return (HtmlInline $ showXml v)
-    where t' = if t == DisplayMath then DisplayBlock else DisplayInline
-          showXml = ppcElement (useShortEmptyTags (const False) defaultConfigPP)
-convertTeXMathToMathML x = return x
 
 -- | Derives a URL from a list of Pandoc Inline elements.
 inlinesToURL :: [Inline] -> String
diff --git a/Network/Gitit/Export.hs b/Network/Gitit/Export.hs
--- a/Network/Gitit/Export.hs
+++ b/Network/Gitit/Export.hs
@@ -19,24 +19,33 @@
 {- Functions for exporting wiki pages in various formats.
 -}
 
-module Network.Gitit.Export ( exportFormats )
-where
+module Network.Gitit.Export ( exportFormats ) where
 import Text.Pandoc
 import Text.Pandoc.Writers.S5 (s5HeaderIncludes)
 import Text.Pandoc.ODT (saveOpenDocumentAsODT)
+import Text.Pandoc.Shared (escapeStringUsing)
 import Network.Gitit.Server
+import Network.Gitit.Framework (pathForPage)
 import Network.Gitit.Util (withTempDir)
-import Network.Gitit.State
+import Network.Gitit.State (getConfig)
 import Network.Gitit.Types
+import Network.Gitit.Cache (cacheContents, lookupCache)
 import Control.Monad.Trans (liftIO)
+import Control.Monad (unless, when)
 import Text.XHtml (noHtml)
-import qualified Data.ByteString.Lazy as B
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as L
 import System.FilePath ((<.>), (</>))
 import Control.Exception (throwIO)
+import System.Environment (getEnvironment)
+import System.Exit (ExitCode(..))
+import System.IO (openTempFile)
+import System.Directory (getCurrentDirectory, setCurrentDirectory, removeFile)
+import System.Process (runProcess, waitForProcess)
+import Codec.Binary.UTF8.String (encodeString)
 
 defaultRespOptions :: WriterOptions
-defaultRespOptions = defaultWriterOptions { writerStandalone = True
-                                          , writerWrapText = True }
+defaultRespOptions = defaultWriterOptions { writerStandalone = True }
 
 respond :: String
         -> String
@@ -49,9 +58,10 @@
   toResponse . fn
 
 respondX :: String -> String -> String -> (WriterOptions -> Pandoc -> String)
-         -> WriterOptions -> String -> Pandoc -> Handler
+          -> WriterOptions -> String -> Pandoc -> Handler
 respondX templ mimetype ext fn opts page doc = do
-  template' <- liftIO $ getDefaultTemplate templ
+  cfg <- getConfig
+  template' <- liftIO $ getDefaultTemplate (pandocUserData cfg) templ
   template <- case template' of
                   Right t  -> return t
                   Left e   -> liftIO $ throwIO e
@@ -65,6 +75,7 @@
 respondConTeXt = respondX "context" "application/x-context" "tex"
   writeConTeXt defaultRespOptions
 
+
 respondRTF :: String -> Pandoc -> Handler
 respondRTF = respondX "rtf" "application/rtf" "rtf"
   writeRTF defaultRespOptions
@@ -73,13 +84,17 @@
 respondRST = respondX "rst" "text/plain; charset=utf-8" ""
   writeRST defaultRespOptions{writerReferenceLinks = True}
 
+respondMarkdown :: String -> Pandoc -> Handler
+respondMarkdown = respondX "markdown" "text/plain; charset=utf-8" ""
+  writeMarkdown defaultRespOptions{writerReferenceLinks = True}
+
 respondMan :: String -> Pandoc -> Handler
 respondMan = respondX "man" "text/plain; charset=utf-8" ""
   writeMan defaultRespOptions
 
-respondS5 :: String -> Pandoc -> Handler
-respondS5 pg doc = do
-  inc <- liftIO $ s5HeaderIncludes
+respondS5 :: Config -> String -> Pandoc -> Handler
+respondS5 cfg pg doc = do
+  inc <- liftIO $ s5HeaderIncludes (pandocUserData cfg)
   respondX "s5" "text/html; charset=utf-8" ""
     writeS5String
     defaultRespOptions{writerS5 = True, writerIncremental = True,
@@ -98,9 +113,9 @@
 respondMediaWiki = respondX "mediawiki" "text/plain; charset=utf-8" ""
   writeMediaWiki defaultRespOptions
 
-respondODT :: String -> Pandoc -> Handler
-respondODT page doc = do
-  template' <- liftIO $ getDefaultTemplate "odt"
+respondODT :: Config -> String -> Pandoc -> Handler
+respondODT cfg page doc = do
+  template' <- liftIO $ getDefaultTemplate (pandocUserData cfg)  "odt"
   template <-  case template' of
                   Right t  -> return t
                   Left e   -> liftIO $ throwIO e
@@ -110,19 +125,83 @@
   conf <- getConfig
   contents <- liftIO $ withTempDir "gitit-temp-odt" $ \tempdir -> do
                 let tempfile = tempdir </> page <.> "odt"
-                saveOpenDocumentAsODT tempfile (repositoryPath conf) Nothing openDoc
-                B.readFile tempfile
+                saveOpenDocumentAsODT (pandocUserData cfg) 
+                   tempfile (repositoryPath conf) Nothing openDoc
+                L.readFile tempfile
   ok $ setContentType "application/vnd.oasis.opendocument.text" $
        setFilename (page ++ ".odt") $ (toResponse noHtml) {rsBody = contents}
 
-exportFormats :: [(String, String -> Pandoc -> Handler)]
-exportFormats = [ ("LaTeX",     respondLaTeX)     -- (description, writer)
+-- | Run shell command and return error status.  Assumes
+-- UTF-8 locale. Note that this does not actually go through \/bin\/sh!
+runShellCommand :: FilePath                     -- ^ Working directory
+                -> Maybe [(String, String)]     -- ^ Environment
+                -> String                       -- ^ Command
+                -> [String]                     -- ^ Arguments
+                -> IO ExitCode
+runShellCommand workingDir environment command optionList = do
+  (errPath, err) <- openTempFile workingDir "err"
+  hProcess <- runProcess (encodeString command) (map encodeString optionList)
+               (Just workingDir) environment Nothing (Just err) (Just err)
+  status <- waitForProcess hProcess
+  removeFile errPath
+  return status
+
+respondPDF :: String -> Pandoc -> Handler
+respondPDF page pndc = do
+  cfg <- getConfig
+  unless (pdfExport cfg) $ error "PDF export disabled"
+  let cacheName = pathForPage page ++ ".export.pdf"
+  cached <- if useCache cfg
+               then lookupCache cacheName
+               else return Nothing
+  pdf' <- case cached of
+            Just (_modtime, bs) -> return $ Right (False, L.fromChunks [bs])
+            Nothing -> liftIO $ withTempDir "gitit-tmp-pdf" $ \tempdir -> do
+              template' <- liftIO $ getDefaultTemplate (pandocUserData cfg) "latex"
+              template  <- either throwIO return template'
+              let toc = tableOfContents cfg
+              let latex = writeLaTeX defaultRespOptions{writerTemplate = template
+                                                       ,writerTableOfContents = toc} pndc
+              let tempfile = page <.> "tex"
+              curdir <- getCurrentDirectory
+              setCurrentDirectory tempdir
+              writeFile tempfile latex
+              -- run pdflatex twice to get the references and toc right
+              let cmd = "pdflatex"
+              oldEnv <- getEnvironment
+              let env = Just $ ("TEXINPUTS",".:" ++ 
+                               escapeStringUsing [(' ',"\\ "),('"',"\\\"")]
+                               (curdir </> repositoryPath cfg) ++ ":") : oldEnv
+              let opts = ["-interaction=batchmode", "-no-shell-escape", tempfile]
+              _ <- runShellCommand tempdir env cmd opts
+              canary <- runShellCommand tempdir env cmd opts
+              setCurrentDirectory curdir -- restore original location
+              case canary of
+                  ExitSuccess   -> do pdfBS <- L.readFile (tempdir </> page <.> "pdf")
+                                      return $ Right (useCache cfg, pdfBS)
+                  ExitFailure n -> do l <- readFile (tempdir </> page <.> "log")
+                                      return $ Left (n, l)
+  case pdf' of
+       Left (n,logOutput) -> simpleErrorHandler ("PDF creation failed with code: " ++
+                               show n ++ "\n" ++ logOutput)
+       Right (needsCaching, pdfBS) -> do
+              when needsCaching $
+                 cacheContents cacheName $ B.concat . L.toChunks $ pdfBS
+              ok $ setContentType "application/pdf" $ setFilename (page ++ ".pdf") $
+                        (toResponse noHtml) {rsBody = pdfBS}
+
+exportFormats :: Config -> [(String, String -> Pandoc -> Handler)]
+exportFormats cfg = if pdfExport cfg
+                       then ("PDF", respondPDF) : rest
+                       else rest
+   where rest = [ ("LaTeX",     respondLaTeX)     -- (description, writer)
                 , ("ConTeXt",   respondConTeXt)
                 , ("Texinfo",   respondTexinfo)
                 , ("reST",      respondRST)
+                , ("markdown",  respondMarkdown)
                 , ("MediaWiki", respondMediaWiki)
                 , ("man",       respondMan)
                 , ("DocBook",   respondDocbook)
-                , ("S5",        respondS5)
-                , ("ODT",       respondODT)
+                , ("S5",        respondS5 cfg)
+                , ("ODT",       respondODT cfg )
                 , ("RTF",       respondRTF) ]
diff --git a/Network/Gitit/Feed.hs b/Network/Gitit/Feed.hs
--- a/Network/Gitit/Feed.hs
+++ b/Network/Gitit/Feed.hs
@@ -17,142 +17,105 @@
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 -}
 
-{- Functions for creating atom feeds for gitit wikis and pages.
--}
+-- | Functions for creating Atom feeds for Gitit wikis and pages.
 
 module Network.Gitit.Feed (FeedConfig(..), filestoreToXmlFeed) where
 
-import Control.Monad
-import Data.DateTime
-import Data.List (intercalate, sortBy)
+import Data.DateTime (addMinutes, formatDateTime, getCurrentTime)
+import Data.Foldable as F (concatMap)
+import Data.List (intercalate, sortBy, nub)
+import Data.Maybe (fromMaybe)
 import Data.Ord (comparing)
 import Network.URI (isUnescapedInURI, escapeURIString)
-import System.FilePath
-
-import Data.FileStore.Types
-
-import Text.Atom.Feed
-import Text.Atom.Feed.Export
-import Text.XML.Light
+import System.FilePath (dropExtension, takeExtension, (<.>))
+import Data.FileStore.Types (history, Author(authorName), Change(..),
+         DateTime, FileStore, Revision(..), TimeRange(..))
+import Text.Atom.Feed (nullEntry, nullFeed, nullLink, nullPerson,
+         Date, Entry(..), Feed(..), Link(linkRel), Generator(..),
+         Person(personName), TextContent(TextString))
+import Text.Atom.Feed.Export (xmlFeed)
+import Text.XML.Light (ppTopElement)
+import Data.Version (showVersion)
+import Paths_gitit (version)
 
 data FeedConfig = FeedConfig {
     fcTitle    :: String
   , fcBaseUrl  :: String
   , fcFeedDays :: Integer
-  } deriving (Show, Read)
+ } deriving (Show, Read)
 
-filestoreToXmlFeed :: FeedConfig -> FileStore -> (Maybe FilePath) -> IO String
-filestoreToXmlFeed cfg f mbPath = filestoreToFeed cfg f mbPath >>= return . ppTopElement . xmlFeed
+filestoreToXmlFeed :: FeedConfig -> FileStore -> Maybe FilePath -> IO String
+filestoreToXmlFeed cfg f = fmap xmlFeedToString . generateFeed cfg f
 
-filestoreToFeed :: FeedConfig -> FileStore -> (Maybe FilePath) -> IO Feed
-filestoreToFeed cfg a mbPath = do
-  let path' = maybe "" id mbPath
-  when (null $ fcBaseUrl cfg) $ error "base-url in the config file is null."
-  rs <- changeLog cfg a mbPath
-{-  let rsShifted = if null rs
-                     then []
-                     else head rs : init rs   -- so we can get revids for diffs
--}
-  let rsShifted = case rs of
-                     [] -> []
-                     (x:_) -> x : init rs   -- so we can get revids for diffs
-  now <- liftM formatFeedTime getCurrentTime
-  return $ Feed { feedId = fcBaseUrl cfg ++ "/" ++ escape path'
-                , feedTitle = TextString $ fcTitle cfg
-                , feedUpdated = now
-                , feedAuthors = []
-                , feedCategories = []
-                , feedContributors = []
-                , feedGenerator = Just Generator{ genURI = Just "http://github.com/jgm/gitit"
-                                                , genVersion = Nothing
-                                                , genText = "gitit" }
-                , feedIcon = Nothing
-                , feedLinks = [ (nullLink (fcBaseUrl cfg ++ "/_feed/" ++ escape path')) {linkRel = Just (Left "self")} ]
-                , feedLogo = Nothing
-                , feedRights = Nothing
-                , feedSubtitle = Nothing
-                , feedAttrs = []
-                , feedOther = []  
-                , feedEntries = reverse $ zipWith (revToEntry cfg path') rs rsShifted }
+xmlFeedToString :: Feed -> String
+xmlFeedToString = ppTopElement . xmlFeed
 
--- | Get the last N days history.
-changeLog :: FeedConfig -> FileStore -> (Maybe FilePath) -> IO [Revision]
-changeLog cfg a mbPath = do
-  let files = maybe [] (\f -> [f, f <.> "page"]) mbPath
+generateFeed :: FeedConfig -> FileStore -> Maybe FilePath -> IO Feed
+generateFeed cfg fs mbPath = do
   now <- getCurrentTime
-  let startTime = addMinutes (-60 * 24 * fcFeedDays cfg) now
-  rs <- history a files TimeRange{timeFrom = Just startTime, timeTo = Just now}
-  return $ sortBy (comparing revDateTime) rs
- 
-revToEntry :: FeedConfig -> String -> Revision -> Revision -> Entry
-revToEntry cfg path' Revision{
-                     revId = rid,
-                     revDateTime = rdt,
-                     revAuthor = ra,
-                     revDescription = rd,
-                     revChanges = rv } prevRevision =
-  baseEntry{ entrySummary = Just $ TextString rd
-           , entryAuthors = [Person { personName = authorName ra
-                                    , personURI = Nothing 
-                                    , personEmail = Nothing
-                                      -- gitit is set up not to reveal registration emails. To change this:
-                                      -- let e = authorEmail ra in if e /= "" then Just e else Nothing
-                                    , personOther = [] }]
-           , entryLinks = [diffLink]
+  revs <- changeLog (fcFeedDays cfg) fs mbPath now
+  let home = fcBaseUrl cfg ++ "/"
+  -- TODO: 'nub . sort' `persons` - but no Eq or Ord instances!
+      persons = map authorToPerson $ nub $ sortBy (comparing authorName) $ map revAuthor revs
+      basefeed = generateEmptyfeed (fcTitle cfg) home mbPath persons (formatFeedTime now)
+      revisions = map (revisionToEntry home) revs
+  return basefeed {feedEntries = revisions}
 
-           -- Comments omitted; needs to be done by Gitit
-           -- only Gitit knows the Url of the Talk: page. See
-           -- http://www.rssboard.org/rss-2-0-1-rv-6#ltcommentsgtSubelementOfLtitemgt
+-- | Get the last N days history.
+changeLog :: Integer -> FileStore -> Maybe FilePath ->DateTime -> IO [Revision]
+changeLog days a mbPath now' = do
+  let files = F.concatMap (\f -> [f, f <.> "page"]) mbPath
+  let startTime = addMinutes (-60 * 24 * days) now'
+  rs <- history a files TimeRange{timeFrom = Just startTime, timeTo = Just now'}
+  return $ sortBy (comparing revDateTime) rs
 
-           -- FIXME: True field seems to tell Guid that it's a 'long-term'/'permanent'
-           -- GUID. This may not be correct. See
-           -- https://secure.wikimedia.org/wikipedia/en/wiki/Globally_Unique_Identifier
-           -- entryId = rid,
+generateEmptyfeed :: String ->String ->Maybe String -> [Person] -> Date -> Feed
+generateEmptyfeed title home mbPath authors now =
+  baseNull {feedAuthors = authors,
+            feedGenerator = Just gititGenerator,
+            feedLinks = [ (nullLink $ home ++ "_feed/" ++ escape (fromMaybe "" mbPath))
+                           {linkRel = Just (Left "self")}]
+            }
+    where baseNull = nullFeed home (TextString title) now
+          gititGenerator :: Generator
+          gititGenerator = Generator {genURI = Just "http://github.com/jgm/gitit"
+                                     , genVersion = Just (showVersion version)
+                                     , genText = "gitit"}
 
-           -- Source is not entirely relevant, and is only handleable by web software,
-           -- not by a filestore-level function. See
-           -- http://www.rssboard.org/rss-2-0-1-rv-6#ltsourcegtSubelementOfLtitemgt
+revisionToEntry :: String -> Revision -> Entry
+revisionToEntry home Revision{ revId = rid, revDateTime = rdt,
+                               revAuthor = ra, revDescription = rd,
+                               revChanges = rv} =
+  baseEntry{ entrySummary = Just $ TextString rd
+           , entryAuthors = [authorToPerson ra], entryLinks = [ln] }
+   where baseEntry = nullEntry url (TextString (intercalate ", " $ map show rv))
+                        (formatFeedTime rdt)
+         url = home ++ escape (extract $ head rv) ++ "?revision=" ++ rid
+         ln = (nullLink url) {linkRel = Just (Left "alternate")}
 
-           -- The following are omitted:
-           -- Category is omitted, see
-           -- http://www.rssboard.org/rss-2-0-1-rv-6#syndic8
-           -- Enclosure seems to be for conveying media, see
-           -- https://secure.wikimedia.org/wikipedia/en/wiki/RSS_enclosure
-        }
-    where diffLink = Link{ linkHref = fcBaseUrl cfg ++ "/_diff/" ++ escape firstpath ++ "?to=" ++ rid ++ fromrev
-                         , linkRel = Just (Left "alternate")
-                         , linkType = Nothing
-                         , linkHrefLang = Nothing
-                         , linkTitle = Nothing
-                         , linkLength = Nothing
-                         , linkAttrs = []
-                         , linkOther = [] } 
-          (firstpath, fromrev) =
-                      if null path'
-                         {- then case head rv of
-                                   Modified f -> (dePage f, "&from=" ++ revId prevRevision)
-                                   Added f    -> (dePage f, "")
-                                   Deleted f  -> (dePage f, "&from=" ++ revId prevRevision)
-                         else (path',"") -}
-                         then case rv of
-                                [] -> error "revToEntry, null rv"
-                                (rev:_) -> case rev of 
-                                   Modified f -> (dePage f, "&from=" ++ revId prevRevision)                                
-                                   Added f    -> (dePage f, "")
-                                   Deleted f  -> (dePage f, "&from=" ++ revId prevRevision)
-                         else (path',"")
-          baseEntry = nullEntry (fcBaseUrl cfg ++ "/" ++ escape path' ++ "?revision=" ++ rid)
-                        (TextString (intercalate ", " $ map showRev rv)) (formatFeedTime rdt)
-          showRev (Modified f) = dePage f
-          showRev (Added f)    = "added " ++ dePage f
-          showRev (Deleted f)  = "deleted " ++ dePage f
-          dePage f = if takeExtension f == ".page"
-                        then dropExtension f
-                        else f
+-- gitit is set up not to reveal registration emails
+authorToPerson :: Author -> Person
+authorToPerson ra = nullPerson {personName = authorName ra}
 
+-- TODO: replace with Network.URI version of shortcut if it ever is added
 escape :: String -> String
 escape = escapeURIString isUnescapedInURI
 
 formatFeedTime :: DateTime -> String
-formatFeedTime = formatDateTime "%Y-%m%--%dT%TZ"  -- Why the double hyphen between %m and %d? It works.
-                                                  -- A single hyphen seems to disappear - I don't know why!
+formatFeedTime = formatDateTime "%FT%TZ"
+
+-- TODO: this boilerplate can be removed by changing Data.FileStore.Types to say
+-- data Change = Modified {extract :: FilePath} | Deleted {extract :: FilePath} | Added
+--                   {extract :: FilePath}
+-- so then it would be just 'escape (extract $ head rv)' without the 4 line definition
+extract :: Change -> FilePath
+extract x = dePage $ case x of {Modified n -> n; Deleted n -> n; Added n -> n}
+          where dePage f = if takeExtension f == ".page" then dropExtension f else f
+
+-- TODO: figure out how to create diff links in a non-broken manner
+{-
+diff :: String -> String -> Revision -> Link
+diff home path' Revision{revId = rid} =
+                        let n = nullLink (home ++ "_diff/" ++ escape path' ++ "?to=" ++ rid) -- ++ fromrev)
+                        in n {linkRel = Just (Left "alternate")}
+-}
diff --git a/Network/Gitit/Framework.hs b/Network/Gitit/Framework.hs
--- a/Network/Gitit/Framework.hs
+++ b/Network/Gitit/Framework.hs
@@ -24,7 +24,6 @@
                                , requireUserThat
                                , requireUser
                                , getLoggedInUser
-                               , sessionTime
                                -- * Combinators to exclude certain actions
                                , unlessNoEdit
                                , unlessNoDelete
@@ -99,10 +98,11 @@
 withUserFromSession :: Handler -> Handler
 withUserFromSession handler = withData $ \(sk :: Maybe SessionKey) -> do
   mbSd <- maybe (return Nothing) getSession sk
+  cfg <- getConfig
   mbUser <- case mbSd of
             Nothing    -> return Nothing
             Just sd    -> do
-              addCookie sessionTime (mkCookie "sid" (show $ fromJust sk))  -- refresh timeout
+              addCookie (sessionTimeout cfg) (mkCookie "sid" (show $ fromJust sk))  -- refresh timeout
               getUser $! sessionUser sd
   let user = maybe "" uUsername mbUser
   localRq (setHeader "REMOTE_USER" user) handler
@@ -147,9 +147,6 @@
   result' <- many (noneOf " \t\n")
   return $ takeWhile (/=':') $ decode result'
 
-sessionTime :: Int
-sessionTime = 60 * 60     -- session will expire 1 hour after page request
-
 -- | @unlessNoEdit responder fallback@ runs @responder@ unless the
 -- page has been designated not editable in configuration; in that
 -- case, runs @fallback@.
@@ -275,9 +272,7 @@
 -- the wiki base.
 urlForPage :: String -> String
 urlForPage page = "/" ++
-  encString True (\c -> isAscii c && (c `notElem` "?&")) (map spaceToPlus page)
-    where spaceToPlus ' ' = '+'
-          spaceToPlus c   = c
+  encString True (\c -> isAscii c && (c `notElem` "?&")) page
 -- / and @ are left unescaped so that browsers recognize relative URLs and talk pages correctly
 
 -- | Returns the filestore path of the file containing the page's source.
diff --git a/Network/Gitit/Handlers.hs b/Network/Gitit/Handlers.hs
--- a/Network/Gitit/Handlers.hs
+++ b/Network/Gitit/Handlers.hs
@@ -680,7 +680,8 @@
                   concatHtml [ anchor ! [theclass "updir",
                                          href $ if length d <= 1
                                                    then base' ++ "/_index"
-                                                   else base' ++ urlForPage (joinPath d)] <<
+                                                   else base' ++
+                                                        urlForPage (joinPath $ drop 1 d)] <<
                   lastNote "fileListToHtml" d, accum]) noHtml updirs
   in uplink +++ ulist ! [theclass "index"] << map fileLink files
 
@@ -770,16 +771,17 @@
   path' <- getPath     -- e.g. "foo/bar" if they hit /_feed/foo/bar
   let file = (path' `orIfNull` "_site") <.> "feed"
   let mbPath = if null path' then Nothing else Just path'
-  fs <- getFileStore
   -- first, check for a cached version that is recent enough
   now <- liftIO $ getClockTime
-  let isRecentEnough t = diffClockTimes now t > noTimeDiff{tdMin = fromIntegral $ feedRefreshTime cfg}
+  let isRecentEnough t = normalizeTimeDiff (diffClockTimes now t) <
+                         normalizeTimeDiff (noTimeDiff{tdMin = fromIntegral $ feedRefreshTime cfg})
   mbCached <- lookupCache file
   case mbCached of
        Just (modtime, contents) | isRecentEnough modtime -> do
             let emptyResponse = setContentType "application/atom+xml; charset=utf-8" . toResponse $ ()
             ok $ emptyResponse{rsBody = B.fromChunks [contents]}
        _ -> do
+            fs <- getFileStore
             resp <- liftM toResponse $ liftIO (filestoreToXmlFeed fc fs mbPath)
             cacheContents file $ S.concat $ B.toChunks $ rsBody resp
             ok . setContentType "application/atom+xml; charset=UTF-8" $ resp
diff --git a/Network/Gitit/Initialize.hs b/Network/Gitit/Initialize.hs
--- a/Network/Gitit/Initialize.hs
+++ b/Network/Gitit/Initialize.hs
@@ -18,8 +18,10 @@
 
 module Network.Gitit.Initialize ( initializeGititState
                                 , recompilePageTemplate
+                                , compilePageTemplate
                                 , createStaticIfMissing
                                 , createRepoIfMissing
+                                , createDefaultPages
                                 , createTemplateIfMissing )
 where
 import System.FilePath ((</>), (<.>))
@@ -115,23 +117,28 @@
            return False
          Left RepositoryExists -> return True
          Left e                -> throwIO e >> return False
-  let pt = defaultPageType conf
-  let toPandoc = readMarkdown
-                 defaultParserState{ stateSanitizeHTML = True
-                                   , stateSmart = True }
-  let defOpts = defaultWriterOptions{
-                        writerStandalone = False
-                      , writerHTMLMathMethod = JsMath
-                               (Just "/js/jsMath/easy/load.js")
-                      , writerLiterateHaskell = showLHSBirdTracks conf
-                      }
-  -- note: we convert this (markdown) to the default page format
-  let converter = case defaultPageType conf of
-                     Markdown -> id
-                     LaTeX    -> writeLaTeX defOpts . toPandoc
-                     HTML     -> writeHtmlString defOpts . toPandoc
-                     RST      -> writeRST defOpts . toPandoc
-  unless repoExists $ do
+  unless repoExists $ createDefaultPages conf
+
+createDefaultPages :: Config -> IO ()
+createDefaultPages conf = do
+    let fs = filestoreFromConfig conf
+        pt = defaultPageType conf
+        toPandoc = readMarkdown
+                   defaultParserState{ stateSanitizeHTML = True
+                                     , stateSmart = True }
+        defOpts = defaultWriterOptions{
+                          writerStandalone = False
+                        , writerHTMLMathMethod = JsMath
+                                 (Just "/js/jsMath/easy/load.js")
+                        , writerLiterateHaskell = showLHSBirdTracks conf
+                        }
+        -- note: we convert this (markdown) to the default page format
+        converter = case defaultPageType conf of
+                       Markdown -> id
+                       LaTeX    -> writeLaTeX defOpts . toPandoc
+                       HTML     -> writeHtmlString defOpts . toPandoc
+                       RST      -> writeRST defOpts . toPandoc
+
     welcomepath <- getDataFileName $ "data" </> "FrontPage" <.> "page"
     welcomecontents <- liftM converter $ readFile welcomepath
     helppath <- getDataFileName $ "data" </> "Help" <.> "page"
@@ -142,12 +149,18 @@
     usersguidepath <- getDataFileName "README.markdown"
     usersguidecontents <- liftM converter $ readFile usersguidepath
     -- add front page, help page, and user's guide
-    create fs (frontPage conf <.> "page") (Author "Gitit" "") "Default front page" welcomecontents
-    logM "gitit" WARNING $ "Added " ++ (frontPage conf <.> "page") ++ " to repository"
-    create fs "Help.page" (Author "Gitit" "") "Default help page" helpcontents
-    logM "gitit" WARNING $ "Added " ++ "Help.page" ++ " to repository"
-    create fs "Gitit User's Guide.page" (Author "Gitit" "") "User's guide (README)" usersguidecontents
-    logM "gitit" WARNING $ "Added " ++ "Gitit User's Guide.page" ++ " to repository"
+    let auth = Author "Gitit" ""
+    createIfMissing fs (frontPage conf <.> "page") auth "Default front page" welcomecontents
+    createIfMissing fs "Help.page" auth "Default help page" helpcontents
+    createIfMissing fs "Gitit User's Guide.page" auth "User's guide (README)" usersguidecontents
+
+createIfMissing :: FileStore -> FilePath -> Author -> Description -> String -> IO ()
+createIfMissing fs p a comm cont = do
+  res <- try $ create fs p a comm cont
+  case res of
+       Right _ -> logM "gitit" WARNING ("Added " ++ p ++ " to repository")
+       Left ResourceExists -> return ()
+       Left e              -> throwIO e >> return ()
 
 -- | Create static directory unless it exists.
 createStaticIfMissing :: Config -> IO ()
diff --git a/Network/Gitit/Layout.hs b/Network/Gitit/Layout.hs
--- a/Network/Gitit/Layout.hs
+++ b/Network/Gitit/Layout.hs
@@ -23,6 +23,8 @@
 module Network.Gitit.Layout ( defaultPageLayout
                             , defaultRenderPage
                             , formattedPage
+                            , filledPageTemplate
+                            , uploadsAllowed
                             )
 where
 import Network.Gitit.Server
@@ -63,23 +65,29 @@
 defaultRenderPage :: T.StringTemplate String -> PageLayout -> Html -> Handler
 defaultRenderPage templ layout htmlContents = do
   cfg <- getConfig
-  let rev  = pgRevision layout
-  let page = pgPageName layout
   base' <- getWikiBase
-  let scripts  = ["jquery.min.js", "jquery-ui.packed.js"] ++ pgScripts layout
-  let scriptLink x = script ! [src (base' ++ "/js/" ++ x),
+  ok . setContentType "text/html; charset=utf-8" . toResponse . T.render .
+       filledPageTemplate base' cfg layout htmlContents $ templ
+
+-- | Returns a page template with gitit variables filled in.
+filledPageTemplate :: String -> Config -> PageLayout -> Html ->
+                      T.StringTemplate String -> T.StringTemplate String 
+filledPageTemplate base' cfg layout htmlContents templ = 
+  let rev  = pgRevision layout
+      page = pgPageName layout
+      scripts  = ["jquery.min.js", "jquery-ui.packed.js"] ++ pgScripts layout
+      scriptLink x = script ! [src (base' ++ "/js/" ++ x),
         thetype "text/javascript"] << noHtml
-  let javascriptlinks = renderHtmlFragment $ concatHtml $ map scriptLink scripts
-  let tabli tab = if tab == pgSelectedTab layout
+      javascriptlinks = renderHtmlFragment $ concatHtml $ map scriptLink scripts
+      tabli tab = if tab == pgSelectedTab layout
                      then li ! [theclass "selected"]
                      else li
-  let tabs' = [x | x <- pgTabs layout,
+      tabs' = [x | x <- pgTabs layout,
                 not (x == EditTab && page `elem` noEdit cfg)]
-  let tabs = ulist ! [theclass "tabs"] << map (linkForTab tabli base' page rev) tabs'
-  let setStrAttr  attr = T.setAttribute attr . stringToHtmlString
-  let setBoolAttr attr test = if test then T.setAttribute attr "true" else id
-  let filledTemp = T.render .
-                   T.setAttribute "base" base' .
+      tabs = ulist ! [theclass "tabs"] << map (linkForTab tabli base' page rev) tabs'
+      setStrAttr  attr = T.setAttribute attr . stringToHtmlString
+      setBoolAttr attr test = if test then T.setAttribute attr "true" else id
+  in               T.setAttribute "base" base' .
                    T.setAttribute "feed" (pgLinkToFeed layout) .
                    setStrAttr "wikititle" (wikiTitle cfg) .
                    setStrAttr "pagetitle" (pgTitle layout) .
@@ -93,24 +101,26 @@
                    setBoolAttr "printable" (pgPrintable layout) .
                    maybe id (T.setAttribute "revision") rev .
                    T.setAttribute "exportbox"
-                       (renderHtmlFragment $  exportBox base' page rev) .
+                       (renderHtmlFragment $  exportBox base' cfg page rev) .
                    T.setAttribute "tabs" (renderHtmlFragment tabs) .
                    T.setAttribute "messages" (pgMessages layout) .
                    T.setAttribute "usecache" (useCache cfg) .
-                   T.setAttribute "content" (renderHtmlFragment htmlContents) $
+                   T.setAttribute "content" (renderHtmlFragment htmlContents) . 
+                   setBoolAttr "wikiupload" ( uploadsAllowed cfg) $
                    templ
-  ok $ setContentType "text/html; charset=utf-8" $ toResponse filledTemp
 
-exportBox :: String -> String -> Maybe String -> Html
-exportBox base' page rev | not (isSourceCode page) =
+
+
+exportBox :: String -> Config -> String -> Maybe String -> Html
+exportBox base' cfg page rev | not (isSourceCode page) =
   gui (base' ++ urlForPage page) ! [identifier "exportbox"] <<
     ([ textfield "revision" ! [thestyle "display: none;",
          value (fromJust rev)] | isJust rev ] ++
      [ select ! [name "format"] <<
-         map ((\f -> option ! [value f] << f) . fst) exportFormats
+         map ((\f -> option ! [value f] << f) . fst) (exportFormats cfg)
      , primHtmlChar "nbsp" 
      , submit "export" "Export" ])
-exportBox _ _ _ = noHtml
+exportBox _ _ _ _ = noHtml
 
 -- auxiliary functions:
 
@@ -145,3 +155,5 @@
                                          then "edit"
                                          else "revert"
 
+uploadsAllowed :: Config -> Bool
+uploadsAllowed = (0 <) . maxUploadSize
diff --git a/Network/Gitit/Plugins.hs b/Network/Gitit/Plugins.hs
--- a/Network/Gitit/Plugins.hs
+++ b/Network/Gitit/Plugins.hs
@@ -30,16 +30,16 @@
 import Data.List (isInfixOf, isPrefixOf)
 import GHC
 import GHC.Paths
-import DynFlags
 import Unsafe.Coerce
 
 loadPlugin :: FilePath -> IO Plugin
 loadPlugin pluginName = do
   logM "gitit" WARNING ("Loading plugin '" ++ pluginName ++ "'...")
-  defaultCleanupHandler defaultDynFlags $
-    runGhc (Just libdir) $ do
-      dflags <- getSessionDynFlags
-      setSessionDynFlags dflags
+  runGhc (Just libdir) $ do
+    dflags <- getSessionDynFlags
+    setSessionDynFlags dflags
+    defaultCleanupHandler dflags $ do
+      -- initDynFlags
       unless ("Network.Gitit.Plugin." `isPrefixOf` pluginName)
         $ do
             addTarget =<< guessTarget pluginName Nothing
diff --git a/Network/Gitit/Types.hs b/Network/Gitit/Types.hs
--- a/Network/Gitit/Types.hs
+++ b/Network/Gitit/Types.hs
@@ -67,6 +67,8 @@
   authHandler          :: Handler,
   -- | Path of users database
   userFile             :: FilePath,
+  -- | Seconds of inactivity before session expires
+  sessionTimeout       :: Int,
   -- | Directory containing page templates
   templatesDir         :: FilePath,
   -- | Path of server log file
@@ -126,7 +128,11 @@
   -- | Number of days history to be included in feed
   feedDays             :: Integer,
   -- | Number of minutes to cache feeds before refreshing
-  feedRefreshTime      :: Integer
+  feedRefreshTime      :: Integer,
+  -- | Allow PDF export?
+  pdfExport            :: Bool,
+  -- | Directory to search for pandoc customizations
+  pandocUserData       :: Maybe FilePath
   }
 
 -- | Data for rendering a wiki page.
diff --git a/README.markdown b/README.markdown
--- a/README.markdown
+++ b/README.markdown
@@ -127,6 +127,10 @@
 Check that it worked: open a web browser and go to
 <http://localhost:5001>.
 
+You can control the port that gitit runs on using the `-p` option:
+`gitit -p 4000` will start gitit on port 4000. Additional runtime
+options are described by `gitit -h`.
+
 Using gitit
 ===========
 
@@ -213,12 +217,16 @@
 Configuration options
 ---------------------
 
-You can set some configuration options when starting gitit, using the
-option `-f [filename]`. To get a copy of the default configuration file,
-which you can customize, just type:
+Use the option `-f [filename]` to specify a configuration file:
 
-    gitit --print-default-config > default.conf
+    gitit -f my.conf
 
+If this option is not used, gitit will use a default configuration.
+To get a copy of the default configuration file, which you
+can customize, just type:
+
+    gitit --print-default-config > my.conf
+
 The default configuration file is documented with comments throughout.
 
 The `static` directory
@@ -365,15 +373,18 @@
 be slightly different.  See the documentation for your VCS for
 details.
 
+Performance
+===========
+
 Caching
-=======
+-------
 
 By default, gitit does not cache content.  If your wiki receives a lot of
 traffic or contains pages that are slow to render, you may want to activate
 caching.  To do this, set the configuration option `use-cache` to `yes`.
-By default, rendered pages and highlighted source files will be cached
-in the `cache` directory. (Another directory can be specified by setting
-the `cache-dir` configuration option.)
+By default, rendered pages, highlighted source files, and exported PDFs
+will be cached in the `cache` directory. (Another directory can be
+specified by setting the `cache-dir` configuration option.)
 
 Cached pages are updated when pages are modified using the web
 interface. They are not updated when pages are modified directly through
@@ -402,6 +413,19 @@
 
 The cache is persistent through restarts of gitit.  To expire all cached
 pages, simply remove the `cache` directory.
+
+Idle
+----
+
+By default, GHC's runtime will repeatedly attempt to collect garbage
+when an executable like Gitit is idle. This means that gitit will, after
+the first page request, never use 0% CPU time and sleep, but will use
+~1%. This can be bad for battery life, among other things.
+
+To fix this, one can disable the idle-time GC with the runtime flag
+`-I0`:
+
+    gitit -f my.conf +RTS -I0 -RTS
 
 Using gitit with apache
 =======================
diff --git a/data/default.conf b/data/default.conf
--- a/data/default.conf
+++ b/data/default.conf
@@ -31,6 +31,9 @@
 # If it does not exist, gitit will create it (with an empty user list).
 # This file is not used if 'http' is selected for authentication-method.
 
+session-timeout: 60
+# number of minutes of inactivity before a session expires.
+
 static-dir: static
 # specifies the path of the static directory (containing javascript,
 # css, and images).  If it does not exist, gitit will create it
@@ -47,7 +50,7 @@
 # rendered correctly if RST is selected. The same goes for LaTeX and
 # HTML.
 
-math: MathML
+math: RawTeX
 # specifies how LaTeX math is to be displayed.  Possible values
 # are MathML, raw, and jsMath.  If mathml is selected, gitit will
 # convert LaTeX math to MathML and link in a script, MathMLinHTML.js,
@@ -125,6 +128,9 @@
 max-upload-size: 100K
 # specifies an upper limit on the size (in bytes) of files uploaded
 # through the wiki's web interface.
+# To disable uploads, set this to 0K.
+# This will result in the uploads link disappearing 
+# and the _upload url becoming inactive.
 
 debug-mode: no
 # if "yes", causes debug information to be logged while gitit is running.
@@ -209,3 +215,17 @@
 
 feed-refresh-time: 60
 # number of minutes to cache feeds before refreshing
+
+pdf-export: no
+# if yes, PDF will appear in export options. PDF will be created using
+# pdflatex, which must be installed and in the path. Note that PDF
+# exports create significant additional server load.
+
+pandoc-user-data:
+# if a directory is specified, this will be searched for pandoc
+# customizations. These can include a templates/ directory for custom
+# templates for various export formats, an S5 directory for custom
+# S5 styles, and a reference.odt for ODT exports. If no directory is
+# specified, $HOME/.pandoc will be searched. See pandoc's README for
+# more information.
+
diff --git a/data/templates/sitenav.st b/data/templates/sitenav.st
--- a/data/templates/sitenav.st
+++ b/data/templates/sitenav.st
@@ -7,7 +7,9 @@
       <li><a href="$base$/_categories">Categories</a></li>
       <li><a href="$base$/_random">Random page</a></li>
       <li><a href="$base$/_activity">Recent activity</a></li>
-      <li><a href="$base$/_upload">Upload a file</a></li>
+      $if(wikiupload)$
+        <li><a href="$base$/_upload">Upload a file</a></li>
+      $endif$
       $if(feed)$
       <li><a href="$base$/_feed/" type="application/atom+xml" rel="alternate" title="ATOM Feed">Atom feed</a> <img alt="feed icon" src="$base$/img/icons/feed.png"/></li>
       $endif$
diff --git a/gitit.cabal b/gitit.cabal
--- a/gitit.cabal
+++ b/gitit.cabal
@@ -1,5 +1,5 @@
 name:                gitit
-version:             0.7.2.1
+version:             0.7.3
 Cabal-version:       >= 1.2
 build-type:          Simple
 synopsis:            Wiki using happstack, git or darcs, and pandoc.
@@ -77,6 +77,7 @@
                      plugins/WebArchiver.hs,
                      plugins/ShowUser.hs,
                      plugins/Signature.hs,
+                     plugins/Subst.hs,
                      CHANGES, README.markdown, YUI-LICENSE, BLUETRIP-LICENSE, TANGOICONS
 
 Flag plugins
@@ -89,8 +90,9 @@
   hs-source-dirs:    .
   exposed-modules:   Network.Gitit, Network.Gitit.ContentTransformer,
                      Network.Gitit.Types, Network.Gitit.Framework,
-                     Network.Gitit.Initialize, Network.Gitit.Config
-  other-modules:     Network.Gitit.Layout, Network.Gitit.Cache, Network.Gitit.State,
+                     Network.Gitit.Initialize, Network.Gitit.Config,
+                     Network.Gitit.Layout
+  other-modules:     Network.Gitit.Cache, Network.Gitit.State,
                      Paths_gitit, Network.Gitit.Server, Network.Gitit.Export,
                      Network.Gitit.Util, Network.Gitit.Handlers, Network.Gitit.Plugins,
                      Network.Gitit.Authentication, Network.Gitit.Page, Network.Gitit.Feed
@@ -98,7 +100,7 @@
     exposed-modules: Network.Gitit.Interface
     build-depends:   ghc, ghc-paths
     cpp-options:     -D_PLUGINS
-  build-depends:     base >= 3, pandoc >= 1.4 && < 1.5, filepath, safe
+  build-depends:     base >= 3, pandoc >= 1.5, filepath, safe
   extensions:        CPP
   if impl(ghc >= 6.12)
     ghc-options:     -Wall -fno-warn-unused-do-bind
@@ -110,14 +112,20 @@
   hs-source-dirs:    .
   main-is:           gitit.hs
   build-depends:     base >=3 && < 5, parsec < 3, pretty, xhtml, containers,
-                     pandoc >= 1.2, process, filepath, directory, mtl, cgi,
-                     network, old-time, highlighting-kate, bytestring,
-                     utf8-string, SHA > 1, HTTP, HStringTemplate, random,
-                     network >= 2.1.0.0, recaptcha >= 0.1, filestore >= 0.3.4,
-                     datetime, zlib, url, happstack-server >= 0.3.3 && < 0.5,
+                     pandoc >= 1.5, process, filepath, directory, mtl, cgi,
+                     network, old-time, highlighting-kate >= 0.2.6, bytestring,
+                     utf8-string >= 0.3 && < 0.4,
+                     SHA > 1 && < 1.5, HTTP >= 4000.0 && < 4000.1,
+                     HStringTemplate >= 0.6 && < 0.7, random,
+                     network >= 2.1.0.0 && < 2.3,
+                     recaptcha >= 0.1, filestore >= 0.3.4,
+                     datetime >= 0.1 && < 0.2, zlib >= 0.5 && < 0.6,
+                     url >= 2.1 && < 2.2,
+                     happstack-server >= 0.3.3 && < 0.5,
                      happstack-util >= 0.3.2 && < 0.5, xml >= 1.3.5,
-                     hslogger >= 1 && < 1.1, ConfigFile >= 1, feed >= 0.3.6,
-                     cautious-file >= 0.1.5 && < 0.2, texmath
+                     hslogger >= 1 && < 1.1, ConfigFile >= 1 && < 1.1,
+                     feed >= 0.3.6 && < 0.4,
+                     cautious-file >= 0.1.5 && < 0.2
   if impl(ghc >= 6.10)
     build-depends:   base >= 4, syb
   if flag(plugins)
diff --git a/plugins/CapitalizeEmphasis.hs b/plugins/CapitalizeEmphasis.hs
--- a/plugins/CapitalizeEmphasis.hs
+++ b/plugins/CapitalizeEmphasis.hs
@@ -11,7 +11,7 @@
 plugin = mkPageTransform capsTransform
 
 capsTransform :: [Inline] -> [Inline]
-capsTransform ((Emph x):xs) = processWith capStr x ++ capsTransform xs
+capsTransform (Emph x : xs) = processWith capStr x ++ capsTransform xs
 capsTransform (x:xs)        = x : capsTransform xs
 capsTransform []            = []
 
diff --git a/plugins/Deprofanizer.hs b/plugins/Deprofanizer.hs
--- a/plugins/Deprofanizer.hs
+++ b/plugins/Deprofanizer.hs
@@ -13,6 +13,6 @@
 deprofanize x                     = x
 
 isBadWord :: String -> Bool
-isBadWord x = (map toLower x) `elem` ["darn", "blasted", "stinker"]
+isBadWord x = map toLower x `elem` ["darn", "blasted", "stinker"]
 -- there are more, but this is a family program
 
diff --git a/plugins/Dot.hs b/plugins/Dot.hs
--- a/plugins/Dot.hs
+++ b/plugins/Dot.hs
@@ -13,13 +13,13 @@
 -- of the file contents.
 
 import Network.Gitit.Interface
-import System.Process
-import System.Exit
+import System.Process (readProcessWithExitCode)
+import System.Exit (ExitCode(ExitSuccess))
 -- from the utf8-string package on HackageDB:
 import Data.ByteString.Lazy.UTF8 (fromString)
 -- from the SHA package on HackageDB:
-import Data.Digest.Pure.SHA
-import System.FilePath
+import Data.Digest.Pure.SHA (sha1, showDigest)
+import System.FilePath ((</>))
 import Control.Monad.Trans (liftIO)
 
 plugin :: Plugin
diff --git a/plugins/ImgTex.hs b/plugins/ImgTex.hs
--- a/plugins/ImgTex.hs
+++ b/plugins/ImgTex.hs
@@ -26,11 +26,8 @@
 -}
 
 import Network.Gitit.Interface
-import Text.Pandoc.Shared
 import System.Process (system)
-import System.Exit
 import System.Directory
-import Data.Char (ord)
 import Data.ByteString.Lazy.UTF8 (fromString)
 import Data.Digest.Pure.SHA
 import System.FilePath
@@ -39,22 +36,21 @@
 plugin :: Plugin
 plugin = mkPageTransformM transformBlock
 
-templateHeader =
-    ( "\\documentclass[12pt]{article}\n"
-      ++ "\\usepackage{amsmath,amssymb,bm}\n"
-      ++ "\\begin{document}\n"
-      ++ "\\thispagestyle{empty}\n"
-      ++ "\\[\n"
-    )
+templateHeader, templateFooter :: String
+templateHeader = concat
+    [ "\\documentclass[12pt]{article}\n"
+      , "\\usepackage{amsmath,amssymb,bm}\n"
+      , "\\begin{document}\n"
+      , "\\thispagestyle{empty}\n"
+      , "\\[\n"]
 
 templateFooter =
-    ( "\n"
+      "\n"
       ++ "\\]\n"
       ++ "\\end{document}\n"
-    )
 
 transformBlock :: Block -> PluginM Block
-transformBlock (CodeBlock (id, classes, namevals) contents)
+transformBlock (CodeBlock (_, classes, namevals) contents)
     | "dvipng" `elem` classes = do
   cfg <- askConfig
   let (name, outfile) =  case lookup "name" namevals of
diff --git a/plugins/Interwiki.hs b/plugins/Interwiki.hs
--- a/plugins/Interwiki.hs
+++ b/plugins/Interwiki.hs
@@ -39,7 +39,7 @@
     ('!':interwiki') ->
         case M.lookup interwiki' interwikiMap of
                 Just url  -> case article of
-                                  "" -> Link ref (url ++ (inlinesToURL ref), (summary $ unEscapeString $ inlinesToURL ref))
+                                  "" -> Link ref (url ++ inlinesToURL ref, summary $ unEscapeString $ inlinesToURL ref)
                                   _  -> Link ref (interwikiurl article url, summary article)
                 Nothing -> Link ref (interwiki, article)
             where -- 'http://starwars.wikia.com/wiki/Emperor_Palpatine'
@@ -61,7 +61,7 @@
                       ("Hoogle", "http://www.haskell.org/hoogle/?hoogle=")]
 
 -- This mapping is derived from <https://secure.wikimedia.org/wikipedia/meta/wiki/Interwiki_map>
--- as of 11:19 PM, 11 February 2009.
+-- as of 3:16 PM, 7 February 2010.
 wpInterwikiMap = [ ("AbbeNormal", "http://ourpla.net/cgi/pikie?"),
                  ("Acronym", "http://www.acronymfinder.com/af-query.asp?String=exact&Acronym="),
                  ("Advisory", "http://advisory.wikimedia.org/wiki/"),
@@ -81,11 +81,13 @@
                  ("betawiki", "http://translatewiki.net/wiki/"),
                  ("BibleWiki", "http://bible.tmtm.com/wiki/"),
                  ("BluWiki", "http://www.bluwiki.org/go/"),
+                 ("BLW", "http://britainloveswikipedia.org/wiki/"),
                  ("Botwiki", "http://botwiki.sno.cc/wiki/"),
                  ("Boxrec", "http://www.boxrec.com/media/index.php?"),
                  ("BrickWiki", "http://brickwiki.org/index.php?title="),
                  ("BridgesWiki", "http://c2.com:8000/"),
                  ("bugzilla", "https://bugzilla.wikimedia.org/show_bug.cgi?id="),
+                 ("bulba", "http://bulbapedia.bulbagarden.net/wiki/"),
                  ("buzztard", "http://buzztard.org/index.php/"),
                  ("Bytesmiths", "http://www.Bytesmiths.com/wiki/"),
                  ("C2", "http://c2.com/cgi/wiki?"),
@@ -112,6 +114,7 @@
                  ("Consciousness", "http://teadvus.inspiral.org/index.php/"),
                  ("CorpKnowPedia", "http://corpknowpedia.org/wiki/index.php/"),
                  ("CrazyHacks", "http://www.crazy-hacks.org/wiki/index.php?title="),
+                 ("Creative Commons", "http://www.creativecommons.org/licenses/"),
                  ("CreaturesWiki", "http://creatures.wikia.com/wiki/"),
                  ("CxEJ", "http://esperanto.blahus.cz/cxej/vikio/index.php/"),
                  ("DAwiki", "http://www.dienstag-abend.de/wiki/index.php/"),
@@ -149,6 +152,7 @@
                  ("ELibre", "http://enciclopedia.us.es/index.php/"),
                  ("EmacsWiki", "http://www.emacswiki.org/cgi-bin/wiki.pl?"),
                  ("EnergieWiki", "http://www.netzwerk-energieberater.de/wiki/index.php/"),
+                 ("Encyc", "http://encyc.org/wiki/"),
                  ("EoKulturCentro", "http://esperanto.toulouse.free.fr/nova/wikini/wakka.php?wiki="),
                  ("Ethnologue", "http://www.ethnologue.com/show_language.asp?code="),
                  ("EvoWiki", "http://wiki.cotch.net/index.php/"),
@@ -183,7 +187,8 @@
                  ("GoogleGroups", "http://groups.google.com/groups?q="),
                  ("GotAMac", "http://www.got-a-mac.org/"),
                  ("GreatLakesWiki", "http://greatlakeswiki.org/index.php/"),
-                 ("Guildwiki", "http://gw.gamewikis.org/wiki/"),
+                 ("GuildWarsWiki", "http://www.wiki.guildwars.com/wiki/"),
+                 ("Guildwiki", "http://guildwars.wikia.com/wiki/"),
                  ("gutenberg", "http://www.gutenberg.org/etext/"),
                  ("gutenbergwiki", "http://www.gutenberg.org/wiki/"),
                  ("H2Wiki", "http://halowiki.net/p/"),
@@ -228,11 +233,11 @@
                  ("LiteratePrograms", "http://en.literateprograms.org/"),
                  ("Livepedia", "http://www.livepedia.gr/index.php?title="),
                  ("Lojban", "http://www.lojban.org/tiki/tiki-index.php?page="),
-                 ("Lostpedia", "http://en.lostpedia.com/wiki/"),
+                 ("Lostpedia", "http://lostpedia.wikia.com/wiki/"),
                  ("LQWiki", "http://wiki.linuxquestions.org/wiki/"),
                  ("LugKR", "http://lug-kr.sourceforge.net/cgi-bin/lugwiki.pl?"),
                  ("Luxo", "http://toolserver.org/~luxo/contributions/contributions.php?user="),
-                 ("lyricwiki", "http://www.lyricwiki.org/"),
+                 ("lyricwiki", "http://lyrics.wikia.com/"),
                  ("Mail", "https://lists.wikimedia.org/mailman/listinfo/"),
                  ("mailarchive", "http://lists.wikimedia.org/pipermail/"),
                  ("Mariowiki", "http://www.mariowiki.com/"),
@@ -249,7 +254,7 @@
                  ("MozCom", "http://mozilla.wikia.com/wiki/"),
                  ("MozillaWiki", "http://wiki.mozilla.org/"),
                  ("MozillaZineKB", "http://kb.mozillazine.org/"),
-                 ("MusicBrainz", "http://wiki.musicbrainz.org/"),
+                 ("MusicBrainz", "http://musicbrainz.org/doc/"),
                  ("MW", "http://www.mediawiki.org/wiki/"),
                  ("MWOD", "http://www.merriam-webster.com/cgi-bin/dictionary?book=Dictionary&va="),
                  ("MWOT", "http://www.merriam-webster.com/cgi-bin/thesaurus?book=Thesaurus&va="),
@@ -261,8 +266,8 @@
                  ("OldWikisource", "http://wikisource.org/wiki/"),
                  ("OLPC", "http://wiki.laptop.org/go/"),
                  ("OneLook", "http://www.onelook.com/?ls=b&w="),
-                 ("OpenFacts", "http://openfacts.berlios.de/index.phtml?title="),
-                 ("Openstreetmap", "http://wiki.openstreetmap.org/index.php/"),
+                 ("OpenFacts", "http://openfacts.berlios.de/index-en.phtml?title="),
+                 ("Openstreetmap", "http://wiki.openstreetmap.org/wiki/"),
                  ("OpenWetWare", "http://openwetware.org/wiki/"),
                  ("OpenWiki", "http://openwiki.com/?"),
                  ("Opera7Wiki", "http://operawiki.info/"),
@@ -270,9 +275,10 @@
                  ("OrgPatterns", "http://www.bell-labs.com/cgi-user/OrgPatterns/OrgPatterns?"),
                  ("OrthodoxWiki", "http://orthodoxwiki.org/"),
                  ("OSI", "http://wiki.tigma.ee/index.php/"),
-                 ("OTRS", "https://secure.wikimedia.org/otrs/index.pl?Action=AgentTicketZoom&TicketID="),
+                 ("OTRS", "https://ticket.wikimedia.org/otrs/index.pl?Action=AgentTicketZoom&TicketID="),
                  ("OTRSwiki", "http://otrs-wiki.wikimedia.org/wiki/"),
                  ("OurMedia", "http://www.socialtext.net/ourmedia/index.cgi?"),
+                 ("OutreachWiki", "http://outreach.wikimedia.org/wiki/"),
                  ("PaganWiki", "http://www.paganwiki.org/wiki/index.php?title="),
                  ("Panawiki", "http://wiki.alairelibre.net/wiki/"),
                  ("PangalacticOrg", "http://www.pangalactic.org/Wiki/"),
@@ -286,6 +292,7 @@
                  ("PMEG", "http://www.bertilow.com/pmeg/.php"),
                  ("PMWiki", "http://old.porplemontage.com/wiki/index.php/"),
                  ("PurlNet", "http://purl.oclc.org/NET/"),
+                 ("pyrev", "http://svn.wikimedia.org/viewvc/pywikipedia?view=rev&revision="),
                  ("PythonInfo", "http://www.python.org/cgi-bin/moinmoin/"),
                  ("PythonWiki", "http://www.pythonwiki.de/"),
                  ("PyWiki", "http://c2.com/cgi/wiki?"),
@@ -306,7 +313,7 @@
                  ("rtfm", "http://s23.org/wiki/"),
                  ("Scholar", "http://scholar.google.com/scholar?q="),
                  ("SchoolsWP", "http://schools-wikipedia.org/wiki/"),
-                 ("Scores", "http://www.imslp.org/wiki/"),
+                 ("Scores", "http://imslp.org/wiki/"),
                  ("Scoutwiki", "http://en.scoutwiki.org/"),
                  ("Scramble", "http://www.scramble.nl/wiki/index.php?title="),
                  ("SeaPig", "http://www.seapig.org/"),
@@ -315,7 +322,6 @@
                  ("SLWiki", "http://wiki.secondlife.com/wiki/"),
                  ("SenseisLibrary", "http://senseis.xmp.net/?"),
                  ("silcode", "http://www.sil.org/iso639-3/documentation.asp?id="),
-                 ("Shakti", "http://cgi.algonet.se/htbin/cgiwrap/pgd/ShaktiWiki/"),
                  ("Slashdot", "http://slashdot.org/article.pl?sid="),
                  ("SMikipedia", "http://www.smiki.de/"),
                  ("SourceForge", "http://sourceforge.net/"),
@@ -323,6 +329,7 @@
                  ("Species", "http://species.wikimedia.org/wiki/"),
                  ("Squeak", "http://wiki.squeak.org/squeak/"),
                  ("stable", "http://stable.toolserver.org/"),
+                 ("Strategy", "http://strategy.wikimedia.org/wiki/"),
                  ("StrategyWiki", "http://strategywiki.org/wiki/"),
                  ("Sulutil", "http://toolserver.org/~vvv/sulutil.php?user="),
                  ("Susning", "http://www.susning.nu/"),
@@ -341,10 +348,9 @@
                  ("Testwiki", "http://test.wikipedia.org/wiki/"),
                  ("Thelemapedia", "http://www.thelemapedia.org/index.php/"),
                  ("Theopedia", "http://www.theopedia.com/"),
-                 ("ThePPN", "http://wiki.theppn.org/"),
                  ("ThinkWiki", "http://www.thinkwiki.org/wiki/"),
                  ("TibiaWiki", "http://tibia.erig.net/"),
-                 ("Ticket", "https://secure.wikimedia.org/otrs/index.pl?Action=AgentTicketZoom&TicketNumber="),
+                 ("Ticket", "https://ticket.wikimedia.org/otrs/index.pl?Action=AgentTicketZoom&TicketNumber="),
                  ("TMBW", "http://tmbw.net/wiki/"),
                  ("TmNet", "http://www.technomanifestos.net/?"),
                  ("TMwiki", "http://www.EasyTopicMaps.com/?page="),
@@ -364,6 +370,7 @@
                  ("Urbandict", "http://www.urbandictionary.com/define.php?term="),
                  ("USEJ", "http://www.tejo.org/usej/"),
                  ("UseMod", "http://www.usemod.com/cgi-bin/wiki.pl?"),
+                 ("usability", "http://usability.wikimedia.org/wiki/"),
                  ("ValueWiki", "http://www.valuewiki.com/w/"),
                  ("Veropedia", "http://en.veropedia.com/a/"),
                  ("Vinismo", "http://vinismo.com/en/"),
@@ -408,6 +415,7 @@
                  ("Wikisource", "http://en.wikisource.org/wiki/"),
                  ("Wikispecies", "http://species.wikimedia.org/wiki/"),
                  ("Wikispot", "http://wikispot.org/?action=gotowikipage&v="),
+                 ("Wikitech", "https://wikitech.wikimedia.org/view/"),
                  ("WikiTI", "http://wikiti.denglend.net/index.php?title="),
                  ("WikiTravel", "http://wikitravel.org/en/"),
                  ("WikiTree", "http://wikitree.org/index.php?title="),
@@ -417,16 +425,21 @@
                  ("Wiktionary", "http://en.wiktionary.org/wiki/"),
                  ("Wipipedia", "http://www.londonfetishscene.com/wipi/index.php/"),
                  ("WLUG", "http://www.wlug.org.nz/"),
+                 ("wmau", "http://wikimedia.org.au/wiki/"),
                  ("wmcz", "http://meta.wikimedia.org/wiki/Wikimedia_Czech_Republic/"),
+                 ("wmno", "http://no.wikimedia.org/wiki/"),
+                 ("wmrs", "http://rs.wikimedia.org/wiki/"),
+                 ("wmse", "http://se.wikimedia.org/wiki/"),
+                 ("wmuk", "http://uk.wikimedia.org/wiki/"),
                  ("Wm2005", "http://wikimania2005.wikimedia.org/wiki/"),
                  ("Wm2006", "http://wikimania2006.wikimedia.org/wiki/"),
                  ("Wm2007", "http://wikimania2007.wikimedia.org/wiki/"),
                  ("Wm2008", "http://wikimania2008.wikimedia.org/wiki/"),
                  ("Wm2009", "http://wikimania2009.wikimedia.org/wiki/"),
+                 ("Wm2010", "http://wikimania2010.wikimedia.org/wiki/"),
+                 ("Wm2011", "http://wikimania2011.wikimedia.org/wiki/"),
                  ("Wmania", "http://wikimania.wikimedia.org/wiki/"),
                  ("WMF", "http://wikimediafoundation.org/wiki/"),
-                 ("wmse", "http://se.wikimedia.org/wiki/"),
-                 ("wmrs", "http://rs.wikimedia.org/wiki/"),
                  ("Wookieepedia", "http://starwars.wikia.com/wiki/"),
                  ("World66", "http://www.world66.com/"),
                  ("WoWWiki", "http://www.wowwiki.com/"),
diff --git a/plugins/PigLatin.hs b/plugins/PigLatin.hs
--- a/plugins/PigLatin.hs
+++ b/plugins/PigLatin.hs
@@ -21,9 +21,10 @@
   Str (cs ++ (c : "ay"))
 pigLatinStr (Str (c:cs)) | isUpper c && isConsonant c =
   Str (capitalize cs ++ (toLower c : "ay"))
-pigLatinStr (Str x@(c:cs)) | isLetter c = Str (x ++ "yay")
+pigLatinStr (Str x@(c:_)) | isLetter c = Str (x ++ "yay")
 pigLatinStr x       = x
 
+isConsonant :: Char -> Bool
 isConsonant c = c `notElem` "aeiouAEIOU" 
 
 capitalize :: String -> String
diff --git a/plugins/ShowUser.hs b/plugins/ShowUser.hs
--- a/plugins/ShowUser.hs
+++ b/plugins/ShowUser.hs
@@ -4,7 +4,6 @@
 -- user, or the empty string if no one is logged in.
 
 import Network.Gitit.Interface
-import Data.Maybe (fromMaybe)
 
 plugin :: Plugin
 plugin = mkPageTransformM showuser
diff --git a/plugins/Signature.hs b/plugins/Signature.hs
--- a/plugins/Signature.hs
+++ b/plugins/Signature.hs
@@ -4,9 +4,7 @@
 -- of the last edit, prior to saving the page in the repository.
 
 import Network.Gitit.Interface
-import Data.Maybe (fromMaybe)
-import Data.DateTime
-import Control.Monad
+import Data.DateTime (getCurrentTime, formatDateTime)
 
 plugin :: Plugin
 plugin = PreCommitTransform replacedate
@@ -14,12 +12,12 @@
 replacedate :: String -> PluginM String
 replacedate [] = return ""
 replacedate ('$':'S':'I':'G':'$':xs) = do
-  datetime <- liftIO $ getCurrentTime
+  datetime <- liftIO getCurrentTime
   mbuser <- askUser
   let username = case mbuser of
                    Nothing  -> "???"
                    Just u   -> uUsername u
-  let sig = "-- " ++ username ++ " (" ++ formatDateTime "%c" datetime ++ ")"
-  liftM (sig ++ ) $ replacedate xs
-replacedate (x:xs) = liftM (x : ) $ replacedate xs
+  let sig = concat ["-- ", username, " (", formatDateTime "%c" datetime, ")"]
+  fmap (sig ++ ) $ replacedate xs
+replacedate (x:xs) = fmap (x : ) $ replacedate xs
 
diff --git a/plugins/Subst.hs b/plugins/Subst.hs
new file mode 100644
--- /dev/null
+++ b/plugins/Subst.hs
@@ -0,0 +1,27 @@
+-- Usage:  a paragraph containing just [My page](!subst)
+-- will be replaced by the contents of My page.
+--
+-- Limitations:  it is assumed that My page is
+-- formatted with markdown, and contains no metadata.
+
+module Subst (plugin) where
+
+import Data.FileStore (retrieve)
+import Text.Pandoc (defaultParserState, readMarkdown)
+import Network.Gitit.ContentTransformer (inlinesToString)
+import Network.Gitit.Interface
+import Network.Gitit.Framework (filestoreFromConfig)
+
+plugin :: Plugin
+plugin = mkPageTransformM substituteIntoBlock
+
+substituteIntoBlock :: [Block] -> PluginM [Block]
+substituteIntoBlock (Para (Link ref ("!subst", _):_ ):xs) = 
+     do let target = inlinesToString ref ++ ".page"
+        cfg <- askConfig
+        let fs = filestoreFromConfig cfg
+        article <- liftIO (retrieve fs target Nothing)
+        let (Pandoc _ content) = readMarkdown defaultParserState article
+        (content ++) `fmap` substituteIntoBlock xs
+substituteIntoBlock (x:xs) = (x:) `fmap` substituteIntoBlock xs
+substituteIntoBlock [] = return []
diff --git a/plugins/WebArchiver.hs b/plugins/WebArchiver.hs
--- a/plugins/WebArchiver.hs
+++ b/plugins/WebArchiver.hs
@@ -49,8 +49,9 @@
          ignore = fmap $ const ()
 
 alexaArchive :: String -> IO ()
-alexaArchive url = do let archiveform = Form POST (fromJust $ parseURI "http://www.alexa.com/help/crawlrequest")
-                                                                             [("url", url), ("submit", "")]
+alexaArchive url = do let archiveform = Form POST
+                             (fromJust $ parseURI "http://www.alexa.com/help/crawlrequest")
+                                 [("url", url), ("submit", "")]
                       (uri, resp) <- browse $ request $ formToRequest archiveform
                       when (uriPath uri /= "/help/crawlthanks") $
                            error $ "Request failed! Did Alexa change webpages? Response:" ++ rspBody resp
