more work on messaging

This commit is contained in:
Thibault Duplessis 2012-05-27 20:34:35 +02:00
parent 988714bb0d
commit bdb199b936
21 changed files with 174 additions and 35 deletions

View file

@ -24,7 +24,21 @@ object Message extends LilaController {
IOptionOk(api.thread(id, me)) { html.message.thread(_) }
}
def form = TODO
def create = TODO
def form = Auth { implicit ctx
implicit me
Ok(html.message.form(forms.thread))
}
def create = AuthBody { implicit ctx
implicit me
implicit val req = ctx.body
forms.thread.bindFromRequest.fold(
err BadRequest(html.message.form(err)),
data api.makeThread(data, me).map(thread
Redirect(routes.Message.thread(thread.id))
).unsafePerformIO
)
}
def delete(id: String) = TODO
}

View file

@ -10,7 +10,7 @@ final class DataForm(captcher: Captcha) {
import DataForm._
def postMapping = mapping(
val postMapping = mapping(
"text" -> text(minLength = 3),
"author" -> optional(text),
"gameId" -> nonEmptyText,
@ -20,11 +20,11 @@ final class DataForm(captcher: Captcha) {
data captcher get data.gameId valid data.move.trim.toLowerCase
)
def post = Form(postMapping)
val post = Form(postMapping)
def postWithCaptcha = post -> captchaCreate
def topic = Form(mapping(
val topic = Form(mapping(
"name" -> text(minLength = 3),
"post" -> postMapping
)(TopicData.apply)(TopicData.unapply))

View file

@ -9,19 +9,32 @@ import scala.math.ceil
final class Api(
threadRepo: ThreadRepo,
unreadCache: UnreadCache,
maxPerPage: Int) {
def inbox(user: User, page: Int): Paginator[Thread] = Paginator(
def inbox(me: User, page: Int): Paginator[Thread] = Paginator(
SalatAdapter(
dao = threadRepo,
query = threadRepo visibleByUserQuery user,
query = threadRepo visibleByUserQuery me,
sort = threadRepo.sortQuery),
currentPage = page,
maxPerPage = maxPerPage
) | inbox(user, 1)
) | inbox(me, 1)
def thread(id: String, user: User): IO[Option[Thread]] =
threadRepo byId id map {
_ filter (_ hasUser user)
def thread(id: String, me: User): IO[Option[Thread]] =
threadRepo byId id map {
_ filter (_ hasUser me)
}
def makeThread(data: DataForm.ThreadData, me: User) = {
val thread = Thread(
name = data.subject,
text = data.text,
creator = me,
invited = data.user)
for {
_ threadRepo saveIO thread
_ io(unreadCache invalidate data.user)
} yield thread
}
}

View file

@ -1,8 +1,40 @@
package lila
package message
import user.{ User, UserRepo }
import play.api.data._
import play.api.data.Forms._
object DataForm {
final class DataForm(userRepo: UserRepo) {
import DataForm._
val thread = Form(mapping(
"username" -> nonEmptyText.verifying("Unknown username", usernameExists _),
"subject" -> text(minLength = 3),
"text" -> text(minLength = 3)
)({
case (username, subject, text) ThreadData(
user = fetchUser(username) err "Unknown username " + username,
subject = subject,
text = text)
})(_.export.some))
private def fetchUser(username: String) =
(userRepo byUsername username).unsafePerformIO
private def usernameExists(username: String) =
fetchUser(username).isDefined
}
object DataForm {
case class ThreadData(
user: User,
subject: String,
text: String) {
def export = (user.username, subject, text)
}
}

View file

@ -15,11 +15,12 @@ final class MessageEnv(
lazy val threadRepo = new ThreadRepo(mongodb(MongoCollectionMessageThread))
lazy val unreadCache = new UnreadCache(threadRepo)
lazy val api = new Api(
threadRepo = threadRepo,
unreadCache = unreadCache,
maxPerPage = MessageThreadMaxPerPage)
lazy val unread = new UnreadCache(threadRepo)
lazy val forms = DataForm
lazy val forms = new DataForm(userRepo)
}

View file

@ -0,0 +1,13 @@
package lila
package message
import core.CoreEnv
import http.Context
trait MessageHelper {
protected def env: CoreEnv
def messageNbUnread(ctx: Context) =
ctx.me.fold(env.message.unreadCache.get, 0)
}

View file

@ -41,7 +41,7 @@ case class Thread(
def nonEmptyName = (name.trim.some filter ("" !=)) | "No subject"
}
object Topic {
object Thread {
val idSize = 8

View file

@ -15,5 +15,7 @@ final class UnreadCache(threadRepo: ThreadRepo) {
(threadRepo userNbUnread user).unsafePerformIO
})
def invalidate(user: User) = cache - user.usernameCanonical
def invalidate(user: User) {
cache - user.usernameCanonical
}
}

View file

@ -19,6 +19,7 @@ object Environment
with JsonHelper
with PaginatorHelper
with FormHelper
with message.MessageHelper
with round.RoundHelper
with game.GameHelper
with user.UserHelper

View file

@ -10,18 +10,19 @@ final class SiteMenu(trans: I18nKeys) {
import SiteMenu._
val play = new Elem(routes.Lobby.home, trans.play)
val game = new Elem(routes.Game.realtime, trans.games)
val user = new Elem(routes.User.list(page = 1), trans.people)
val forum = new Elem(routes.ForumCateg.index, trans.forum)
val message = new Elem(routes.Message.inbox(page = 1), trans.inbox)
val play = new Elem("play", routes.Lobby.home, trans.play)
val game = new Elem("game", routes.Game.realtime, trans.games)
val user = new Elem("user", routes.User.list(page = 1), trans.people)
val forum = new Elem("forum", routes.ForumCateg.index, trans.forum)
val message = new Elem("message", routes.Message.inbox(page = 1), trans.inbox)
val all = List(play, game, user, message, forum)
val all = List(play, game, user, forum, message)
}
object SiteMenu {
sealed class Elem(
val code: String,
val route: Call,
val name: I18nKeys#Key) {

View file

@ -1,4 +1,4 @@
@(form: Form[Option[User]])(implicit flash: Flash)
@(form: Form[_])(implicit flash: Flash)
<!DOCTYPE html>
<html>

View file

@ -18,7 +18,7 @@
<meta content="@trans.freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents()" name="description">
<meta name="google-site-verification" content="fZ08Imok7kcLaGcJg7BKQExO6vXGgSgsJUsW6JalUCo" />
<link rel="shortcut icon" href="@routes.Assets.at("favicon.ico")" type="image/x-icon" />
@if(!robots) {
@if(!robots) {
<meta content="noindex, nofollow" name="robots">
}
</head>
@ -39,8 +39,15 @@
</div>
<a href="@routes.Setting.color()" class="colorpicker" data-color="@setting.color">@trans.color()</a>
@siteMenu.all.map { elem =>
@if(elem.code == "message") {
<a class="goto_nav blank_if_play @elem.currentClass(active)" href="@elem.route">
@elem.name()
<span id="nb_messages" class="new_messages @{ (messageNbUnread(ctx) > 0).fold("unread", "") }">@messageNbUnread(ctx)</span>
</a>
} else {
<a class="goto_nav blank_if_play @elem.currentClass(active)" href="@elem.route">@elem.name()</a>
}
}
<div id="nb_connected_players" class="nb_connected_players none">
@trans.nbConnectedPlayers("<strong>0</strong>")
</div>
@ -85,12 +92,11 @@
<a href="@routes.Wiki.home()" class="blank_if_play">@trans.learnMoreAboutLichess()</a>
</div>
<a href="@routes.Wiki.home()">Wiki</a> |
<a href="#" title="Having a suggestion, feature request or bug report? Let me know">Feedback</a> |
<a href="@routes.ForumCateg.show("lichess-feedback", 1)" title="Having a suggestion, feature request or bug report? Let me know">Feedback</a> |
<a href="http://github.com/ornicar/lila" target="_blank" title="See what's inside, fork and contribute">Source Code</a><br />
<a href="#">Translate Lichess</a>
</div>
</div>
@jsTag("deps.min.js")
@jsTag("socket.js")
@jsTag("ctrl.js")

View file

@ -1,4 +1,4 @@
@(categ: lila.forum.Categ, form: Form[lila.forum.DataForm.TopicData], captcha: lila.site.Captcha.Challenge)(implicit ctx: Context)
@(categ: lila.forum.Categ, form: Form[_], captcha: lila.site.Captcha.Challenge)(implicit ctx: Context)
@forum.layout(
title = "New forum topic") {

View file

@ -1,4 +1,4 @@
@(categ: lila.forum.Categ, topic: lila.forum.Topic, posts: Paginator[lila.forum.Post], formWithCaptcha: Option[(Form[lila.forum.DataForm.PostData], lila.site.Captcha.Challenge)])(implicit ctx: Context)
@(categ: lila.forum.Categ, topic: lila.forum.Topic, posts: Paginator[lila.forum.Post], formWithCaptcha: Option[(Form[_], lila.site.Captcha.Challenge)])(implicit ctx: Context)
@forum.layout(
title = topic.name) {

View file

@ -0,0 +1,47 @@
@(form: Form[_])(implicit ctx: Context)
@message.layout(
title = trans.composeMessage.str()) {
<div class="message_new">
<h1>@trans.composeMessage()</h1>
<div class="center form">
<form action="@routes.Message.create()" method="post">
<div class="field_to">
<label for="@form("username").id">To:</label>
<input
type="text"
data-provider="@routes.User.autocomplete"
class="autocomplete"
required="required"
name="@form("username").name"
id="@form("username").id"
value="@form("username").value">
@errMsg(form("username"))
</div>
<div class="field_subject">
<label for="@form("subject").id">Subject:</label>
<input
type="text"
required="required"
name="@form("subject").name"
id="@form("subject").id"
value="@form("subject").value">
@errMsg(form("username"))
</div>
<div class="field_body">
<textarea
name="@form("text").name"
required="required"
id="@form("text").id">
@form("text").value
</textarea>
@errMsg(form("text"))
</div>
<div class="actions">
<input class="send button" type="submit" value="Send" />
<a class="cancel" href="@routes.Message.inbox()">@trans.cancel()</a>
</div>
</form>
</div>
</div>
}

View file

@ -6,12 +6,21 @@
@moreJs = {
@jsTag("vendor/jquery.infinitescroll.min.js")
@jsTag("vendor/jquery.ui.autocomplete.min.js")
}
@goodies = {
<div class="sidebar">
<a href="@routes.Message.inbox(1)" class="goto_inbox">@trans.inbox()</a>
<a href="@routes.Message.form" class="goto_compose">@trans.composeMessage()</a>
</div>
}
@base.layout(
title = title,
moreCss = moreCss,
moreJs = moreJs,
goodies = goodies.some,
active = siteMenu.message.some) {
<div id="lichess_message" class="content_box">
@body

View file

@ -1,4 +1,4 @@
@(form: Form[lila.setup.AiConfig])(implicit ctx: Context)
@(form: Form[_])(implicit ctx: Context)
@fields = {
<div class="variants buttons">

View file

@ -1,4 +1,4 @@
@(form: Form[lila.setup.FriendConfig])(implicit ctx: Context)
@(form: Form[_])(implicit ctx: Context)
@import lila.setup.FriendConfig

View file

@ -1,4 +1,4 @@
@(form: Form[lila.setup.HookConfig])(implicit ctx: Context)
@(form: Form[_])(implicit ctx: Context)
@import lila.setup.HookConfig
@import lila.elo.EloRange

View file

@ -233,7 +233,7 @@ a.goto_nav:hover, #top a.toggle:hover, a#sound_state:hover {
margin-top: 4px;
line-height: 16px;
}
a.goto_message span.new_messages {
a.goto_nav span.new_messages {
padding: 1px 4px;
background: #bbb;
color: white;
@ -243,7 +243,7 @@ a.goto_message span.new_messages {
-webkit-border-radius:4px 4px 4px 4px;
font-size: 0.9em;
}
a.goto_message span.new_messages.unread {
a.goto_nav span.new_messages.unread {
font-size: 1em;
}
a.goto_logout span.s16 {

View file

@ -98,7 +98,7 @@ div.sidebar a {
padding: 10px;
border: 1px solid #D4D4D4;
}
#lichess_message ul {
#lichess_message .error {
color: red;
margin-left: 60px;
}