Added comment scoring
This commit is contained in:
parent
28e87dca93
commit
6bf8586735
15 changed files with 209 additions and 36 deletions
1
TODO
1
TODO
|
@ -36,7 +36,6 @@ everything related to tags:
|
|||
|
||||
everything related to comments:
|
||||
- markdown
|
||||
- score
|
||||
|
||||
refactors:
|
||||
- add enum validation in IValidatables (needs refactors of enums and
|
||||
|
|
|
@ -39,19 +39,30 @@
|
|||
line-height: 16pt;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.comment .score,
|
||||
.comment .date {
|
||||
color: silver;
|
||||
font-size: 80%;
|
||||
font-size: 90%;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
.comment .score-up.active,
|
||||
.comment .score-down.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
.comment .header .ops a {
|
||||
color: silver;
|
||||
font-size: 80%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.comment .header .ops a:before {
|
||||
.comment .header .ops a:first-of-type:before {
|
||||
margin-left: 0.5em;
|
||||
content: '[';
|
||||
}
|
||||
.comment .header .ops a:after {
|
||||
.comment .header .ops a:not(:first-of-type):before {
|
||||
content: '|';
|
||||
margin: 0 0.3em;
|
||||
}
|
||||
.comment .header .ops a:last-of-type:after {
|
||||
content: ']';
|
||||
}
|
||||
|
||||
|
|
|
@ -97,18 +97,31 @@ App.Presenters.PostCommentListPresenter = function(
|
|||
comment: comment,
|
||||
formatRelativeTime: util.formatRelativeTime,
|
||||
formatMarkdown: util.formatMarkdown,
|
||||
canVote: auth.isLoggedIn(),
|
||||
canEditComment: auth.isLoggedIn(comment.user.name) ? privileges.editOwnComments : privileges.editAllComments,
|
||||
canDeleteComment: auth.isLoggedIn(comment.user.name) ? privileges.deleteOwnComments : privileges.deleteAllComments,
|
||||
}) + '</li>');
|
||||
util.loadImagesNicely($item.find('img'));
|
||||
$targetList.append($item);
|
||||
|
||||
$item.find('a.edit').click(function(e) {
|
||||
e.preventDefault();
|
||||
editCommentStart($item, comment);
|
||||
});
|
||||
|
||||
$item.find('a.delete').click(function(e) {
|
||||
e.preventDefault();
|
||||
deleteComment($item, comment);
|
||||
deleteComment(comment);
|
||||
});
|
||||
|
||||
$item.find('a.score-up').click(function(e) {
|
||||
e.preventDefault();
|
||||
score(comment, jQuery(this).hasClass('active') ? 0 : 1);
|
||||
});
|
||||
|
||||
$item.find('a.score-down').click(function(e) {
|
||||
e.preventDefault();
|
||||
score(comment, jQuery(this).hasClass('active') ? 0 : -1);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -132,6 +145,16 @@ App.Presenters.PostCommentListPresenter = function(
|
|||
});
|
||||
}
|
||||
|
||||
function updateComment(comment) {
|
||||
comments = _.map(comments, function(c) { return c.id === comment.id ? comment : c; });
|
||||
render();
|
||||
}
|
||||
|
||||
function addComment(comment) {
|
||||
comments.push(comment);
|
||||
render();
|
||||
}
|
||||
|
||||
function submitComment($form, commentToEdit) {
|
||||
$form.find('.preview').slideUp();
|
||||
var $textarea = $form.find('textarea');
|
||||
|
@ -152,14 +175,11 @@ App.Presenters.PostCommentListPresenter = function(
|
|||
$form.slideUp(function() {
|
||||
$form.remove();
|
||||
});
|
||||
comments = _.map(comments, function(c) { return c.id === commentToEdit.id ? comment : c; });
|
||||
updateComment(comment);
|
||||
} else {
|
||||
comments.push(comment);
|
||||
addComment(comment);
|
||||
}
|
||||
render();
|
||||
}).fail(function(response) {
|
||||
window.alert(response.json && response.json.error || response);
|
||||
});
|
||||
}).fail(showGenericError);
|
||||
}
|
||||
|
||||
function editCommentStart($item, comment) {
|
||||
|
@ -171,7 +191,7 @@ App.Presenters.PostCommentListPresenter = function(
|
|||
$item.find('form button[type=submit]').click(function(e) { commentFormSubmitted(e, comment); });
|
||||
}
|
||||
|
||||
function deleteComment($item, comment) {
|
||||
function deleteComment(comment) {
|
||||
if (!window.confirm('Are you sure you want to delete this comment?')) {
|
||||
return;
|
||||
}
|
||||
|
@ -179,9 +199,20 @@ App.Presenters.PostCommentListPresenter = function(
|
|||
.then(function(response) {
|
||||
comments = _.filter(comments, function(c) { return c.id !== comment.id; });
|
||||
renderComments(comments, true);
|
||||
}).fail(function(response) {
|
||||
}).fail(showGenericError);
|
||||
}
|
||||
|
||||
function score(comment, scoreValue) {
|
||||
promise.wait(api.post('/comments/' + comment.id + '/score', {score: scoreValue}))
|
||||
.then(function(response) {
|
||||
comment.score = response.json.score;
|
||||
comment.ownScore = parseInt(response.json.score);
|
||||
updateComment(comment);
|
||||
}).fail(showGenericError);
|
||||
}
|
||||
|
||||
function showGenericError(response) {
|
||||
window.alert(response.json && response.json.error || response);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -31,19 +31,32 @@
|
|||
<%= formatRelativeTime(comment.creationTime) %>
|
||||
</span>
|
||||
|
||||
<span class="ops">
|
||||
<% if (canEditComment) { %>
|
||||
<a class="edit" href="#"><!--
|
||||
-->edit<!--
|
||||
--></a>
|
||||
<% } %>
|
||||
|
||||
<% if (canDeleteComment) { %>
|
||||
<a class="delete" href="#"><!--
|
||||
-->delete<!--
|
||||
--></a>
|
||||
<% } %>
|
||||
<span class="score">
|
||||
Score: <%= comment.score %>
|
||||
</span>
|
||||
|
||||
<span class="ops"><!--
|
||||
--><% if (canVote) { %><!--
|
||||
--><a class="score-up <% print(comment.ownScore === 1 ? 'active' : '') %>"><!--
|
||||
-->vote up<!--
|
||||
--></a><!--
|
||||
--><a class="score-down <% print(comment.ownScore === -1 ? 'active' : '') %>"><!--
|
||||
-->vote down<!--
|
||||
--></a><!--
|
||||
--><% } %><!--
|
||||
|
||||
--><% if (canEditComment) { %><!--
|
||||
--><a class="edit"><!--
|
||||
-->edit<!--
|
||||
--></a><!--
|
||||
--><% } %><!--
|
||||
|
||||
--><% if (canDeleteComment) { %><!--
|
||||
--><a class="delete"><!--
|
||||
-->delete<!--
|
||||
--></a><!--
|
||||
--><% } %><!--
|
||||
--></span>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
|
|
@ -63,7 +63,10 @@ class CommentController extends AbstractController
|
|||
{
|
||||
$data[] = [
|
||||
'post' => $this->postViewProxy->fromEntity($post),
|
||||
'comments' => $this->commentViewProxy->fromArray($this->commentService->getByPost($post))];
|
||||
'comments' => $this->commentViewProxy->fromArray(
|
||||
$this->commentService->getByPost($post),
|
||||
$this->getCommentsFetchConfig()),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
|
@ -88,7 +91,7 @@ class CommentController extends AbstractController
|
|||
$filter->addRequirement($requirement);
|
||||
|
||||
$result = $this->commentService->getFiltered($filter);
|
||||
$entities = $this->commentViewProxy->fromArray($result->getEntities());
|
||||
$entities = $this->commentViewProxy->fromArray($result->getEntities(), $this->getCommentsFetchConfig());
|
||||
return ['data' => $entities];
|
||||
}
|
||||
|
||||
|
@ -98,7 +101,7 @@ class CommentController extends AbstractController
|
|||
|
||||
$post = $this->postService->getByNameOrId($postNameOrId);
|
||||
$comment = $this->commentService->createComment($post, $this->inputReader->text);
|
||||
return $this->commentViewProxy->fromEntity($comment);
|
||||
return $this->commentViewProxy->fromEntity($comment, $this->getCommentsFetchConfig());
|
||||
}
|
||||
|
||||
public function editComment($commentId)
|
||||
|
@ -111,7 +114,7 @@ class CommentController extends AbstractController
|
|||
: \Szurubooru\Privilege::EDIT_ALL_COMMENTS);
|
||||
|
||||
$comment = $this->commentService->updateComment($comment, $this->inputReader->text);
|
||||
return $this->commentViewProxy->fromEntity($comment);
|
||||
return $this->commentViewProxy->fromEntity($comment, $this->getCommentsFetchConfig());
|
||||
}
|
||||
|
||||
public function deleteComment($commentId)
|
||||
|
@ -125,4 +128,12 @@ class CommentController extends AbstractController
|
|||
|
||||
return $this->commentService->deleteComment($comment);
|
||||
}
|
||||
|
||||
private function getCommentsFetchConfig()
|
||||
{
|
||||
return
|
||||
[
|
||||
\Szurubooru\Controllers\ViewProxies\CommentViewProxy::FETCH_OWN_SCORE => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ class ScoreController extends AbstractController
|
|||
private $privilegeService;
|
||||
private $authService;
|
||||
private $postService;
|
||||
private $commentService;
|
||||
private $scoreService;
|
||||
private $inputReader;
|
||||
|
||||
|
@ -13,12 +14,14 @@ class ScoreController extends AbstractController
|
|||
\Szurubooru\Services\PrivilegeService $privilegeService,
|
||||
\Szurubooru\Services\AuthService $authService,
|
||||
\Szurubooru\Services\PostService $postService,
|
||||
\Szurubooru\Services\CommentService $commentService,
|
||||
\Szurubooru\Services\ScoreService $scoreService,
|
||||
\Szurubooru\Helpers\InputReader $inputReader)
|
||||
{
|
||||
$this->privilegeService = $privilegeService;
|
||||
$this->authService = $authService;
|
||||
$this->postService = $postService;
|
||||
$this->commentService = $commentService;
|
||||
$this->scoreService = $scoreService;
|
||||
$this->inputReader = $inputReader;
|
||||
}
|
||||
|
@ -27,6 +30,8 @@ class ScoreController extends AbstractController
|
|||
{
|
||||
$router->get('/api/posts/:postNameOrId/score', [$this, 'getPostScore']);
|
||||
$router->post('/api/posts/:postNameOrId/score', [$this, 'setPostScore']);
|
||||
$router->get('/api/comments/:commentId/score', [$this, 'getCommentScore']);
|
||||
$router->post('/api/comments/:commentId/score', [$this, 'setCommentScore']);
|
||||
}
|
||||
|
||||
public function getPostScore($postNameOrId)
|
||||
|
@ -41,6 +46,18 @@ class ScoreController extends AbstractController
|
|||
return $this->setScore($post);
|
||||
}
|
||||
|
||||
public function getCommentScore($commentId)
|
||||
{
|
||||
$comment = $this->commentService->getById($commentId);
|
||||
return $this->getScore($comment);
|
||||
}
|
||||
|
||||
public function setCommentScore($commentId)
|
||||
{
|
||||
$comment = $this->commentService->getById($commentId);
|
||||
return $this->setScore($comment);
|
||||
}
|
||||
|
||||
private function setScore(\Szurubooru\Entities\Entity $entity)
|
||||
{
|
||||
$this->privilegeService->assertLoggedIn();
|
||||
|
|
|
@ -3,14 +3,19 @@ namespace Szurubooru\Controllers\ViewProxies;
|
|||
|
||||
class CommentViewProxy extends AbstractViewProxy
|
||||
{
|
||||
private $postViewProxy;
|
||||
private $authService;
|
||||
private $scoreService;
|
||||
private $userViewProxy;
|
||||
|
||||
const FETCH_OWN_SCORE = 'fetchOwnScore';
|
||||
|
||||
public function __construct(
|
||||
PostViewProxy $postViewProxy,
|
||||
\Szurubooru\Services\AuthService $authService,
|
||||
\Szurubooru\Services\ScoreService $scoreService,
|
||||
UserViewProxy $userViewProxy)
|
||||
{
|
||||
$this->postViewProxy = $postViewProxy;
|
||||
$this->authService = $authService;
|
||||
$this->scoreService = $scoreService;
|
||||
$this->userViewProxy = $userViewProxy;
|
||||
}
|
||||
|
||||
|
@ -25,8 +30,11 @@ class CommentViewProxy extends AbstractViewProxy
|
|||
$result->text = $comment->getText();
|
||||
$result->postId = $comment->getPostId();
|
||||
$result->user = $this->userViewProxy->fromEntity($comment->getUser());
|
||||
$result->score = $comment->getScore();
|
||||
|
||||
if (!empty($config[self::FETCH_OWN_SCORE]) and $this->authService->isLoggedIn())
|
||||
$result->ownScore = $this->scoreService->getScoreValue($this->authService->getLoggedInUser(), $comment);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ class CommentEntityConverter extends AbstractEntityConverter implements IEntityC
|
|||
$entity->setText($array['text']);
|
||||
$entity->setCreationTime($this->dbTimeToEntityTime($array['creationTime']));
|
||||
$entity->setLastEditTime($this->dbTimeToEntityTime($array['lastEditTime']));
|
||||
$entity->setMeta(\Szurubooru\Entities\Comment::META_SCORE, intval($array['score']));
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ class ScoreEntityConverter extends AbstractEntityConverter implements IEntityCon
|
|||
'id' => $entity->getId(),
|
||||
'userId' => $entity->getUserId(),
|
||||
'postId' => $entity->getPostId(),
|
||||
'commentId' => $entity->getCommentId(),
|
||||
'time' => $this->entityTimeToDbTime($entity->getTime()),
|
||||
'score' => $entity->getScore(),
|
||||
];
|
||||
|
@ -20,6 +21,7 @@ class ScoreEntityConverter extends AbstractEntityConverter implements IEntityCon
|
|||
$entity = new \Szurubooru\Entities\Score($array['id']);
|
||||
$entity->setUserId($array['userId']);
|
||||
$entity->setPostId($array['postId']);
|
||||
$entity->setCommentId($array['commentId']);
|
||||
$entity->setTime($this->dbTimeToEntityTime($array['time']));
|
||||
$entity->setScore(intval($array['score']));
|
||||
return $entity;
|
||||
|
|
|
@ -23,6 +23,8 @@ class ScoreDao extends AbstractDao implements ICrudDao
|
|||
|
||||
if ($entity instanceof \Szurubooru\Entities\Post)
|
||||
$query->where('postId', $entity->getId());
|
||||
elseif ($entity instanceof \Szurubooru\Entities\Comment)
|
||||
$query->where('commentId', $entity->getId());
|
||||
else
|
||||
throw new \InvalidArgumentException();
|
||||
|
||||
|
@ -42,6 +44,8 @@ class ScoreDao extends AbstractDao implements ICrudDao
|
|||
|
||||
if ($entity instanceof \Szurubooru\Entities\Post)
|
||||
$score->setPostId($entity->getId());
|
||||
elseif ($entity instanceof \Szurubooru\Entities\Comment)
|
||||
$score->setCommentId($entity->getId());
|
||||
else
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ class Comment extends Entity
|
|||
const LAZY_LOADER_USER = 'user';
|
||||
const LAZY_LOADER_POST = 'post';
|
||||
|
||||
const META_SCORE = 'score';
|
||||
|
||||
public function getUserId()
|
||||
{
|
||||
return $this->userId;
|
||||
|
@ -83,4 +85,9 @@ class Comment extends Entity
|
|||
$this->lazySave(self::LAZY_LOADER_POST, $post);
|
||||
$this->postId = $post->getId();
|
||||
}
|
||||
|
||||
public function getScore()
|
||||
{
|
||||
return $this->getMeta(self::META_SCORE, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ class Score extends Entity
|
|||
{
|
||||
private $postId;
|
||||
private $userId;
|
||||
private $commentId;
|
||||
private $time;
|
||||
private $score;
|
||||
|
||||
|
@ -28,6 +29,16 @@ class Score extends Entity
|
|||
$this->postId = $postId;
|
||||
}
|
||||
|
||||
public function getCommentId()
|
||||
{
|
||||
return $this->commentId;
|
||||
}
|
||||
|
||||
public function setCommentId($commentId)
|
||||
{
|
||||
$this->commentId = $commentId;
|
||||
}
|
||||
|
||||
public function getTime()
|
||||
{
|
||||
return $this->time;
|
||||
|
|
|
@ -47,7 +47,7 @@ class ScoreService
|
|||
|
||||
$transactionFunc = function() use ($user, $entity, $scoreValue)
|
||||
{
|
||||
if ($scoreValue !== 1)
|
||||
if (($scoreValue !== 1) and ($entity instanceof \Szurubooru\Entities\Post))
|
||||
$this->favoritesDao->delete($user, $entity);
|
||||
|
||||
return $this->scoreDao->setScore($user, $entity, $scoreValue);
|
||||
|
|
57
src/Upgrades/Upgrade16.php
Normal file
57
src/Upgrades/Upgrade16.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
namespace Szurubooru\Upgrades;
|
||||
|
||||
class Upgrade16 implements IUpgrade
|
||||
{
|
||||
public function run(\Szurubooru\DatabaseConnection $databaseConnection)
|
||||
{
|
||||
$pdo = $databaseConnection->getPDO();
|
||||
|
||||
$pdo->exec('ALTER TABLE scores ADD COLUMN commentId INTEGER');
|
||||
$pdo->exec('ALTER TABLE comments ADD COLUMN score INTEGER NOT NULL DEFAULT 0');
|
||||
|
||||
$pdo->exec('DROP TRIGGER IF EXISTS scoresDelete');
|
||||
$pdo->exec('DROP TRIGGER IF EXISTS scoresInsert');
|
||||
$pdo->exec('DROP TRIGGER IF EXISTS scoresUpdate');
|
||||
|
||||
$pdo->exec('
|
||||
CREATE TRIGGER scoresDelete AFTER DELETE ON scores
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE posts SET
|
||||
score = (SELECT SUM(score) FROM scores WHERE scores.postId = posts.id)
|
||||
WHERE posts.id = OLD.postId;
|
||||
|
||||
UPDATE comments SET
|
||||
score = (SELECT SUM(score) FROM scores WHERE scores.commentId = comments.id)
|
||||
WHERE comments.id = OLD.commentId;
|
||||
END');
|
||||
|
||||
$pdo->exec('
|
||||
CREATE TRIGGER scoresInsert AFTER INSERT ON scores
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE posts SET
|
||||
score = (SELECT SUM(score) FROM scores WHERE scores.postId = posts.id)
|
||||
WHERE posts.id = NEW.postId;
|
||||
|
||||
UPDATE comments SET
|
||||
score = (SELECT SUM(score) FROM scores WHERE scores.commentId = comments.id)
|
||||
WHERE comments.id = NEW.commentId;
|
||||
END');
|
||||
|
||||
$pdo->exec('
|
||||
CREATE TRIGGER scoresUpdate AFTER UPDATE ON scores
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE posts SET
|
||||
score = (SELECT SUM(score) FROM scores WHERE scores.postId = posts.id)
|
||||
WHERE posts.id IN (OLD.postId, NEW.postId);
|
||||
|
||||
UPDATE comments SET
|
||||
score = (SELECT SUM(score) FROM scores WHERE scores.commentId = comments.id)
|
||||
WHERE comments.id IN (OLD.commentId, NEW.commentId);
|
||||
END');
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ return [
|
|||
$container->get(\Szurubooru\Upgrades\Upgrade13::class),
|
||||
$container->get(\Szurubooru\Upgrades\Upgrade14::class),
|
||||
$container->get(\Szurubooru\Upgrades\Upgrade15::class),
|
||||
$container->get(\Szurubooru\Upgrades\Upgrade16::class),
|
||||
];
|
||||
}),
|
||||
|
||||
|
|
Loading…
Reference in a new issue