teams frontend
parent
05aabfd2c1
commit
fe9847a938
|
@ -57,6 +57,7 @@ final class CoreEnv private (application: Application, val settings: Settings) {
|
|||
sendMessage = message.api.lichessThread,
|
||||
makeForum = forum.categApi.makeTeam,
|
||||
getForumNbPosts = forum.categApi.getTeamNbPosts,
|
||||
getForumPosts = forum.recent.team,
|
||||
mongodb = mongodb.apply _)
|
||||
|
||||
lazy val lobby = new lila.lobby.LobbyEnv(
|
||||
|
|
|
@ -16,4 +16,6 @@ case class Categ(
|
|||
def isStaff = slug == "staff"
|
||||
|
||||
def isTeam = team.nonEmpty
|
||||
|
||||
def id = slug
|
||||
}
|
||||
|
|
|
@ -40,7 +40,8 @@ final class CategApi(env: ForumEnv) {
|
|||
userId = "lichess".some,
|
||||
ip = none,
|
||||
text = "Welcome to the %s forum!\nOnly members of the team can post here" format name,
|
||||
number = 1)
|
||||
number = 1,
|
||||
categId = categ.id)
|
||||
_ ← env.categRepo saveIO categ
|
||||
_ ← env.postRepo saveIO post
|
||||
// denormalize topic
|
||||
|
|
|
@ -14,6 +14,10 @@ final class CategRepo(
|
|||
findOneById(slug)
|
||||
}
|
||||
|
||||
def byIds(ids: Iterable[String]): IO[List[Categ]] = io {
|
||||
find("_id" $in ids).toList
|
||||
}
|
||||
|
||||
val all: IO[List[Categ]] = io {
|
||||
find(DBObject()).sort(DBObject("pos" -> 1)).toList
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import user.User
|
|||
case class Post(
|
||||
@Key("_id") id: String,
|
||||
topicId: String,
|
||||
categId: String,
|
||||
author: Option[String],
|
||||
userId: Option[String],
|
||||
ip: Option[String],
|
||||
|
@ -26,6 +27,7 @@ object Post {
|
|||
|
||||
def apply(
|
||||
topicId: String,
|
||||
categId: String,
|
||||
author: Option[String],
|
||||
userId: Option[String],
|
||||
ip: Option[String],
|
||||
|
@ -38,5 +40,6 @@ object Post {
|
|||
ip = ip,
|
||||
text = text,
|
||||
number = number,
|
||||
createdAt = DateTime.now)
|
||||
createdAt = DateTime.now,
|
||||
categId = categId)
|
||||
}
|
||||
|
|
|
@ -33,7 +33,8 @@ final class PostApi(
|
|||
userId = ctx.me map (_.id),
|
||||
ip = ctx.isAnon option ctx.req.remoteAddress,
|
||||
text = data.text,
|
||||
number = number + 1)
|
||||
number = number + 1,
|
||||
categId = categ.id)
|
||||
_ ← env.postRepo saveIO post
|
||||
// denormalize topic
|
||||
_ ← env.topicRepo saveIO topic.copy(
|
||||
|
@ -55,15 +56,18 @@ final class PostApi(
|
|||
)
|
||||
} yield (topicOption |@| postOption).tupled
|
||||
|
||||
def view(post: Post): IO[Option[PostView]] = for {
|
||||
topicOption ← env.topicRepo byId post.topicId
|
||||
categOption ← topicOption.fold(
|
||||
topic ⇒ env.categRepo bySlug topic.categId,
|
||||
io(none[Categ])
|
||||
)
|
||||
} yield topicOption |@| categOption apply {
|
||||
case (topic, categ) ⇒ PostView(post, topic, categ, lastPageOf(topic))
|
||||
}
|
||||
def views(posts: List[Post]): IO[List[PostView]] = for {
|
||||
topics ← env.topicRepo byIds posts.map(_.topicId).distinct
|
||||
categs ← env.categRepo byIds topics.map(_.categId).distinct
|
||||
} yield (for {
|
||||
post ← posts
|
||||
} yield for {
|
||||
topic ← topics find (_.id == post.topicId)
|
||||
categ ← categs find (_.slug == topic.categId)
|
||||
} yield PostView(post, topic, categ, lastPageOf(topic))
|
||||
).flatten
|
||||
|
||||
def view(post: Post): IO[Option[PostView]] = views(List(post)) map (_.headOption)
|
||||
|
||||
def lastNumberOf(topic: Topic): IO[Int] =
|
||||
env.postRepo lastByTopics List(topic) map (_.number)
|
||||
|
@ -96,7 +100,7 @@ final class PostApi(
|
|||
} yield ()
|
||||
)
|
||||
post = view.post
|
||||
_ ← modLog.deletePost(mod, post.userId, post.author, post.ip,
|
||||
_ ← modLog.deletePost(mod, post.userId, post.author, post.ip,
|
||||
text = "%s / %s / %s".format(view.categ.name, view.topic.name, post.text))
|
||||
} yield (),
|
||||
io()
|
||||
|
|
|
@ -26,8 +26,8 @@ final class PostRepo(
|
|||
find(DBObject()).toList
|
||||
}
|
||||
|
||||
def recent(nb: Int): IO[List[Post]] = io {
|
||||
find(DBObject()).sort(sortQuery(-1)).limit(nb).toList.reverse
|
||||
def recentInCategs(nb: Int)(categIds: List[String]): IO[List[Post]] = io {
|
||||
find("categId" $in categIds).sort(sortQuery(-1)).limit(nb).toList
|
||||
}
|
||||
|
||||
val sortQuery: DBObject = sortQuery(1)
|
||||
|
|
|
@ -9,22 +9,40 @@ import scalaz.effects._
|
|||
|
||||
final class Recent(postRepo: PostRepo, postApi: PostApi, timeout: Int) {
|
||||
|
||||
val nb = 30
|
||||
private val nb = 20
|
||||
|
||||
private val cache = Builder.cache[Boolean, List[PostView]](timeout, staff ⇒
|
||||
fetch(staff).unsafePerformIO
|
||||
private val Public = "$public"
|
||||
private val Staff = "$staff"
|
||||
private def teamSlug(id: String) = "team-" + id
|
||||
|
||||
private lazy val publicCategIds = List(
|
||||
"game-analysis",
|
||||
"general-chess-discussion",
|
||||
"lichess-feedback",
|
||||
"off-topic-discussion")
|
||||
|
||||
private lazy val staffCategIds = "staff" :: publicCategIds
|
||||
|
||||
private val cache = Builder.cache[String, List[PostView]](timeout, key ⇒
|
||||
fetch(key).unsafePerformIO
|
||||
)
|
||||
|
||||
def apply(user: Option[User]): IO[List[PostView]] = io {
|
||||
cache get Granter.option(Permission.StaffForum)(user)
|
||||
cache get Granter.option(Permission.StaffForum)(user).fold(Staff, Public)
|
||||
}
|
||||
|
||||
def team(teamId: String): IO[List[PostView]] = io {
|
||||
cache get teamId
|
||||
}
|
||||
|
||||
val invalidate: IO[Unit] = io(cache.invalidateAll)
|
||||
|
||||
private def fetch(staff: Boolean): IO[List[PostView]] = for {
|
||||
posts ← postRepo.recent(nb)
|
||||
views ← (posts map postApi.view).sequence
|
||||
} yield views collect {
|
||||
case Some(v) if (staff || v.categ.slug != "staff") ⇒ v
|
||||
}
|
||||
private def fetch(key: String): IO[List[PostView]] = for {
|
||||
posts ← postRepo.recentInCategs(nb)(key match {
|
||||
case Public ⇒ publicCategIds
|
||||
case Staff ⇒ staffCategIds
|
||||
case teamId ⇒ List(teamSlug(teamId))
|
||||
})
|
||||
views ← postApi views posts
|
||||
} yield views
|
||||
}
|
||||
|
|
|
@ -33,7 +33,8 @@ final class TopicApi(env: ForumEnv, maxPerPage: Int) {
|
|||
userId = ctx.me map (_.id),
|
||||
ip = ctx.isAnon option ctx.req.remoteAddress,
|
||||
text = data.post.text,
|
||||
number = 1)
|
||||
number = 1,
|
||||
categId = categ.id)
|
||||
_ ← env.postRepo saveIO post
|
||||
// denormalize topic
|
||||
_ ← env.topicRepo saveIO topic.copy(
|
||||
|
|
|
@ -14,6 +14,10 @@ final class TopicRepo(
|
|||
findOneById(id)
|
||||
}
|
||||
|
||||
def byIds(ids: Iterable[String]): IO[List[Topic]] = io {
|
||||
find("_id" $in ids).toList
|
||||
}
|
||||
|
||||
def byCateg(categ: Categ): IO[List[Topic]] = io {
|
||||
find(DBObject("categId" -> categ.slug)).toList
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import core.Settings
|
|||
import site.Captcha
|
||||
import user.UserRepo
|
||||
import message.LichessThread
|
||||
import forum.Categ
|
||||
import forum.{ Categ, PostView }
|
||||
|
||||
import com.mongodb.casbah.MongoCollection
|
||||
import scalaz.effects._
|
||||
|
@ -17,6 +17,7 @@ final class TeamEnv(
|
|||
sendMessage: LichessThread ⇒ IO[Unit],
|
||||
makeForum: (String, String) ⇒ IO[Unit],
|
||||
getForumNbPosts: String ⇒ IO[Int],
|
||||
getForumPosts: String ⇒ IO[List[PostView]],
|
||||
mongodb: String ⇒ MongoCollection) {
|
||||
|
||||
import settings._
|
||||
|
@ -51,7 +52,8 @@ final class TeamEnv(
|
|||
memberRepo = memberRepo,
|
||||
requestRepo = requestRepo,
|
||||
userRepo = userRepo,
|
||||
getForumNbPosts = getForumNbPosts) _
|
||||
getForumNbPosts = getForumNbPosts,
|
||||
getForumPosts = getForumPosts) _
|
||||
|
||||
lazy val forms = new DataForm(teamRepo, captcha)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package team
|
|||
|
||||
import user.{ User, UserRepo }
|
||||
import game.{ GameRepo, DbGame }
|
||||
import forum.Categ
|
||||
import forum.PostView
|
||||
import http.Context
|
||||
|
||||
import scalaz.effects._
|
||||
|
@ -15,15 +15,8 @@ case class TeamInfo(
|
|||
requests: List[RequestWithUser],
|
||||
bestPlayers: List[User],
|
||||
averageElo: Int,
|
||||
forumNbPosts: Int) {
|
||||
// rank: Option[(Int, Int)],
|
||||
// nbPlaying: Int,
|
||||
// nbWithMe: Option[Int],
|
||||
// nbBookmark: Int,
|
||||
// eloWithMe: Option[List[(String, Int)]],
|
||||
// eloChart: Option[EloChart],
|
||||
// winChart: Option[WinChart],
|
||||
// spy: Option[TeamSpy]) {
|
||||
forumNbPosts: Int,
|
||||
forumPosts: List[PostView]) {
|
||||
|
||||
def hasRequests = requests.nonEmpty
|
||||
}
|
||||
|
@ -35,7 +28,8 @@ object TeamInfo {
|
|||
memberRepo: MemberRepo,
|
||||
requestRepo: RequestRepo,
|
||||
userRepo: UserRepo,
|
||||
getForumNbPosts: String ⇒ IO[Int])(team: Team, me: Option[User]): IO[TeamInfo] = for {
|
||||
getForumNbPosts: String ⇒ IO[Int],
|
||||
getForumPosts: String ⇒ IO[List[PostView]])(team: Team, me: Option[User]): IO[TeamInfo] = for {
|
||||
mine ← ~me.map(api.belongsTo(team, _))
|
||||
requestedByMe ← ~me.map(m ⇒ requestRepo.exists(team.id, m.id)) doUnless mine
|
||||
requests ← api.requestsWithUsers(team) doIf {
|
||||
|
@ -45,6 +39,7 @@ object TeamInfo {
|
|||
bestPlayers ← userRepo.byIdsSortByElo(userIds, 5)
|
||||
averageElo ← userRepo.idsAverageElo(userIds)
|
||||
forumNbPosts ← getForumNbPosts(team.id)
|
||||
forumPosts ← getForumPosts(team.id)
|
||||
} yield TeamInfo(
|
||||
mine = mine,
|
||||
createdByMe = ~me.map(m ⇒ team.isCreator(m.id)),
|
||||
|
@ -52,5 +47,6 @@ object TeamInfo {
|
|||
requests = requests,
|
||||
bestPlayers = bestPlayers,
|
||||
averageElo = averageElo,
|
||||
forumNbPosts = forumNbPosts)
|
||||
forumNbPosts = forumNbPosts,
|
||||
forumPosts = forumPosts)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import play.api.templates.Html
|
|||
|
||||
trait AssetHelper {
|
||||
|
||||
val assetVersion = 17
|
||||
val assetVersion = 18
|
||||
|
||||
def cssTag(name: String) = css("stylesheets/" + name)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@(posts: List[lila.forum.PostView])(implicit ctx: Context)
|
||||
@for(v <- posts) {
|
||||
@for(v <- posts.reverse) {
|
||||
@defining(routes.ForumTopic.show(v.categ.slug, v.topic.slug, v.topicLastPage) + "#" + v.post.number) { postUrl =>
|
||||
<li>
|
||||
<a class="post_link" href="@postUrl">@v.topic.name</a>
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
<section class="infos">
|
||||
<p>@trans.averageElo(): <strong>@info.averageElo</strong></p>
|
||||
<p>@trans.teamLeader(): @userIdLinkMini(t.createdBy)</p>
|
||||
<p><a href="@teamForumUrl(t.id)">@trans.forum() (@info.forumNbPosts)</a>
|
||||
</section>
|
||||
<h2>@trans.teamBestPlayers()</h2>
|
||||
<ol class="userlist">
|
||||
|
@ -50,14 +49,34 @@
|
|||
</form>
|
||||
}
|
||||
}
|
||||
@if(info.mine && !info.createdByMe) {
|
||||
<form class="inline" method="post" action="@routes.Team.quit(t.id)">
|
||||
<input class="submit button" type="submit" value="@trans.quitTeam()" />
|
||||
</form>
|
||||
}
|
||||
@if(info.createdByMe) {
|
||||
<a href="@routes.Team.edit(t.id)" class="submit button">@trans.settings()</a>
|
||||
}
|
||||
</div>
|
||||
<div class="forum">
|
||||
<h2><a href="@teamForumUrl(t.id)">@trans.forum() (@info.forumNbPosts)</a></h2>
|
||||
<ol class="posts">
|
||||
@info.forumPosts.take(5).map { v =>
|
||||
@defining(routes.ForumTopic.show(v.categ.slug, v.topic.slug, v.topicLastPage) + "#" + v.post.number) { postUrl =>
|
||||
<li>
|
||||
<p class="meta clearfix">
|
||||
<a href="@postUrl">@v.topic.name</a>
|
||||
@authorLink(v.post, withOnline = false)
|
||||
<span class="date">@showDate(v.post.createdAt)</span>
|
||||
</p>
|
||||
<p>@shorten(v.post.text, 200)</p>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</ol>
|
||||
<a class="more" href="@teamForumUrl(t.id)">@t.name @trans.forum() »</a>
|
||||
<div class="actions">
|
||||
@if(info.mine && !info.createdByMe) {
|
||||
<form class="quit" method="post" action="@routes.Team.quit(t.id)">
|
||||
<input class="submit button small confirm" type="submit" value="@trans.quitTeam()" />
|
||||
</form>
|
||||
}
|
||||
@if(info.createdByMe) {
|
||||
<a href="@routes.Team.edit(t.id)" class="submit button small">@trans.settings()</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -93,7 +93,8 @@ body.dark div.notifications > div,
|
|||
body.dark form.wide input[type="text"],
|
||||
body.dark form.wide textarea,
|
||||
body.dark #team .team-left,
|
||||
body.dark #team .team-left h2
|
||||
body.dark #team .team-right,
|
||||
body.dark #team h2
|
||||
{
|
||||
border-color: #3e3e3e;
|
||||
}
|
||||
|
@ -117,6 +118,7 @@ body.dark div.anon_chat span,
|
|||
body.dark div.anon_chat a.user_link,
|
||||
body.dark div.undertable a.user_link,
|
||||
body.dark div.new_posts li span,
|
||||
body.dark #team .forum a.user_link,
|
||||
body.dark span.board_mark
|
||||
{
|
||||
color: #808080;
|
||||
|
@ -214,7 +216,7 @@ body.dark div.user_show .elo_with_me,
|
|||
body.dark div.content_box_inter,
|
||||
body.dark #GameText tr:nth-child(even),
|
||||
body.dark table.slist tbody tr:nth-child(even),
|
||||
body.dark #team .userlist li:nth-child(even),
|
||||
body.dark #team .forum li:nth-child(odd),
|
||||
body.dark table.translations tbody tr:nth-child(even),
|
||||
body.dark form.translation_form div.message:nth-child(even),
|
||||
body.dark div.content_box table.datatable tr:nth-child(odd),
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
#team form textarea {
|
||||
height: 8em;
|
||||
}
|
||||
#team form .submit {
|
||||
#team form.wide .submit {
|
||||
margin-left: 110px;
|
||||
}
|
||||
|
||||
|
@ -37,12 +37,16 @@
|
|||
color: red;
|
||||
}
|
||||
|
||||
#team table a.team-name {
|
||||
#team table.slist td {
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
#team table.slist a.team-name {
|
||||
font-size: 1.3em;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
#team table .info {
|
||||
#team table.slist .info {
|
||||
font-size: 0.9em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
@ -66,17 +70,19 @@
|
|||
#team .team-left h2 {
|
||||
font-size: 1.0em;
|
||||
font-weight: bold;
|
||||
padding-left: 25px;
|
||||
padding: 0 0 5px 25px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
#team .team-left .infos {
|
||||
margin: 20px 0 20px 25px;
|
||||
}
|
||||
#team .team-right {
|
||||
margin-left: 300px;
|
||||
margin-left: 280px;
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
#team .description {
|
||||
margin: 20px 0;
|
||||
font-size: 1.2em;
|
||||
margin: 20px 0 20px 20px;
|
||||
}
|
||||
|
||||
#team .userlist {
|
||||
|
@ -87,9 +93,48 @@
|
|||
padding: 10px 0 10px 25px;
|
||||
}
|
||||
|
||||
#team .userlist li:nth-child(even) {
|
||||
#team .actions {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#team .forum {
|
||||
margin-top: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
#team .forum h2 {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding: 0 0 5px 20px;
|
||||
}
|
||||
#team .forum h2 a {
|
||||
text-decoration: none;
|
||||
}
|
||||
#team .forum li {
|
||||
padding: 20px 10px 20px 20px;
|
||||
}
|
||||
#team .forum li:nth-child(odd) {
|
||||
background: none repeat scroll 0 0 #F4F4F4;
|
||||
}
|
||||
#team .forum a.user_link {
|
||||
font-weight: bold;
|
||||
margin-right: 6px;
|
||||
color: #aaa;
|
||||
text-decoration: none;
|
||||
}
|
||||
#team .forum .date {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
#team .forum .meta > * {
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
}
|
||||
#team .forum a.more {
|
||||
display: block;
|
||||
text-align: right;
|
||||
margin: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
#team table.requests form {
|
||||
margin: 0;
|
||||
|
@ -111,3 +156,7 @@
|
|||
display: inline;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#team .button.small {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
3
todo
3
todo
|
@ -54,5 +54,6 @@ db.team.ensureIndex({createdAt: -1})
|
|||
db.team_member.ensureIndex({team:1})
|
||||
db.team_member.ensureIndex({user:1})
|
||||
db.team_member.ensureIndex({date: -1})
|
||||
db.team_request.ensureIndex({team:1})
|
||||
db.team_request.ensureIndex({team: 1})
|
||||
db.team_request.ensureIndex({date: -1})
|
||||
db.f_post.ensureIndex({categId: 1})
|
||||
|
|
Loading…
Reference in New Issue