yesod/demo/subsite/Wiki.hs
2013-03-18 14:08:04 +02:00

148 lines
5.8 KiB
Haskell

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- | Define the dispatch for a Wiki. You should probably start off by reading
-- WikiRoutes.
module Wiki
( module WikiRoutes
) where
import Control.Applicative ((<$>))
import Control.Monad (unless)
import Data.IORef.Lifted (readIORef, atomicModifyIORef)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Text (Text)
import WikiRoutes
import Yesod
-- | A subsite needs to be an instance of YesodSubDispatch, which states how to
-- dispatch. By using constraints, we can make requirements of our master site.
-- In this example, we're saying that the master site must be an instance of
-- YesodWiki.
instance YesodWiki master => YesodSubDispatch Wiki (HandlerT master IO) where
-- | This is all the TH magic for dispatch. WikiRoutes provides the
-- resourcesWiki value automatically, and mkYesodSubDispatch will generate
-- a dispatch function that will call out to the appropriate handler
-- functions.
yesodSubDispatch = $(mkYesodSubDispatch resourcesWiki)
-- | Helper type synonym to be used below.
type WikiHandler a = forall master. YesodWiki master
=> HandlerT Wiki (HandlerT master IO) a
------------- Helper functions
-- | Get all of the content in the Wiki.
getContent :: WikiHandler (Map Texts Textarea)
getContent = getYesod >>= readIORef . wikiContent
-- | Put a single new value into the Wiki.
putContent :: Texts -> Textarea -> WikiHandler ()
putContent k v = do
refMap <- wikiContent <$> getYesod
atomicModifyIORef refMap $ \m -> (Map.insert k v m, ())
-- | Gets the homepage, which lists all of the pages available.
getWikiHomeR :: WikiHandler TypedContent
getWikiHomeR = do
content <- getContent
-- We use the new selectRep/provideRep functionality to provide either an
-- HTML or JSON representation of the page. You could just as easily
-- provide YAML, plain text, RSS, or anything else.
selectRep $ do
provideRep $ do
-- We'll use toParent to convert Wiki routes into our master site
-- routes.
toParent <- getRouteToParent
-- Run the master site's defaultLayout to style the page.
lift $ defaultLayout
[whamlet|
<p>This wiki has the following pages:
<ul>
$forall page <- Map.keys content
<li>
-- Notice the usage of toParent!
<a href=@{toParent $ WikiReadR page}>#{show page}
|]
-- You provide a JSON representation just by returning a JSON value.
-- aeson's toJSON make it easy to convert a list of values into JSON.
provideRep $ return $ toJSON $ Map.keys content
getWikiReadR :: Texts -> WikiHandler TypedContent
getWikiReadR page = do
content <- getContent
selectRep $ do
provideRep $
case Map.lookup page content of
Nothing -> do
setMessage $ "Page does not exist, please create it."
-- We don't need to convert or lift here: we're using a
-- route from our subsite, and redirect lives in our
-- subsite.
redirect $ WikiEditR page
Just t -> do
toParent <- getRouteToParent
-- Notice that we lift the canEditPage function from the
-- master site.
canEdit <- lift $ canEditPage page
lift $ defaultLayout
[whamlet|
<article>#{t}
$if canEdit
<p>
<a href=@{toParent $ WikiEditR page}>Edit
|]
provideRep $ return $ toJSON $
case Map.lookup page content of
-- Our HTML representation sends a redirect if the page isn't
-- found, but our JSON representation just returns a JSON value
-- instead.
Nothing -> object ["error" .= ("Page not found" :: Text)]
Just (Textarea t) -> object ["content" .= t]
getWikiEditR :: Texts -> WikiHandler Html
getWikiEditR page = do
canEdit <- lift $ canEditPage page
unless canEdit $ permissionDenied "You do not have permissions to edit this page."
content <- getContent
let form = renderTable
$ areq textareaField "Content" (Map.lookup page content)
-- We need to use lift here since the widget will be used below.
-- Practically speaking, this means that we'll be rendering form messages
-- using the master site's translation functions.
((res, widget), enctype) <- lift $ runFormPost form
case res of
FormSuccess t -> do
putContent page t
setMessage "Content updated"
redirect $ WikiEditR page
_ -> do
toParent <- getRouteToParent
lift $ defaultLayout
[whamlet|
<p>
<a href=@{toParent $ WikiReadR page}>Read page
<form method=post action=@{toParent $ WikiEditR page} enctype=#{enctype}>
<table>
^{widget}
<tr>
<td colspan=2>
<button>Update page
|]
postWikiEditR :: Texts -> WikiHandler Html
postWikiEditR = getWikiEditR