diff --git a/TODO b/TODO index 03f12696..b8da2db7 100644 --- a/TODO +++ b/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 diff --git a/public_html/css/comments.css b/public_html/css/comments.css index 41ac6de7..5a92e5e8 100644 --- a/public_html/css/comments.css +++ b/public_html/css/comments.css @@ -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: ']'; } diff --git a/public_html/js/Presenters/PostCommentListPresenter.js b/public_html/js/Presenters/PostCommentListPresenter.js index 1e6b7105..b9305447 100644 --- a/public_html/js/Presenters/PostCommentListPresenter.js +++ b/public_html/js/Presenters/PostCommentListPresenter.js @@ -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, }) + ''); 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) { - window.alert(response.json && response.json.error || 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 { diff --git a/public_html/templates/comment-list-item.tpl b/public_html/templates/comment-list-item.tpl index 86421f3d..14e45eb2 100644 --- a/public_html/templates/comment-list-item.tpl +++ b/public_html/templates/comment-list-item.tpl @@ -31,19 +31,32 @@ <%= formatRelativeTime(comment.creationTime) %> - - <% if (canEditComment) { %> - edit - <% } %> - - <% if (canDeleteComment) { %> - delete - <% } %> + + Score: <%= comment.score %> + + <% if (canVote) { %>vote upvote down<% } %><% if (canEditComment) { %>edit<% } %><% if (canDeleteComment) { %>delete<% } %>
diff --git a/src/Controllers/CommentController.php b/src/Controllers/CommentController.php index b35cb0e3..88581f67 100644 --- a/src/Controllers/CommentController.php +++ b/src/Controllers/CommentController.php @@ -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, + ]; + } } diff --git a/src/Controllers/ScoreController.php b/src/Controllers/ScoreController.php index dd1b2e79..ca6afc87 100644 --- a/src/Controllers/ScoreController.php +++ b/src/Controllers/ScoreController.php @@ -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(); diff --git a/src/Controllers/ViewProxies/CommentViewProxy.php b/src/Controllers/ViewProxies/CommentViewProxy.php index f905b4ac..a26a9a4d 100644 --- a/src/Controllers/ViewProxies/CommentViewProxy.php +++ b/src/Controllers/ViewProxies/CommentViewProxy.php @@ -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; } } - diff --git a/src/Dao/EntityConverters/CommentEntityConverter.php b/src/Dao/EntityConverters/CommentEntityConverter.php index c39363c2..3335a217 100644 --- a/src/Dao/EntityConverters/CommentEntityConverter.php +++ b/src/Dao/EntityConverters/CommentEntityConverter.php @@ -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; } } diff --git a/src/Dao/EntityConverters/ScoreEntityConverter.php b/src/Dao/EntityConverters/ScoreEntityConverter.php index 62e74676..ce7ec43b 100644 --- a/src/Dao/EntityConverters/ScoreEntityConverter.php +++ b/src/Dao/EntityConverters/ScoreEntityConverter.php @@ -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; diff --git a/src/Dao/ScoreDao.php b/src/Dao/ScoreDao.php index 44549506..5b43f596 100644 --- a/src/Dao/ScoreDao.php +++ b/src/Dao/ScoreDao.php @@ -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(); } diff --git a/src/Entities/Comment.php b/src/Entities/Comment.php index 4857e0fa..87654204 100644 --- a/src/Entities/Comment.php +++ b/src/Entities/Comment.php @@ -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); + } } diff --git a/src/Entities/Score.php b/src/Entities/Score.php index 459117bc..e9aa188d 100644 --- a/src/Entities/Score.php +++ b/src/Entities/Score.php @@ -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; diff --git a/src/Services/ScoreService.php b/src/Services/ScoreService.php index cc844274..f4361995 100644 --- a/src/Services/ScoreService.php +++ b/src/Services/ScoreService.php @@ -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); diff --git a/src/Upgrades/Upgrade16.php b/src/Upgrades/Upgrade16.php new file mode 100644 index 00000000..2304c833 --- /dev/null +++ b/src/Upgrades/Upgrade16.php @@ -0,0 +1,57 @@ +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'); + } +} + diff --git a/src/di.php b/src/di.php index 31869f61..0dcfddaa 100644 --- a/src/di.php +++ b/src/di.php @@ -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), ]; }),