forked from filipg/gonito
add timeline (discussion/comments)
This commit is contained in:
parent
2eebd2d075
commit
30b7db5714
@ -36,6 +36,7 @@ import qualified Data.IntMap as IntMap
|
|||||||
-- Import all relevant handler modules here.
|
-- Import all relevant handler modules here.
|
||||||
-- Don't forget to add new modules to your cabal file!
|
-- Don't forget to add new modules to your cabal file!
|
||||||
import Handler.Common
|
import Handler.Common
|
||||||
|
import Handler.Discussion
|
||||||
import Handler.Fay
|
import Handler.Fay
|
||||||
import Handler.Graph
|
import Handler.Graph
|
||||||
import Handler.Home
|
import Handler.Home
|
||||||
|
@ -113,6 +113,7 @@ instance Yesod App where
|
|||||||
isAuthorized (ChallengeReadmeR _) _ = return Authorized
|
isAuthorized (ChallengeReadmeR _) _ = return Authorized
|
||||||
isAuthorized (ChallengeAllSubmissionsR _) _ = return Authorized
|
isAuthorized (ChallengeAllSubmissionsR _) _ = return Authorized
|
||||||
isAuthorized (ChallengeGraphDataR _) _ = return Authorized
|
isAuthorized (ChallengeGraphDataR _) _ = return Authorized
|
||||||
|
isAuthorized (ChallengeDiscussionR _) _ = return Authorized
|
||||||
|
|
||||||
-- Default to Authorized for now.
|
-- Default to Authorized for now.
|
||||||
isAuthorized _ _ = isTrustedAuthorized
|
isAuthorized _ _ = isTrustedAuthorized
|
||||||
|
90
Handler/Discussion.hs
Normal file
90
Handler/Discussion.hs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
module Handler.Discussion where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
|
||||||
|
import Handler.Shared
|
||||||
|
|
||||||
|
import Text.Blaze
|
||||||
|
import Text.Blaze.Html4.Strict (i)
|
||||||
|
|
||||||
|
import Handler.ShowChallenge
|
||||||
|
|
||||||
|
import Yesod.Form.Bootstrap3
|
||||||
|
|
||||||
|
data TimelineItem = TimelineItem UTCTime User Markup
|
||||||
|
|
||||||
|
getTime (TimelineItem stamp _ _) = stamp
|
||||||
|
|
||||||
|
class ToTimelineItem a where
|
||||||
|
timelineWhen :: a -> UTCTime
|
||||||
|
|
||||||
|
timelineWhoId :: a -> UserId
|
||||||
|
timelineWho :: a -> Handler User
|
||||||
|
timelineWho sItem = runDB $ get404 $ timelineWhoId sItem
|
||||||
|
|
||||||
|
timelineWhat :: a -> Handler Markup
|
||||||
|
toTimelineItem :: a -> Handler TimelineItem
|
||||||
|
|
||||||
|
toTimelineItem sItem = do
|
||||||
|
let when = timelineWhen sItem
|
||||||
|
who <- timelineWho sItem
|
||||||
|
what <- timelineWhat sItem
|
||||||
|
return $ TimelineItem when who what
|
||||||
|
|
||||||
|
instance ToTimelineItem (Entity Comment) where
|
||||||
|
timelineWhoId (Entity _ comment) = commentAuthor comment
|
||||||
|
timelineWhen (Entity _ comment) = commentPosted comment
|
||||||
|
timelineWhat (Entity _ comment) = return $ toMarkup $ commentText comment
|
||||||
|
|
||||||
|
instance ToTimelineItem (Entity Submission) where
|
||||||
|
timelineWhoId (Entity _ submission) = submissionSubmitter submission
|
||||||
|
timelineWhen (Entity _ submission) = submissionStamp submission
|
||||||
|
timelineWhat (Entity _ submission) = return $ i $ toMarkup (
|
||||||
|
"submitted a solution:" ++ submissionDescription submission )
|
||||||
|
|
||||||
|
|
||||||
|
getChallengeDiscussionR :: Text -> Handler Html
|
||||||
|
getChallengeDiscussionR name = do
|
||||||
|
(Entity challengeId challenge) <- runDB $ getBy404 $ UniqueName name
|
||||||
|
(formWidget, formEnctype) <- generateFormPost $ renderBootstrap3 BootstrapBasicForm (commentForm challengeId)
|
||||||
|
comments <- runDB $ selectList [CommentChallenge ==. challengeId] [Desc CommentPosted]
|
||||||
|
submissions <- runDB $ selectList [SubmissionChallenge ==. challengeId] [Desc SubmissionStamp]
|
||||||
|
timelineItems' <- mapM toTimelineItem comments
|
||||||
|
timelineItems'' <- mapM toTimelineItem submissions
|
||||||
|
let sortedTimelineItems = sortBy (\item1 item2 -> (getTime item2 `compare` getTime item1)) (
|
||||||
|
timelineItems' ++ timelineItems'')
|
||||||
|
challengeLayout True challenge (discussionWidget formWidget formEnctype name sortedTimelineItems)
|
||||||
|
|
||||||
|
discussionWidget formWidget formEnctype name sortedTimelineItems = $(widgetFile "challenge-discussion")
|
||||||
|
|
||||||
|
timelineItemWidget item = $(widgetFile "timeline-item")
|
||||||
|
|
||||||
|
postChallengeDiscussionR :: Text -> Handler TypedContent
|
||||||
|
postChallengeDiscussionR name = do
|
||||||
|
(Entity challengeId _) <- runDB $ getBy404 $ UniqueName name
|
||||||
|
((result, formWidget), formEnctype) <- runFormPost $ renderBootstrap3 BootstrapBasicForm (commentForm challengeId)
|
||||||
|
case result of
|
||||||
|
FormSuccess comment -> do
|
||||||
|
userId <- requireAuthId
|
||||||
|
|
||||||
|
if commentAuthor comment == userId
|
||||||
|
then
|
||||||
|
do
|
||||||
|
setMessage $ toHtml ("Comment submitted" :: Text)
|
||||||
|
_ <- runDB $ insert comment
|
||||||
|
return ()
|
||||||
|
else
|
||||||
|
do
|
||||||
|
setMessage $ toHtml ("Wrong user ID" :: Text)
|
||||||
|
return ()
|
||||||
|
_ -> do
|
||||||
|
setMessage $ toHtml ("Something went wrong" :: Text)
|
||||||
|
|
||||||
|
redirect $ ChallengeDiscussionR name
|
||||||
|
|
||||||
|
commentForm :: Key Challenge -> AForm Handler Comment
|
||||||
|
commentForm challengeId = Comment
|
||||||
|
<$> pure challengeId
|
||||||
|
<*> lift requireAuthId
|
||||||
|
<*> lift (liftIO getCurrentTime)
|
||||||
|
<*> areq textareaField (bfs MsgCommentText) Nothing
|
@ -2,7 +2,6 @@ module Handler.Query where
|
|||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
|
||||||
import Handler.Tables (formatSubmitter)
|
|
||||||
import Handler.Shared
|
import Handler.Shared
|
||||||
import PersistSHA1
|
import PersistSHA1
|
||||||
|
|
||||||
|
@ -315,3 +315,12 @@ gatherSHA1ForCollectionOfFiles :: [FilePath] -> IO ByteString
|
|||||||
gatherSHA1ForCollectionOfFiles files = do
|
gatherSHA1ForCollectionOfFiles files = do
|
||||||
contentss <- mapM readFile $ sort files
|
contentss <- mapM readFile $ sort files
|
||||||
return $ CHS.finalize $ foldl' CHS.update CHS.init contentss
|
return $ CHS.finalize $ foldl' CHS.update CHS.init contentss
|
||||||
|
|
||||||
|
formatSubmitter :: User -> Text
|
||||||
|
formatSubmitter user = if userIsAnonymous user
|
||||||
|
then
|
||||||
|
"[anonymised]"
|
||||||
|
else
|
||||||
|
case userName user of
|
||||||
|
Just name -> name
|
||||||
|
Nothing -> "[name not given]"
|
||||||
|
@ -160,13 +160,3 @@ getEvaluationMap s@(Entity submissionId submission) = do
|
|||||||
let evaluations = catMaybes maybeEvaluations
|
let evaluations = catMaybes maybeEvaluations
|
||||||
let m = Map.fromList $ map (\(Entity _ e) -> (evaluationTest e, e)) evaluations
|
let m = Map.fromList $ map (\(Entity _ e) -> (evaluationTest e, e)) evaluations
|
||||||
return (s, Entity (submissionSubmitter submission) user, m)
|
return (s, Entity (submissionSubmitter submission) user, m)
|
||||||
|
|
||||||
|
|
||||||
formatSubmitter :: User -> Text
|
|
||||||
formatSubmitter user = if userIsAnonymous user
|
|
||||||
then
|
|
||||||
"[anonymised]"
|
|
||||||
else
|
|
||||||
case userName user of
|
|
||||||
Just name -> name
|
|
||||||
Nothing -> "[name not given]"
|
|
||||||
|
@ -61,6 +61,11 @@ Evaluation
|
|||||||
errorMessage Text Maybe
|
errorMessage Text Maybe
|
||||||
stamp UTCTime default=now()
|
stamp UTCTime default=now()
|
||||||
UniqueEvaluationTestChecksum test checksum
|
UniqueEvaluationTestChecksum test checksum
|
||||||
|
Comment
|
||||||
|
challenge ChallengeId
|
||||||
|
author UserId
|
||||||
|
posted UTCTime default=now()
|
||||||
|
text Textarea
|
||||||
Out
|
Out
|
||||||
submission SubmissionId
|
submission SubmissionId
|
||||||
test TestId
|
test TestId
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
/challenge-all-submissions/#Text ChallengeAllSubmissionsR GET
|
/challenge-all-submissions/#Text ChallengeAllSubmissionsR GET
|
||||||
/challenge-how-to/#Text ChallengeHowToR GET
|
/challenge-how-to/#Text ChallengeHowToR GET
|
||||||
/challenge-graph-data/#Text ChallengeGraphDataR GET
|
/challenge-graph-data/#Text ChallengeGraphDataR GET
|
||||||
|
/challenge-discussion/#Text ChallengeDiscussionR GET POST
|
||||||
|
|
||||||
/q QueryFormR GET POST
|
/q QueryFormR GET POST
|
||||||
/q/#Text QueryResultsR GET
|
/q/#Text QueryResultsR GET
|
||||||
|
@ -31,6 +31,7 @@ library
|
|||||||
SharedTypes
|
SharedTypes
|
||||||
Handler.Common
|
Handler.Common
|
||||||
Handler.CreateChallenge
|
Handler.CreateChallenge
|
||||||
|
Handler.Discussion
|
||||||
Handler.Fay
|
Handler.Fay
|
||||||
Handler.Graph
|
Handler.Graph
|
||||||
Handler.Home
|
Handler.Home
|
||||||
@ -120,6 +121,8 @@ library
|
|||||||
, regex-tdfa
|
, regex-tdfa
|
||||||
, optparse-applicative
|
, optparse-applicative
|
||||||
, wai-handler-fastcgi
|
, wai-handler-fastcgi
|
||||||
|
, blaze-markup
|
||||||
|
, blaze-html
|
||||||
|
|
||||||
executable gonito
|
executable gonito
|
||||||
if flag(library-only)
|
if flag(library-only)
|
||||||
|
@ -21,3 +21,5 @@ SshPubKey: your SSH public key
|
|||||||
Home: home
|
Home: home
|
||||||
Search: search
|
Search: search
|
||||||
GitCommitSha1: Git commit SHA1 hash
|
GitCommitSha1: Git commit SHA1 hash
|
||||||
|
CommentText: Write a comment
|
||||||
|
Send: Send
|
||||||
|
4
static/images/male-avatar.svg
Normal file
4
static/images/male-avatar.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="200" height="200">
|
||||||
|
<path d="M 3.1479956,199.8792 C 3.2975635,189.11012 3.401856,181.58351 3.0839455,167.62229 C 10.105706,158.82749 34.823782,154.79402 41.576501,152.37892 C 50.290941,145.87438 60.163612,140.3389 68.674128,135.43826 C 73.373849,132.73205 73.434971,123.45565 73.090981,117.22608 C 71.529518,108.40367 70.704151,100.79588 64.405541,94.317884 C 64.053201,92.46819 63.024431,88.22384 59.931471,82.679233 C 57.850015,73.360874 49.646154,61.821919 51.707332,55.90224 C 57.196471,40.137595 52.40075,33.731056 55.771462,32.383588 C 63.458756,29.310542 65.537537,22.590118 72.578356,15.841971 C 93.149082,11.923669 89.987081,1.4887794 105.35709,3.5044295 C 124.4076,6.0027415 113.91578,8.8197325 126.98208,15.459115 C 143.03475,23.615971 133.46982,24.442181 148.12438,46.038481 C 156.82644,58.862616 139.87501,59.736843 144.26704,71.085333 C 148.3243,81.568657 138.76017,83.355833 133.93493,94.828249 C 129.30575,105.83459 126.40093,109.0707 131.73108,123.57794 C 136.68074,137.04961 144.89857,130.89422 163.46019,151.07177 C 168.1733,156.1952 185.10612,149.99341 196.76509,164.95509 C 196.75779,172.625 196.92784,183.5909 196.98027,199.93748 C 144.72386,199.75031 3.1479956,199.8792 3.1479956,199.8792 z" fill="#808080"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
10
templates/challenge-discussion.hamlet
Normal file
10
templates/challenge-discussion.hamlet
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
<p>
|
||||||
|
<form method=post action=@{ChallengeDiscussionR name}#form enctype=#{formEnctype}>
|
||||||
|
^{formWidget}
|
||||||
|
<button .btn .btn-primary type="submit">
|
||||||
|
_{MsgSend} <span class="glyphicon glyphicon-upload"></span>
|
||||||
|
|
||||||
|
<div .timeline-box>
|
||||||
|
$forall item <- sortedTimelineItems
|
||||||
|
^{timelineItemWidget item}
|
@ -8,6 +8,7 @@
|
|||||||
<li role="presentation"><a href="@{ChallengeSubmissionR (challengeName challenge)}">Submit</a>
|
<li role="presentation"><a href="@{ChallengeSubmissionR (challengeName challenge)}">Submit</a>
|
||||||
<li role="presentation"><a href="@{ChallengeMySubmissionsR (challengeName challenge)}">My Submissions</a>
|
<li role="presentation"><a href="@{ChallengeMySubmissionsR (challengeName challenge)}">My Submissions</a>
|
||||||
<li role="presentation"><a href="@{ChallengeAllSubmissionsR (challengeName challenge)}">All Submissions</a>
|
<li role="presentation"><a href="@{ChallengeAllSubmissionsR (challengeName challenge)}">All Submissions</a>
|
||||||
|
<li role="presentation"><a href="@{ChallengeDiscussionR (challengeName challenge)}">Discussion</a>
|
||||||
<div .col-md-10 role="main">
|
<div .col-md-10 role="main">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
@ -14,3 +14,20 @@
|
|||||||
height: 400px;
|
height: 400px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeline-box {
|
||||||
|
padding-top: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
padding-bottom: 40px
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item-thumbnail {
|
||||||
|
padding:0px;
|
||||||
|
}
|
||||||
|
.timeline-item-panel {
|
||||||
|
}
|
||||||
|
.timeline-item-panel-heading {
|
||||||
|
padding-bottom: 13px
|
||||||
|
}
|
||||||
|
12
templates/timeline-item.hamlet
Normal file
12
templates/timeline-item.hamlet
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
$case item
|
||||||
|
$of TimelineItem when who what
|
||||||
|
<div class="row timeline-item">
|
||||||
|
<div class="col-sm-1">
|
||||||
|
<div class="timeline-item-thumbnail">
|
||||||
|
<img class="img-responsive user-photo" src="/static/images/male-avatar.svg">
|
||||||
|
<div class="col-sm-11">
|
||||||
|
<div .timeline-item-panel>
|
||||||
|
<div class="timeline-item-panel-heading">
|
||||||
|
<strong>#{formatSubmitter who}
|
||||||
|
<span class="text-muted"> #{formatTime defaultTimeLocale "%Y-%m-%d %H:%M" when}
|
||||||
|
<div class="timeline-item-panel-body">#{what}
|
Loading…
Reference in New Issue
Block a user