From e578da5c4f19af7446a48a9d78f45de51383d4ac Mon Sep 17 00:00:00 2001 From: Benjamin Barenblat Date: Thu, 2 May 2013 21:21:22 -0400 Subject: Forum: Add upvote button --- forum/author.ur | 14 ++++++++++++++ forum/author.urs | 8 ++++++++ forum/forum.css | 5 +++++ forum/forum.ur | 48 ++++++++++++++++++++++++++++++++++++++---------- forum/lib.urp | 1 + forum/myOption.ur | 4 ++++ forum/myOption.urs | 1 + forum/score.ur | 5 +++++ forum/score.urs | 5 +++++ 9 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 forum/myOption.ur create mode 100644 forum/myOption.urs diff --git a/forum/author.ur b/forum/author.ur index f58d4cf..5cbc136 100644 --- a/forum/author.ur +++ b/forum/author.ur @@ -57,8 +57,22 @@ val sql_username = sql_prim fun name uOrA = uOrA +val nameError = MyOption.getError + val orAnonymous = Some +(* I can't express this in terms of whenIdentified'--I get a "substitution in +constructor is blocked by a too-deep unification variable." *) +fun whenIdentified [ctx] [use] uOrA text = + case uOrA of + None => + | Some u => text + +fun whenIdentified' [ctx] [use] uOrA generator = + case uOrA of + None => + | Some u => generator u + fun toOptionTag [_use] uOrA = case uOrA of None => diff --git a/forum/author.urs b/forum/author.urs index 1f394f0..db6e7a8 100644 --- a/forum/author.urs +++ b/forum/author.urs @@ -43,9 +43,17 @@ val sql_username : sql_injectable username (******************************** Converting *********************************) val name : usernameOrAnonymous -> option username +val nameError : usernameOrAnonymous -> username val orAnonymous : username -> usernameOrAnonymous +val whenIdentified : ctx ::: {Unit} -> use ::: {Type} -> + usernameOrAnonymous -> xml ctx use [] -> xml ctx use [] + +val whenIdentified' : ctx ::: {Unit} -> use ::: {Type} -> + usernameOrAnonymous -> (username -> xml ctx use []) + -> xml ctx use [] + (* Converts a 'usernameOrAnonymous' to an 'option' tag. If anonymous, produces empty XML. *) val toOptionTag : use ::: {Type} -> usernameOrAnonymous -> xml select use [] diff --git a/forum/forum.css b/forum/forum.css index 4f7b7f2..9193de0 100644 --- a/forum/forum.css +++ b/forum/forum.css @@ -27,6 +27,7 @@ } .Forum_entryMetadata { + display: inline; font-style: italic; color: hsl(0, 0%, 65%); } @@ -44,3 +45,7 @@ li .Forum_entryMetadata { .Forum_entryBody { height: 15em; } + +.Forum_voting, .Forum_voting * { + display: inline; +} diff --git a/forum/forum.ur b/forum/forum.ur index c43befe..85f3e19 100644 --- a/forum/forum.ur +++ b/forum/forum.ur @@ -26,6 +26,7 @@ style entryList style entryMetadata style entryTitle style entryBody +style voting table entry : { Id : int, References : option int, @@ -40,6 +41,8 @@ table vote : { QuestionId : int, Author : Author.username, Value : Score.score } + CONSTRAINT OneVotePerEntry UNIQUE (QuestionId, Author), + CONSTRAINT RefersToEntry FOREIGN KEY QuestionId REFERENCES entry(Id) (* Like query1', but automatically dereferences the field *) fun queryColumn [tab ::: Name] [field ::: Name] [state ::: Type] @@ -56,27 +59,51 @@ fun getScore (questionId : int) : transaction Score.score = Score.update Score.undecided +fun recordVote (value : Score.score) (entryId : int) _formData : transaction page = + authorOpt <- Author.current; + (* If the user didn't exist, the user should not have been allowed to vote + in the first place. *) + let val author = Author.nameError authorOpt + in + dml (INSERT INTO vote (QuestionId, Author, Value) + VALUES ({[entryId]}, {[author]}, {[value]})); + detail entryId + end + +and upvote entryId _formData = recordVote Score.insightful entryId _formData + + + (***************************** Single questions ******************************) -fun detail (id : int) : transaction page = +and detail (id : int) : transaction page = authorOpt <- Author.current; question <- oneRow1 (SELECT * FROM entry WHERE Entry.Class = {[EntryClass.question]} AND Entry.Id = {[id]}); - answerBlock <- queryX1 (SELECT * FROM entry + score <- getScore id; + answerBlock <- queryX1' (SELECT * FROM entry WHERE Entry.Class = {[EntryClass.answer]} AND Entry.References = {[Some id]}) (fn answer => -

- {[answer.Body]} - —{[answer.Author]} -

); + score <- getScore answer.Id; + return ( +

+ {[answer.Body]} + —{[answer.Author]} ({[Score.withUnits score "point"]}) +

)); return ( Template.generic (Some "Forum")

{[question.Title]}

{[question.Body]}

-

Asked by {[question.Author]}

+

+ Asked by {[question.Author]} ({[Score.withUnits score "point"]}) +

+ {Author.whenIdentified authorOpt + +
+
}
{answerBlock}
@@ -85,7 +112,7 @@ fun detail (id : int) : transaction page =