Merge pull request #2256 from Happy0/post_edit

[WIP] Allow users to edit their forum posts
This commit is contained in:
Thibault Duplessis 2016-09-17 10:33:45 +02:00 committed by GitHub
commit 530dafd58c
9 changed files with 168 additions and 21 deletions

View file

@ -1,7 +1,6 @@
package controllers
import scala.concurrent.duration._
import lila.common.HTTPRequest
import lila.app._
import views._
@ -53,6 +52,18 @@ object ForumPost extends LilaController with ForumController {
}
}
def edit(postId: String) = AuthBody { implicit ctx =>
me =>
implicit val req = ctx.body
forms.postEdit.bindFromRequest.fold(err => Redirect(routes.ForumPost.redirect(postId)).fuccess,
data =>
postApi.editPost(postId, data.changes, me).map { post =>
Redirect(routes.ForumPost.redirect(post.id))
}
)
}
def delete(categSlug: String, id: String) = Auth { implicit ctx =>
me =>
CategGrantMod(categSlug) {

View file

@ -8,6 +8,7 @@
@forum.layout(
title = s"${categ.name} / ${topic.name} page ${posts.currentPage}",
menu = modMenu.some.ifTrue(categ.isStaff),
moreJs = jsTag("forum-post.js"),
openGraph = lila.app.ui.OpenGraph(
title = s"Forum: ${categ.name} / ${topic.name} page ${posts.currentPage}",
url = s"$netBaseUrl${routes.ForumTopic.show(categ.slug, topic.slug).url}",
@ -36,8 +37,16 @@ description = shorten(posts.currentPageResults.headOption.??(_.text).replace("\n
<div class="post" id="@post.number">
<div class="metas clearfix">
@authorLink(post, "author".some)
@momentFromNow(post.createdAt)
@if(post.hasEdits) {
@momentFromNow(post.updatedAt)
<span class="post-edited"> - Edited</span>
} else {
@momentFromNow(post.createdAt)
}
<a class="anchor" href="@routes.ForumTopic.show(categ.slug, topic.slug, posts.currentPage)#@post.number">#@post.number</a>
@if(isGranted(_.IpBan)) {
<span class="mod postip">@post.ip</span>
}
@ -46,6 +55,11 @@ description = shorten(posts.currentPageResults.headOption.??(_.text).replace("\n
}
</div>
<p class="message">@autoLink(post.text)</p>
@if(ctx.userId.fold(false)(post.canBeEditedBy(_)) && post.canStillBeEdited) {
<div class="edit-buttons">
<a class="mod" id="edit-button-@post.number" data-icon="r" onclick="editForumPost('@post.id', @post.number)">Edit</a>
</div>
}
</div>
}
</div>

View file

@ -362,6 +362,7 @@ POST /forum/:categSlug/:slug/close controllers.ForumTopic.close(categSlug: S
POST /forum/:categSlug/:slug/hide controllers.ForumTopic.hide(categSlug: String, slug: String)
POST /forum/:categSlug/:slug/new controllers.ForumPost.create(categSlug: String, slug: String, page: Int ?= 1)
POST /forum/:categSlug/delete/:id controllers.ForumPost.delete(categSlug: String, id: String)
POST /forum/post/:id controllers.ForumPost.edit(id: String)
GET /forum/redirect/post/:id controllers.ForumPost.redirect(id: String)
# Message

View file

@ -7,7 +7,10 @@ import reactivemongo.bson._
private object BSONHandlers {
implicit val CategBSONHandler = Macros.handler[Categ]
implicit val PostEditBSONHandler = Macros.handler[OldVersion]
implicit val PostBSONHandler = Macros.handler[Post]
private val topicHandler: BSONDocumentReader[Topic] with BSONDocumentWriter[Topic] with BSONHandler[Bdoc, Topic] = Macros.handler[Topic]
implicit val TopicBSONHandler: BSONDocumentReader[Topic] with BSONDocumentWriter[Topic] with BSONHandler[Bdoc, Topic] = LoggingHandler(logger)(topicHandler)
}

View file

@ -17,6 +17,8 @@ private[forum] final class DataForm(val captcher: akka.actor.ActorSelection) ext
val post = Form(postMapping)
val postEdit = Form(mapping("changes" -> text(minLength=3))(PostEdit.apply)(PostEdit.unapply))
def postWithCaptcha = withCaptcha(post)
val topic = Form(mapping(
@ -36,4 +38,6 @@ object DataForm {
case class TopicData(
name: String,
post: PostData)
case class PostEdit(changes: String)
}

View file

@ -2,8 +2,10 @@ package lila.forum
import org.joda.time.DateTime
import ornicar.scalalib.Random
import lila.user.User
import scala.concurrent.duration._
case class OldVersion(text: String, createdAt: DateTime)
case class Post(
_id: String,
@ -17,7 +19,11 @@ case class Post(
troll: Boolean,
hidden: Boolean,
lang: Option[String],
createdAt: DateTime) {
editHistory: List[OldVersion],
createdAt: DateTime,
updatedAt: DateTime) {
private val permitEditsFor = 3 hours
def id = _id
@ -28,6 +34,21 @@ case class Post(
def isTeam = categId startsWith teamSlug("")
def isStaff = categId == "staff"
def canStillBeEdited() = {
updatedAt.plus(permitEditsFor.toMillis).isAfter(DateTime.now)
}
def canBeEditedBy(editingId: String) : Boolean = userId.fold(false)(editingId == _)
def editPost(updated: DateTime, newText: String) : Post = {
val oldVersion = new OldVersion(text, updatedAt)
val history = oldVersion :: editHistory
this.copy(editHistory = history, text = newText, updatedAt = updated)
}
def hasEdits = editHistory.nonEmpty
}
object Post {
@ -44,17 +65,24 @@ object Post {
number: Int,
lang: Option[String],
troll: Boolean,
hidden: Boolean): Post = Post(
_id = Random nextStringUppercase idSize,
topicId = topicId,
author = author,
userId = userId,
ip = ip,
text = text,
number = number,
lang = lang,
troll = troll,
hidden = hidden,
createdAt = DateTime.now,
categId = categId)
hidden: Boolean): Post = {
val now = DateTime.now
Post(
_id = Random nextStringUppercase idSize,
topicId = topicId,
author = author,
userId = userId,
ip = ip,
text = text,
number = number,
lang = lang,
editHistory = List.empty,
troll = troll,
hidden = hidden,
createdAt = now,
updatedAt = now,
categId = categId)
}
}

View file

@ -3,14 +3,13 @@ package lila.forum
import actorApi._
import akka.actor.ActorSelection
import org.joda.time.DateTime
import lila.common.paginator._
import lila.db.dsl._
import lila.db.paginator._
import lila.hub.actorApi.timeline.{ Propagate, ForumPost }
import lila.hub.actorApi.timeline.{ForumPost, Propagate}
import lila.mod.ModlogApi
import lila.security.{ Granter => MasterGranter }
import lila.user.{ User, UserContext }
import lila.security.{Granter => MasterGranter}
import lila.user.{User, UserContext}
final class PostApi(
env: Env,
@ -69,6 +68,25 @@ final class PostApi(
}
}
def editPost(postId: String, newText: String, user: User) : Fu[Post] = {
get(postId) flatMap { post =>
val now = DateTime.now
post match {
case Some((_, post)) if !post.canBeEditedBy(user.id) =>
fufail("You are not authorized to modify this post.")
case Some((_, post)) if !post.canStillBeEdited() =>
fufail("Post can no longer be edited")
case Some((_,post)) =>
val spamEscapedTest = lila.security.Spam.replace(newText)
val newPost = post.editPost(now, spamEscapedTest)
env.postColl.update($id(post.id), newPost) inject newPost
case None => fufail("Post no longer exists.")
}
}
}
private val quickHideCategs = Set("lichess-feedback", "off-topic-discussion")
private def shouldHideOnPost(topic: Topic) =

View file

@ -0,0 +1,46 @@
// Map of old post contents by post number in case the user clicks 'edit' on multiple posts
// on the same page (although rare.)
var oldContents = {};
var editForumPost = function(postId, postNumber) {
var postSelector = $("#" + postNumber);
// We grab the text element of the post and turn it into a textarea to make it editable
oldContents[postNumber] = postSelector.find(".message");
var old = oldContents[postNumber];
var postContents = old.text();
var editForm = $('<form>', {
id: 'post-edit-form-' + postNumber,
method: 'POST',
action:"/forum/post/" + postId
});
var formTextArea = $('<textarea>', {
id:'post-edit-area-' + postNumber,
name:"changes",
class:'edit-post-box',
'minlength': 3
});
formTextArea.text(postContents);
var formSubmitButton = $('<input>', {type:'submit', value: 'Submit'});
var formCancelButton = $('<a>', {'data-icon':'s', onclick:'cancelEdit(' + postNumber + ')' }).text("Cancel");
editForm.append(formTextArea);
editForm.append(formSubmitButton);
editForm.append(formCancelButton);
old.replaceWith(editForm);
$("#edit-button-" + postNumber).hide();
};
var cancelEdit = function(postNumber) {
$('#post-edit-form-' + postNumber).replaceWith(oldContents[postNumber]);
$("#edit-button-" + postNumber).show();
}

View file

@ -183,6 +183,28 @@ div.post .message {
line-height: 1.5em;
}
div.post .edit-buttons {
float: right;
}
div.post .edit-post-box {
width: 100%;
height: 250px;
}
div.post a.post-edited {
font-size: 0.8em;
font-style: bold;
color: #aaa;
margin-top: 0.3em;
margin-left: 1em;
float: left;
}
div.post .button-hidden {
display: none;
}
div.topicReply,
div.category .description {
margin: 1em 24px 0 24px;