Wiki.hs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. {-# LANGUAGE FlexibleInstances #-}
  2. {-# LANGUAGE MultiParamTypeClasses #-}
  3. {-# LANGUAGE OverloadedStrings #-}
  4. {-# LANGUAGE QuasiQuotes #-}
  5. {-# LANGUAGE RankNTypes #-}
  6. {-# LANGUAGE TemplateHaskell #-}
  7. {-# LANGUAGE TypeFamilies #-}
  8. {-# OPTIONS_GHC -fno-warn-orphans #-}
  9. -- | Define the dispatch for a Wiki. You should probably start off by reading
  10. -- WikiRoutes.
  11. module Wiki
  12. ( module WikiRoutes
  13. ) where
  14. import Control.Applicative ((<$>))
  15. import Control.Monad (unless)
  16. import Data.IORef.Lifted (readIORef, atomicModifyIORef)
  17. import Data.Map (Map)
  18. import qualified Data.Map as Map
  19. import Data.Text (Text)
  20. import WikiRoutes
  21. import Yesod
  22. -- | A subsite needs to be an instance of YesodSubDispatch, which states how to
  23. -- dispatch. By using constraints, we can make requirements of our master site.
  24. -- In this example, we're saying that the master site must be an instance of
  25. -- YesodWiki.
  26. instance YesodWiki master => YesodSubDispatch Wiki (HandlerT master IO) where
  27. -- | This is all the TH magic for dispatch. WikiRoutes provides the
  28. -- resourcesWiki value automatically, and mkYesodSubDispatch will generate
  29. -- a dispatch function that will call out to the appropriate handler
  30. -- functions.
  31. yesodSubDispatch = $(mkYesodSubDispatch resourcesWiki)
  32. -- | Helper type synonym to be used below.
  33. type WikiHandler a = forall master. YesodWiki master
  34. => HandlerT Wiki (HandlerT master IO) a
  35. ------------- Helper functions
  36. -- | Get all of the content in the Wiki.
  37. getContent :: WikiHandler (Map Texts Textarea)
  38. getContent = getYesod >>= readIORef . wikiContent
  39. -- | Put a single new value into the Wiki.
  40. putContent :: Texts -> Textarea -> WikiHandler ()
  41. putContent k v = do
  42. refMap <- wikiContent <$> getYesod
  43. atomicModifyIORef refMap $ \m -> (Map.insert k v m, ())
  44. -- | Gets the homepage, which lists all of the pages available.
  45. getWikiHomeR :: WikiHandler TypedContent
  46. getWikiHomeR = do
  47. content <- getContent
  48. -- We use the new selectRep/provideRep functionality to provide either an
  49. -- HTML or JSON representation of the page. You could just as easily
  50. -- provide YAML, plain text, RSS, or anything else.
  51. selectRep $ do
  52. provideRep $ do
  53. -- We'll use toParent to convert Wiki routes into our master site
  54. -- routes.
  55. toParent <- getRouteToParent
  56. -- Run the master site's defaultLayout to style the page.
  57. lift $ defaultLayout
  58. [whamlet|
  59. <p>This wiki has the following pages:
  60. <ul>
  61. $forall page <- Map.keys content
  62. <li>
  63. -- Notice the usage of toParent!
  64. <a href=@{toParent $ WikiReadR page}>#{show page}
  65. |]
  66. -- You provide a JSON representation just by returning a JSON value.
  67. -- aeson's toJSON make it easy to convert a list of values into JSON.
  68. provideRep $ return $ toJSON $ Map.keys content
  69. getWikiReadR :: Texts -> WikiHandler TypedContent
  70. getWikiReadR page = do
  71. content <- getContent
  72. selectRep $ do
  73. provideRep $
  74. case Map.lookup page content of
  75. Nothing -> do
  76. setMessage $ "Page does not exist, please create it."
  77. -- We don't need to convert or lift here: we're using a
  78. -- route from our subsite, and redirect lives in our
  79. -- subsite.
  80. redirect $ WikiEditR page
  81. Just t -> do
  82. toParent <- getRouteToParent
  83. -- Notice that we lift the canEditPage function from the
  84. -- master site.
  85. canEdit <- lift $ canEditPage page
  86. lift $ defaultLayout
  87. [whamlet|
  88. <article>#{t}
  89. $if canEdit
  90. <p>
  91. <a href=@{toParent $ WikiEditR page}>Edit
  92. |]
  93. provideRep $ return $ toJSON $
  94. case Map.lookup page content of
  95. -- Our HTML representation sends a redirect if the page isn't
  96. -- found, but our JSON representation just returns a JSON value
  97. -- instead.
  98. Nothing -> object ["error" .= ("Page not found" :: Text)]
  99. Just (Textarea t) -> object ["content" .= t]
  100. getWikiEditR :: Texts -> WikiHandler Html
  101. getWikiEditR page = do
  102. canEdit <- lift $ canEditPage page
  103. unless canEdit $ permissionDenied "You do not have permissions to edit this page."
  104. content <- getContent
  105. let form = renderTable
  106. $ areq textareaField "Content" (Map.lookup page content)
  107. -- We need to use lift here since the widget will be used below.
  108. -- Practically speaking, this means that we'll be rendering form messages
  109. -- using the master site's translation functions.
  110. ((res, widget), enctype) <- lift $ runFormPost form
  111. case res of
  112. FormSuccess t -> do
  113. putContent page t
  114. setMessage "Content updated"
  115. redirect $ WikiEditR page
  116. _ -> do
  117. toParent <- getRouteToParent
  118. lift $ defaultLayout
  119. [whamlet|
  120. <p>
  121. <a href=@{toParent $ WikiReadR page}>Read page
  122. <form method=post action=@{toParent $ WikiEditR page} enctype=#{enctype}>
  123. <table>
  124. ^{widget}
  125. <tr>
  126. <td colspan=2>
  127. <button>Update page
  128. |]
  129. postWikiEditR :: Texts -> WikiHandler Html
  130. postWikiEditR = getWikiEditR