Guest User

Untitled

a guest
May 11th, 2020
95
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Haskell 26.04 KB | None | 0 0
  1. {-# Language TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, FlexibleInstances, FlexibleContexts, UndecidableInstances  #-}
  2. {-# Language ExistentialQuantification, TupleSections #-}
  3.  
  4. ----------------------------------------------------------------------------------------------------------------
  5. module Anon.ImageBoard.Client.Web where
  6. ----------------------------------------------------------------------------------------------------------------
  7. import Anon.Yesod
  8. import Anon.Data.List
  9. import Anon.Control.Monad.Trans.Either
  10. ----------------------------------------------------------------------------------------------------------------
  11. import Yesod -- (mkYesod, parseRoutes, getYesod, defaultLayout, renderRoute, approot, warpDebug, yesodDispatch, RepHtml, Yesod, hamletToRepHtml, hamlet, FormMessage, defaultFormMessage)
  12. import Yesod.Core
  13. import Yesod.Widget
  14. import Yesod.Static
  15. ----------------------------------------------------------------------------------------------------------------
  16. import qualified Text.Blaze.Html5 as H
  17. ----------------------------------------------------------------------------------------------------------------
  18. import qualified Data.List as List
  19. import qualified Data.Text as Text
  20. import qualified Data.UUID as UUID
  21. import qualified Data.Maybe as Maybe
  22. import Control.Applicative ((<*>), (<$>))
  23. import Control.Arrow
  24. ----------------------------------------------------------------------------------------------------------------
  25. import Control.Monad.Trans.Control
  26. import Control.Monad.Trans.Either
  27. import Control.Monad.IO.Class
  28. import Control.Monad
  29. ----------------------------------------------------------------------------------------------------------------
  30. import qualified Anon.ImageBoard.Server.Data as Data
  31.  
  32. import qualified Anon.ImageBoard.TransferData.TryResult as TryResult
  33. import qualified Anon.ImageBoard.TransferData.RemoteServerExceptionDetail as RemoteServerExceptionDetail
  34. import qualified Anon.ImageBoard.TransferData.BoardPageRequest as BoardPageRequest
  35. import qualified Anon.ImageBoard.TransferData.BoardRequest as BoardRequest
  36. import qualified Anon.ImageBoard.TransferData.BoardPage as BoardPage
  37. import qualified Anon.ImageBoard.TransferData.BoardGroup as BoardGroup
  38. import qualified Anon.ImageBoard.TransferData.Board as Board
  39. import qualified Anon.ImageBoard.TransferData.Thread as Thread
  40. import qualified Anon.ImageBoard.TransferData.ThreadIdentity as ThreadIdentity
  41. import qualified Anon.ImageBoard.TransferData.Post as Post
  42. import qualified Anon.ImageBoard.TransferData.PostIdentity as PostIdentity
  43. import qualified Anon.ImageBoard.TransferData.PostHeader as PostHeader
  44. import qualified Anon.ImageBoard.TransferData.PostMessage as PostMessage
  45. import qualified Anon.ImageBoard.TransferData.Attachment as Attachment
  46. import qualified Anon.ImageBoard.TransferData.AttachmentFormatGroup as AttachmentFormatGroup
  47. import qualified Anon.ImageBoard.TransferData.ImageAttachmentFormatGroup as ImageAttachmentFormatGroup
  48. import qualified Anon.ImageBoard.TransferData.AttachmentFormat as AttachmentFormat
  49. import qualified Anon.ImageBoard.TransferData.VideoAttachmentFormat as VideoAttachmentFormat
  50. import qualified Anon.ImageBoard.TransferData.ImageSize as ImageSize
  51. import qualified Anon.ImageBoard.TransferData.CaptchaRequestMode as CaptchaRequestMode
  52.  
  53. import qualified Anon.ImageBoard.Client.Web.CreatePostFill as CreatePostFill
  54. ----------------------------------------------------------------------------------------------------------------
  55. --                                                                                                            --
  56. ----------------------------------------------------------------------------------------------------------------
  57. data WebApplication = forall source. (Data.ServerDataStorage IO source) =>
  58.  
  59.  
  60.                       WebApplicationData { boardGroups       :: [BoardGroup.BoardGroup]
  61.                                          , dataSource        :: source
  62.                                          , pageSize          :: Int
  63.                                          , previewPostCount  :: Int
  64.                                          , defaultSenderName :: String
  65.                                          , cssFiles          :: Static
  66.                                          , jsFiles           :: Static }
  67. ----------------------------------------------------------------------------------------------------------------
  68. instance Yesod WebApplication
  69.  
  70.   where defaultLayout w = do p <- widgetToPageContent w
  71.                              mmsg <- getMessage
  72.                              hamletToRepHtml [hamlet|
  73. !!!
  74. <html>
  75.     <head>
  76.         <title>#{pageTitle p}
  77.         ^{pageHead p}
  78.         <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  79.         <link href="favicon.ico" rel="icon" type="image/png" />
  80.     <body .Page>
  81.         $maybe msg <- mmsg
  82.             <p .message>#{msg}
  83.         ^{pageBody p}
  84. |]
  85. ----------------------------------------------------------------------------------------------------------------
  86. instance RenderMessage WebApplication FormMessage where
  87.     renderMessage _ _ = defaultFormMessage
  88. ----------------------------------------------------------------------------------------------------------------
  89. --                                                                                                            --
  90. ----------------------------------------------------------------------------------------------------------------
  91. mkYesod "WebApplication" [parseRoutesNoCheck|
  92. /Content            SiteCss          Static cssFiles
  93. /favicon.ico        FaviconR         GET
  94.  
  95. /                   MainPage         GET
  96. /#String            DefaultBoardPage GET
  97. /#String/#Int       BoardPage        GET
  98. /#String/res/#Int   ThreadPage       GET
  99.  
  100. /CreatePost         CreatePost       POST
  101.  
  102. |]
  103. ----------------------------------------------------------------------------------------------------------------
  104. --                                                                                                            --
  105. ----------------------------------------------------------------------------------------------------------------
  106. getFaviconR :: Handler RepHtml
  107. getFaviconR = sendFile "image/png" "static/favicon.ico"
  108. ----------------------------------------------------------------------------------------------------------------
  109. rootTemplateLayout :: String -> Widget -> Handler RepHtml
  110. rootTemplateLayout title widget = defaultLayout $ do addStylesheet $ SiteCss $ StaticRoute ["Site.css"] []
  111.                                                      setTitle $ H.toHtml title
  112.                                                      [whamlet|^{renderNavigationPanel}
  113.                                                                 <br>
  114.                                                                 ^{widget}|]
  115. ----------------------------------------------------------------------------------------------------------------
  116. resultTemplateLayout :: (a -> String) -> (a -> Widget) -> TryResult.TryResult a -> Handler RepHtml
  117. resultTemplateLayout _     _      (Left   error) = rootTemplateLayout "Error" [whamlet| Error: #{show error}|]
  118. resultTemplateLayout getTitle render (Right result) = rootTemplateLayout (getTitle result) $ render result
  119. ----------------------------------------------------------------------------------------------------------------
  120. renderNavigationPanel :: Widget
  121. renderNavigationPanel = do app <- lift getYesod
  122.  
  123.                            sequence_ . List.intersperse [whamlet|&nbsp;-&nbsp;|]
  124.                                      . map (brackets [whamlet|[|] [whamlet|]|] . sequence_
  125.                                                                                . List.intersperse [whamlet|/|]
  126.                                                                                . map (brackets [whamlet|&nbsp;|] [whamlet|&nbsp;|]))
  127.                                      . links $ app
  128.  
  129.   where brackets open close value = open >> value >> close
  130.  
  131.         links :: WebApplication -> [[Widget]]
  132.         links app = [[[whamlet|<strong>
  133.                                 <a href=@{DefaultBoardPage shortName}>#{shortName}|] | board <- visibleBoards
  134.                                                                                      , let shortName = Board.shortName board]
  135.                                                                                      | boardGroup <- boardGroups app
  136.                                                                                      , let visibleBoards = BoardGroup.visibleBoards boardGroup
  137.                                                                                      , visibleBoards /= []]
  138.                  ++ [[[whamlet| <a href=@{MainPage}>Главная|]]]
  139. ----------------------------------------------------------------------------------------------------------------
  140. renderBoardLogo :: Board.Board -> Widget
  141. renderBoardLogo board = [whamlet|<div .BoardLogo>
  142.                                     <a href=@{DefaultBoardPage shortName} .BoardLogoLink>#{fullName}|]
  143.  
  144.   where fullName  = Board.fullName  board
  145.         shortName = Board.shortName board
  146.  
  147. ----------------------------------------------------------------------------------------------------------------
  148. getMainPage :: Handler RepHtml
  149. getMainPage = rootTemplateLayout "Haskell ImageBoard. MainPage." [whamlet|Haskell ImageBoard|]
  150. ----------------------------------------------------------------------------------------------------------------
  151. getThreadPage :: String -> Int -> Handler RepHtml
  152. getThreadPage boardShortName threadLocalId = do app  <- getYesod
  153.                                                 info <- liftIO $ runEitherT $ getRenderInfo app
  154.  
  155.                                                 resultTemplateLayout (Board.fullName . fst) renderThreadPage info
  156.  
  157.   where renderThreadPage :: (Board.Board, Thread.Thread) -> Widget
  158.         renderThreadPage info @ (board, thread) = do (formWidget, formEnctype) <- lift $ generateFormPost $ createPostFillForm board $ Just localThreadId
  159.  
  160.                                                      [whamlet|
  161. ^{renderBoardLogo board}
  162. <hr>
  163. <a href=@{DefaultBoardPage shortName}>Вернуться
  164. <div>
  165. <form method=post action=@{CreatePost} enctype=#{formEnctype}>
  166.     ^{formWidget}
  167. <hr>
  168. ^{render info}
  169. <hr>
  170. |]
  171.  
  172.               where shortName = Board.shortName board
  173.  
  174.                     localThreadId = ThreadIdentity.localId $ Thread.threadId thread
  175.  
  176.         getRenderInfo :: WebApplication -> TryResult.TryResultT IO (Board.Board, Thread.Thread)
  177.         getRenderInfo app = do board  <- EitherT $ return $ getBoardByShortCode boardShortName app
  178.                                thread <- getRequest board `getThreadApp` app
  179.  
  180.                                return (board, thread)
  181.  
  182.         getRequest :: Board.Board -> ThreadIdentity.ThreadIdentity
  183.         getRequest board = ThreadIdentity.ThreadIdentity { ThreadIdentity.boardId = Board.id board
  184.                                                          , ThreadIdentity.localId = threadLocalId }
  185. ----------------------------------------------------------------------------------------------------------------
  186. postCreatePost :: Handler RepHtml
  187. postCreatePost = undefined
  188. ----------------------------------------------------------------------------------------------------------------
  189. getDefaultBoardPage :: String -> Handler RepHtml
  190. getDefaultBoardPage = getBoardPage `flip` 0
  191. ----------------------------------------------------------------------------------------------------------------
  192. getBoardPage :: String -> Int -> Handler RepHtml
  193. getBoardPage boardShortName pageIndex = do app  <- getYesod
  194.                                            info <- liftIO $ runEitherT $ getRenderInfo app
  195.  
  196.                                            resultTemplateLayout (Board.fullName . fst) render info
  197.  
  198.   where getRenderInfo :: WebApplication -> TryResult.TryResultT IO (Board.Board, BoardPage.BoardPage)
  199.         getRenderInfo app = do board     <- EitherT $ return $ getBoardByShortCode boardShortName app
  200.                                boardPage <- getRequest app board `getBoardPageApp` app
  201.  
  202.                                return (board, boardPage)
  203.  
  204.  
  205.         getRequest :: WebApplication -> Board.Board -> BoardPageRequest.BoardPageRequest
  206.         getRequest app board = BoardPageRequest.BoardPageRequest { BoardPageRequest.boardId          = Board.id board
  207.                                                                  , BoardPageRequest.threadStartIndex = pageIndex * pageSize app
  208.                                                                  , BoardPageRequest.threadCount      = pageSize app
  209.                                                                  , BoardPageRequest.previewPostCount = previewPostCount app }
  210. ----------------------------------------------------------------------------------------------------------------
  211. getBoardByShortCode :: String -> WebApplication -> TryResult.TryResult Board.Board
  212. getBoardByShortCode boardShortName = getBoardByShortCode' . List.find ((== boardShortName) . Board.shortName) . concatMap BoardGroup.boards . boardGroups
  213.  
  214.  where getBoardByShortCode' (Just board) = Right $ board
  215.         getBoardByShortCode' Nothing      = TryResult.boardByShortNameNotFound boardShortName
  216. ------------------------------------------------------------------------------------------------------------------------------------
  217. class Renderable a
  218.  
  219.  where render :: a -> Widget
  220. ------------------------------------------------------------------------------------------------------------------------------------
  221. instance Renderable (Board.Board, BoardPage.BoardPage)
  222.  
  223.  where render (board, boardPage) = do (formWidget, formEnctype) <- lift $ generateFormPost $ createPostFillForm board Nothing
  224.                                       [whamlet|
  225. ^{renderBoardLogo board}
  226. <hr>
  227. <form method=post action=@{CreatePost} enctype=#{formEnctype}>
  228.    ^{formWidget}
  229. $forall thread <- BoardPage.selectedThreads boardPage
  230.  <hr>
  231.  ^{renderThread thread}
  232. <hr>
  233. |]
  234.  
  235.          where renderThread = render . (board,)
  236. ----------------------------------------------------------------------------------------------------------------
  237. instance Renderable (Board.Board, Thread.Thread)
  238.  
  239.  where render (board, thread @ Thread.Thread { Thread.threadId     = threadId
  240.                                              , Thread.posts        = (mainPost : bodyPosts)
  241.                                              , Thread.collapseInfo = collapseInfo }) = [whamlet|
  242.  
  243. <div>
  244.  ^{renderPost mainPost}
  245.  $forall bodyPost <- bodyPosts
  246.    <br>
  247.    ^{renderPost bodyPost } |]
  248.  
  249.          where renderPost = render . (board, thread,)
  250. ----------------------------------------------------------------------------------------------------------------
  251. instance Renderable (Board.Board, PostIdentity.PostIdentity)
  252.  
  253.  where render (board, PostIdentity.PostIdentity { PostIdentity.localId  = postLocalId
  254.                                                 , PostIdentity.threadId = threadId } ) = [whamlet|
  255.  
  256. <a onclick=addComment (this.href) href=@{ThreadPage boardShortName $ ThreadIdentity.localId threadId}#{fragment}>
  257.    №#{postLocalId} |]
  258.  
  259.          where boardShortName = Board.shortName board
  260.  
  261.                fragment = "#i" ++ show postLocalId
  262. ----------------------------------------------------------------------------------------------------------------
  263. instance Renderable (Board.Board, Thread.Thread, PostHeader.PostHeader)
  264.  
  265.  where render (board, thread, postHeader) = do app <- lift getYesod
  266.                                                [whamlet|
  267. <a name=#{PostIdentity.localId postId}>
  268. <label>
  269.    <input type=checkbox name=deletepost onclick=deletePost(this) value=#{PostIdentity.localId postId}>
  270.    &nbsp;
  271.    <span .Subject>
  272.        #{PostHeader.subject postHeader}
  273.    &nbsp;
  274.    <span>
  275.        $if PostHeader.isSage postHeader
  276.            <a href=mailto:sage>
  277.                #{sender app}
  278.        $else
  279.            #{sender app}
  280.    &nbsp;
  281.    #{show $ PostHeader.createDate postHeader}
  282. &nbsp;
  283. <span .RefLink>
  284.    ^{renderPostLink}|]
  285.  
  286.          where sender app = defaultSenderName app `fromList` PostHeader.sender postHeader
  287.  
  288.                postId = PostHeader.postId postHeader
  289.  
  290.                renderPostLink = render (board, postId)
  291. ----------------------------------------------------------------------------------------------------------------
  292. instance Renderable (Board.Board, Thread.Thread, PostMessage.PostMessage)
  293.  
  294.  where render (board, thread, message) = [whamlet|message|]
  295. ----------------------------------------------------------------------------------------------------------------
  296. instance Renderable (Board.Board, Thread.Thread, Attachment.Attachment)
  297.  
  298.  where render (board, thread, attachment) = [whamlet|attachment|]
  299. ----------------------------------------------------------------------------------------------------------------
  300. instance Renderable (Board.Board, Thread.Thread, Post.Post)
  301.  
  302.  where render (board, thread, Post.Post { Post.header     = header
  303.                                         , Post.message    = maybeMessage
  304.                                         , Post.attachment = maybeAttachment })
  305.  
  306.          | PostIdentity.isMainPost postId  = [whamlet|
  307. <div #id=#{PostIdentity.localId postId}>
  308.    ^{maybeRender renderItem maybeAttachment}
  309.    ^{renderItem header}
  310.    <div>
  311.        ^{ maybeRender renderItem maybeMessage} |]
  312.  
  313.          | otherwise                       = [whamlet|
  314. <table>
  315.    <tbody>
  316.        <tr>
  317.            <td>
  318.                ^{renderItem header}
  319.                <br>
  320.                ^{maybeRender renderItem maybeAttachment}
  321.                ^{maybeRender renderItem maybeMessage} |]
  322.  
  323.          where postId = PostHeader.postId header
  324.  
  325.                renderItem :: Renderable (Board.Board, Thread.Thread, a) => a -> Widget
  326.                renderItem item = render (board, thread, item)
  327. ----------------------------------------------------------------------------------------------------------------
  328. --                                                                                                            --
  329. ----------------------------------------------------------------------------------------------------------------
  330. createPostFillForm :: Board.Board -> Maybe Int -> Html -> MForm WebApplication WebApplication (FormResult CreatePostFill.CreatePostFill, Widget)
  331. createPostFillForm board tryThreadLocalId extra =
  332.  
  333.  do (goToThreadRes, goToThreadView) <- mreq checkBoxField "Go To Thread" Nothing
  334.     (boardIdRes, boardIdView) <- mreq hiddenField ("Board Id" { fsId = Just "BoardId", fsName = Just "BoardId" }) $ Just $ Board.id board
  335.     (threadLocalIdRes, threadLocalIdView) <- mopt hiddenField ("Thread Local Id" { fsId = Just "ThreadLocalId", fsName = Just "ThreadLocalId" }) $ Just $ tryThreadLocalId
  336.     (captchaKeyRes, captchaKeyView) <- mreq hiddenField ("Captcha Key" { fsId = Just "CaptchaKey", fsName = Just "CaptchaKey" } ) $ Nothing
  337.     (isSageRes, isSageView) <- mreq checkBoxField "Sage" Nothing
  338.  
  339.     (subjectRes, subjectView) <- mopt textField "Subject" Nothing
  340.     (senderRes, senderView) <- mopt textField "Sender" Nothing
  341.     (passwordRes, passwordView) <- mopt textField "Password" Nothing
  342.     (captchaSolutionRes, captchaSolutionView) <- mopt textField "Captcha Solution" Nothing
  343.     (postMessageRes, postMessageView) <- mopt textareaField (addTextAreaSize 60 6 $ "Post Message" { fsId = Just "PostMessage", fsName = Just "PostMessage" } ) Nothing
  344.     (noFileRes, noFileView) <- mreq checkBoxField ("No File"{ fsId = Just "NoFile", fsName = Just "NoFile" } ) Nothing
  345.     (attachmentRes, attachmentView) <- moptFileInfoField "Attachment"
  346.     (externalVideoAttachmentRes, externalVideoAttachmentView) <- mopt textField "External Video Attachment" Nothing
  347.  
  348.     let createPostFillRes = CreatePostFill.CreatePostFill <$> goToThreadRes
  349.                                                           <*> boardIdRes
  350.                                                           <*> threadLocalIdRes
  351.                                                           <*> (read <$> captchaKeyRes)
  352.                                                           <*> isSageRes
  353.                                                           <&> subjectRes
  354.                                                           <&> senderRes
  355.                                                           <&> passwordRes
  356.                                                           <&> captchaSolutionRes
  357.                                                           <@> postMessageRes
  358.                                                           <*> noFileRes
  359.                                                           <*> attachmentRes
  360.                                                           <&> externalVideoAttachmentRes
  361.  
  362.  
  363.  
  364.     let widget = [whamlet|
  365. #{extra}
  366. ^{fvInput boardIdView}
  367. ^{fvInput threadLocalIdView}
  368. <div .CreatePostHeader>
  369.    #{createPostHeaderMessage}
  370. <table .CreatePostTable>
  371.    <tbody>
  372.        <tr>
  373.            <td>
  374.                Имя
  375.            <td>
  376.                ^{fvInput senderView}
  377.                <em>
  378.                    (оставьте это поле пустым)
  379.        <tr>
  380.            <td>
  381.                Sage
  382.            <td>
  383.                ^{fvInput isSageView}
  384.                <em>
  385.                    (не поднимать этот ITT тред)
  386.        <tr>
  387.            <td>
  388.                Тема
  389.            <td>
  390.                ^{fvInput subjectView}
  391.                <input type="submit" value="Отправить" />
  392.        <tr>
  393.            <td>
  394.                Мнение
  395.                <br> анонимного
  396.                <br> иксперта
  397.            <td>
  398.                ^{fvInput postMessageView}
  399.        <tr>
  400.            <td>
  401.                Файл
  402.            <td>
  403.                ^{fvInput attachmentView}
  404.                [^{fvInput noFileView}
  405.                <label for="NoFile">Без файла]
  406.  
  407.        $if hasVideoGroupFormats
  408.             <tr>
  409.                <td>
  410.                    Видео
  411.                    <td>
  412.                        ^{fvInput externalVideoAttachmentView}
  413.  
  414.        <tr>
  415.            <td>
  416.                К треду
  417.            <td>
  418.                ^{fvInput goToThreadView}
  419.        $if hasCaptcha
  420.            <tr>
  421.                <td>
  422.                    Капча
  423.                    <td>
  424.                        <img alt="(капча появится по клику)" id="CaptchaImage" style="cursor:pointer" />
  425.                        ^{fvInput captchaKeyView}
  426.                <tr>
  427.                    <td>
  428.                        Подтверждение
  429.                    <td>
  430.                        ^{fvInput captchaSolutionView}
  431.  
  432.        <tr>
  433.            <td>
  434.                Пароль
  435.            <td>
  436.                ^{fvInput passwordView}
  437.                (чтобы удалить пост или файл)
  438.        $if not $ Text.null allowedAttachmentFormats
  439.            <tr>
  440.                <td colspan="2">
  441.                    <ul>
  442.                        <li>
  443.                            Поддерживаемые типы файлов: #{allowedAttachmentFormats}
  444.  
  445.                        <li>
  446.                            Максимальный размер файла #{maxAttachmentSizeKB} KB.
  447.  
  448.                        $if not $ Text.null allowedAttachmentFormats
  449.                            <li>
  450.                                Изображения больше #{maxImageAttachmentSize} будут уменьшены.
  451.  
  452. |]
  453.     return (createPostFillRes, widget)
  454.  
  455.  where maxAttachmentSizeKB :: Int
  456.        maxAttachmentSizeKB = Board.maxAttachmentSize board `div` 1024
  457.  
  458.        maxImageAttachmentSize :: Text.Text
  459.        maxImageAttachmentSize = Text.pack $ concat
  460.                               $ [ show (ImageSize.width maxCanvasSize) ++ "x" ++ show (ImageSize.height maxCanvasSize)
  461.                                 | (AttachmentFormatGroup.ImageAttachmentFormatGroup' (ImageAttachmentFormatGroup.ImageAttachmentFormatGroup { ImageAttachmentFormatGroup.maxCanvasSize = maxCanvasSize })) <- Board.attachmentFormatGroups board]
  462.  
  463.  
  464.         allowedAttachmentFormats :: Text.Text
  465.         allowedAttachmentFormats = Text.toUpper $ Text.pack $ concat $ List.intersperse ", "
  466.                                  $ [ show $ AttachmentFormat.code format | attachmentFormatGroup <- Board.attachmentFormatGroups board
  467.                                                                          , not $ AttachmentFormatGroup.isExternal attachmentFormatGroup
  468.                                                                          , format <- AttachmentFormatGroup.formats attachmentFormatGroup]
  469.  
  470.         hasCaptcha :: Bool
  471.         hasCaptcha = Board.hasCaptcha board captchaRequestMode
  472.  
  473.  
  474.         captchaRequestMode :: CaptchaRequestMode.CaptchaRequestMode
  475.         captchaRequestMode | isThreadMode = CaptchaRequestMode.CreatePost
  476.                            | otherwise    = CaptchaRequestMode.CreateThread
  477.  
  478.         hasVideoGroupFormats :: Bool --[VideoAttachmentFormat.VideoAttachmentFormat]
  479.         hasVideoGroupFormats = not $ null $ Board.videoGroupFormats board
  480.  
  481.         isThreadMode :: Bool
  482.         isThreadMode = tryThreadLocalId /= Nothing
  483.  
  484.         createPostHeaderMessage :: Text.Text
  485.         createPostHeaderMessage | isThreadMode = "Ответить в тред"
  486.                                 | otherwise    = "Создать новый тред"
  487.  
  488. ----------------------------------------------------------------------------------------------------------------
  489. getThreadApp threadIdentity (WebApplicationData { dataSource = dataSource }) = liftIOEitherT $ Data.getThread threadIdentity dataSource
  490. ----------------------------------------------------------------------------------------------------------------
  491. getBoardPageApp boardPageRequest (WebApplicationData { dataSource = dataSource }) = liftIOEitherT $ Data.getBoardPage boardPageRequest dataSource
  492. ----------------------------------------------------------------------------------------------------------------
Advertisement
Add Comment
Please, Sign In to add comment