forum post list, autolink urls

This commit is contained in:
Thibault Duplessis 2012-05-26 19:41:21 +02:00
parent 1c57aaf6dd
commit 8d801171d5
13 changed files with 163 additions and 54 deletions

View file

@ -0,0 +1,13 @@
package controllers
import lila._
import views._
object ForumPost extends LilaController {
def postApi = env.forum.postApi
def create(categSlug: String, slug: String) = TODO
def delete(id: String) = TODO
}

View file

@ -5,9 +5,15 @@ import views._
object ForumTopic extends LilaController {
def topicApi = env.forum.topicApi
def create(categ: String) = TODO
def show(categSlug: String, slug: String) = TODO
def show(categSlug: String, slug: String, page: Int) = Open { implicit ctx
IOptionOk(topicApi.show(categSlug, slug, page)) {
case (categ, topic, posts) html.forum.topic.show(categ, topic, posts)
}
}
def delete(id: String) = TODO
}

View file

@ -2,9 +2,9 @@ package lila
package forum
import scalaz.effects._
import com.github.ornicar.paginator._
import com.github.ornicar.paginator.Paginator
final class CategApi(env: ForumEnv, maxPerPage: Int) {
final class CategApi(env: ForumEnv) {
val list: IO[List[CategView]] = for {
categs env.categRepo.all

View file

@ -19,9 +19,11 @@ final class ForumEnv(
lazy val postRepo = new PostRepo(mongodb(MongoCollectionForumPost))
lazy val categApi = new CategApi(this, ForumTopicMaxPerPage)
lazy val categApi = new CategApi(this)
lazy val topicApi = new TopicApi(this, ForumPostMaxPerPage)
lazy val topicApi = new TopicApi(this, ForumTopicMaxPerPage)
lazy val postApi = new PostApi(this, ForumPostMaxPerPage)
lazy val denormalize = topicApi.denormalize flatMap { _ categApi.denormalize }
}

18
app/forum/PostApi.scala Normal file
View file

@ -0,0 +1,18 @@
package lila
package forum
import scalaz.effects._
import com.github.ornicar.paginator._
final class PostApi(env: ForumEnv, maxPerPage: Int) {
def paginator(topic: Topic, page: Int): Paginator[Post] =
Paginator(
SalatAdapter(
dao = env.postRepo,
query = env.postRepo byTopicQuery topic,
sort = env.postRepo.sortQuery),
currentPage = page,
maxPerPage = maxPerPage
) | paginator(topic, 1)
}

View file

@ -16,19 +16,21 @@ final class PostRepo(
}
def countByTopics(topics: List[Topic]): IO[Int] = io {
count(topicsQuery(topics)).toInt
count(byTopicsQuery(topics)).toInt
}
def lastByTopics(topics: List[Topic]): IO[Post] = io {
find(topicsQuery(topics)).sort(sortQuery).limit(1).next
find(byTopicsQuery(topics)).sort(sortQuery).limit(1).next
}
val all: IO[List[Post]] = io {
find(DBObject()).toList
}
private val sortQuery = DBObject("createdAt" -> -1)
val sortQuery = DBObject("createdAt" -> -1)
private def topicsQuery(topics: List[Topic]) =
def byTopicQuery(topic: Topic) = DBObject("topicId" -> topic.id)
private def byTopicsQuery(topics: List[Topic]) =
"topicId" $in topics.map(_.id)
}

View file

@ -6,6 +6,14 @@ import com.github.ornicar.paginator._
final class TopicApi(env: ForumEnv, maxPerPage: Int) {
def show(categSlug: String, slug: String, page: Int): IO[Option[(Categ, Topic, Paginator[Post])]] =
for {
categOption env.categRepo bySlug categSlug
topicOption env.topicRepo.byTree(categSlug, slug)
} yield categOption |@| topicOption apply {
case (categ, topic) (categ, topic, env.postApi.paginator(topic, page))
}
def paginator(categ: Categ, page: Int): Paginator[TopicView] =
Paginator(
SalatAdapter(

View file

@ -16,6 +16,13 @@ final class TopicRepo(
find(DBObject("categId" -> categ.slug)).toList
}
def byTree(categSlug: String, slug: String): IO[Option[Topic]] = io {
findOne(DBObject(
"categId" -> categSlug,
"slug" -> slug
))
}
val all: IO[List[Topic]] = io {
find(DBObject()).toList
}

View file

@ -1,10 +1,11 @@
package lila
package templating
import scala.math._
import java.text.SimpleDateFormat
import java.text.Normalizer
import java.util.Date
import org.apache.commons.lang3.StringEscapeUtils.escapeXml
import play.api.templates.Html
object StringHelper extends StringHelper
@ -22,10 +23,14 @@ trait StringHelper {
def pluralize(s: String, n: Int) = "%d %s%s".format(n, s, if (n > 1) "s" else "")
//implicit def richString(str: String) = new {
def autoLink(text: String) = Html {
addLinks(escapeXml(text)).replace("\n", "<br />")
}
//def capitalize = str(0).toUpperCase + str.drop(1)
//}
private val urlRegex = """(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))""".r
def addLinks(text: String) = urlRegex.replaceAllIn(text, m
"<a href='%s'>%s</a>".format(m group 1, m group 1))
def showNumber(n: Int): String = (n > 0).fold("+" + n, n.toString)
}

View file

@ -9,46 +9,46 @@ title = categ.name) {
</ol>
<p class="description">@categ.desc</p>
<div class="main topics">
<div class="bar top clearfix">
<div class="pagination">
@forum.pagination(routes.ForumCateg.show(categ.slug, 1), topics)
</div>
<a href="@routes.ForumTopic.create(categ.slug)" class="action button">Create a new topic</a>
<div class="bar top clearfix">
<div class="pagination">
@forum.pagination(routes.ForumCateg.show(categ.slug, 1), topics)
</div>
<a href="@routes.ForumTopic.create(categ.slug)" class="action button">Create a new topic</a>
</div>
<table class="forum_topics_list">
<thead>
<tr class="thead">
<th>Topics</th>
<th class="right">Views</th>
<th class="right">Replies</th>
<th>Last Post</th>
</tr>
</thead>
<tbody>
@topics.currentPageResults.map { topic =>
<tr>
<td class="subject"><a href="@routes.ForumTopic.show(categ.slug, topic.slug)">@topic.name</a></td>
<td class="right">@topic.views</td>
<td class="right">@topic.nbPosts</td>
<td class="last_post">
@topic.lastPost.map { post =>
<a href="@routes.ForumTopic.show(categ.slug, topic.slug)#@post.number">@showDate(post.createdAt)</a><br />by @userIdToUsername(post.userId)
}
</td>
@if(isGranted(Permission.ModerateForum)) {
<td><a class="delete" href="@routes.ForumTopic.delete(topic.id)">Delete</a></td>
<table class="forum_topics_list">
<thead>
<tr class="thead">
<th>Topics</th>
<th class="right">Views</th>
<th class="right">Replies</th>
<th>Last Post</th>
</tr>
</thead>
<tbody>
@topics.currentPageResults.map { topic =>
<tr>
<td class="subject"><a href="@routes.ForumTopic.show(categ.slug, topic.slug)">@topic.name</a></td>
<td class="right">@topic.views</td>
<td class="right">@topic.nbPosts</td>
<td class="last_post">
@topic.lastPost.map { post =>
<a href="@routes.ForumTopic.show(categ.slug, topic.slug)#@post.number">@showDate(post.createdAt)</a><br />by @userIdToUsername(post.userId)
}
</tr>
</td>
@if(isGranted(Permission.ModerateForum)) {
<td><a class="delete" href="@routes.ForumTopic.delete(topic.id)">Delete</a></td>
}
</tbody>
</table>
</tr>
}
</tbody>
</table>
<div class="bar bottom clearfix">
<div class="pagination">pagination</div>
<a href="@routes.ForumTopic.create(categ.slug)" class="action button">Create a new topic</a>
<div class="bar bottom clearfix">
<div class="pagination">
@forum.pagination(routes.ForumCateg.show(categ.slug, 1), topics)
</div>
<a href="@routes.ForumTopic.create(categ.slug)" class="action button">Create a new topic</a>
</div>
</div>
}

View file

@ -0,0 +1,46 @@
@(categ: lila.forum.Categ, topic: lila.forum.Topic, posts: Paginator[lila.forum.Post])(implicit ctx: Context)
@forum.layout(
title = topic.name) {
<div class="topic">
<ol class="crumbs">
<li><a href="@routes.ForumCateg.index">Forum</a></li>
<li><a href="@routes.ForumCateg.show(categ.slug)">@categ.name</a></li>
<li><h1>@topic.name</h1></li>
</ol>
<div class="bar top clearfix">
<div class="pagination">
@forum.pagination(routes.ForumTopic.show(categ.slug, topic.slug, 1), posts)
</div>
</div>
<div class="forum_posts_list">
@for(post <- posts.currentPageResults) {
<div class="post" id="@post.number">
<div class="metas clearfix">
@post.userId.map { userId =>
<span class="authorName authenticated">@userIdLink(userId.some)</span>
}.getOrElse {
<span class="authorName">@post.author</span>
}
<span class="createdAt">@showDate(post.createdAt)</span>
<a class="anchor" href="routes.ForumTopic.show(categ.slug, topic.slug, posts.page)">#@post.number</a>
@if(isGranted(Permission.ModerateForum)) {
<a class="delete" href="@routes.ForumPost.delete(post.id)">Delete</a>
}
</div>
<p class="message">@autoLink(post.text)</p>
</div>
}
</div>
<div class="bar bottom clearfix">
<div class="pagination">
@forum.pagination(routes.ForumTopic.show(categ.slug, topic.slug, 1), posts)
</div>
@if(posts.currentPage == posts.nbPages) {
<a href="@routes.ForumPost.create(categ.slug, topic.slug)" class="action button">Reply to this topic</a>
}
</div>
</div>
}

View file

@ -92,8 +92,10 @@ GET /lobby/socket controllers.Lobby.socket
GET /forum controllers.ForumCateg.index
GET /forum/:slug controllers.ForumCateg.show(slug: String, page: Int ?= 1)
GET /forum/:categSlug/new controllers.ForumTopic.create(categSlug: String)
GET /forum/:categSlug/:slug controllers.ForumTopic.show(categSlug: String, slug: String)
GET /forum/delete-topic/:id controllers.ForumTopic.delete(id: String)
GET /forum/:categSlug/:slug controllers.ForumTopic.show(categSlug: String, slug: String, page: Int ?= 1)
GET /forum/:categSlug/:slug/new controllers.ForumPost.create(categSlug: String, slug: String)
GET /forum/delete/topic/:id controllers.ForumTopic.delete(id: String)
GET /forum/delete/post/:id controllers.ForumPost.delete(id: String)
# Monitor
GET /monitor controllers.Monitor.index

View file

@ -45,9 +45,9 @@ var topicIds = {};
}
nb ++;
}
coll.ensureIndex({categ: 1, slug: 1}, {unique: true});
coll.ensureIndex({categ: 1});
coll.ensureIndex({categ: 1, updatedAt: -1});
coll.ensureIndex({categId: 1, slug: 1}, {unique: true});
coll.ensureIndex({categId: 1});
coll.ensureIndex({categId: 1, updatedAt: -1});
print("Done topics: " + nb);
})(db.forum_topic, db.f_topic);
@ -74,8 +74,8 @@ var topicIds = {};
}
nb ++;
}
coll.ensureIndex({topic: 1});
coll.ensureIndex({topic: 1, createdAt: -1});
coll.ensureIndex({topicId: 1});
coll.ensureIndex({topicId: 1, createdAt: -1});
print("Done posts: " + nb);
})(db.forum_post, db.f_post);