diff --git a/app/core/CoreEnv.scala b/app/core/CoreEnv.scala index 968210043e..a81d582ce7 100644 --- a/app/core/CoreEnv.scala +++ b/app/core/CoreEnv.scala @@ -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( diff --git a/app/forum/Categ.scala b/app/forum/Categ.scala index d86f551a90..480df70428 100644 --- a/app/forum/Categ.scala +++ b/app/forum/Categ.scala @@ -16,4 +16,6 @@ case class Categ( def isStaff = slug == "staff" def isTeam = team.nonEmpty + + def id = slug } diff --git a/app/forum/CategApi.scala b/app/forum/CategApi.scala index fe8ffd18cd..5e68eb29d1 100644 --- a/app/forum/CategApi.scala +++ b/app/forum/CategApi.scala @@ -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 diff --git a/app/forum/CategRepo.scala b/app/forum/CategRepo.scala index 071f10760c..edab57ee17 100644 --- a/app/forum/CategRepo.scala +++ b/app/forum/CategRepo.scala @@ -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 } diff --git a/app/forum/Post.scala b/app/forum/Post.scala index 5a1d890840..abf5ccfbcd 100644 --- a/app/forum/Post.scala +++ b/app/forum/Post.scala @@ -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) } diff --git a/app/forum/PostApi.scala b/app/forum/PostApi.scala index 7c5129867e..3a1bbbc825 100644 --- a/app/forum/PostApi.scala +++ b/app/forum/PostApi.scala @@ -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() diff --git a/app/forum/PostRepo.scala b/app/forum/PostRepo.scala index c8727e81c1..bbc8d830d5 100644 --- a/app/forum/PostRepo.scala +++ b/app/forum/PostRepo.scala @@ -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) diff --git a/app/forum/Recent.scala b/app/forum/Recent.scala index f5f6ae92e4..874225a7d5 100644 --- a/app/forum/Recent.scala +++ b/app/forum/Recent.scala @@ -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 } diff --git a/app/forum/TopicApi.scala b/app/forum/TopicApi.scala index 611acdcd36..f02d913987 100644 --- a/app/forum/TopicApi.scala +++ b/app/forum/TopicApi.scala @@ -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( diff --git a/app/forum/TopicRepo.scala b/app/forum/TopicRepo.scala index a80ee8a906..eff85cb069 100644 --- a/app/forum/TopicRepo.scala +++ b/app/forum/TopicRepo.scala @@ -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 } diff --git a/app/team/TeamEnv.scala b/app/team/TeamEnv.scala index a893520f42..501fb68146 100644 --- a/app/team/TeamEnv.scala +++ b/app/team/TeamEnv.scala @@ -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) diff --git a/app/team/TeamInfo.scala b/app/team/TeamInfo.scala index 86b4825a3a..0be0ac69b4 100644 --- a/app/team/TeamInfo.scala +++ b/app/team/TeamInfo.scala @@ -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) } diff --git a/app/templating/AssetHelper.scala b/app/templating/AssetHelper.scala index c3899c2064..44dd20106f 100644 --- a/app/templating/AssetHelper.scala +++ b/app/templating/AssetHelper.scala @@ -7,7 +7,7 @@ import play.api.templates.Html trait AssetHelper { - val assetVersion = 17 + val assetVersion = 18 def cssTag(name: String) = css("stylesheets/" + name) diff --git a/app/views/forum/post/recent.scala.html b/app/views/forum/post/recent.scala.html index 5fc2e040e8..35fa6d7630 100644 --- a/app/views/forum/post/recent.scala.html +++ b/app/views/forum/post/recent.scala.html @@ -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 =>
  • @v.topic.name diff --git a/app/views/team/show.scala.html b/app/views/team/show.scala.html index aa9c7546e2..ec9dfe6ec2 100644 --- a/app/views/team/show.scala.html +++ b/app/views/team/show.scala.html @@ -18,7 +18,6 @@

    @trans.averageElo(): @info.averageElo

    @trans.teamLeader(): @userIdLinkMini(t.createdBy)

    -

    @trans.forum() (@info.forumNbPosts)

    @trans.teamBestPlayers()

      @@ -50,14 +49,34 @@ } } - @if(info.mine && !info.createdByMe) { -
      - -
      - } - @if(info.createdByMe) { - @trans.settings() - } + +
      +

      @trans.forum() (@info.forumNbPosts)

      +
        + @info.forumPosts.take(5).map { v => + @defining(routes.ForumTopic.show(v.categ.slug, v.topic.slug, v.topicLastPage) + "#" + v.post.number) { postUrl => +
      1. +

        + @v.topic.name + @authorLink(v.post, withOnline = false) + @showDate(v.post.createdAt) +

        +

        @shorten(v.post.text, 200)

        +
      2. + } + } +
      + @t.name @trans.forum() » +
      + @if(info.mine && !info.createdByMe) { +
      + +
      + } + @if(info.createdByMe) { + @trans.settings() + } +
      diff --git a/public/stylesheets/dark.css b/public/stylesheets/dark.css index a36e9a33ec..aaabc7162b 100644 --- a/public/stylesheets/dark.css +++ b/public/stylesheets/dark.css @@ -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), diff --git a/public/stylesheets/team.css b/public/stylesheets/team.css index 225939b878..9becf61202 100644 --- a/public/stylesheets/team.css +++ b/public/stylesheets/team.css @@ -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; +} diff --git a/todo b/todo index 117c22cc03..d071fc0520 100644 --- a/todo +++ b/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})