+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Deck\Service;
+
+
+use OCA\Deck\AppInfo\Application;
+use OCA\Deck\BadRequestException;
+use OCA\Deck\Db\Acl;
+use OCA\Deck\Db\CardMapper;
+use OCA\Deck\NoPermissionException;
+use OCA\Deck\NotFoundException;
+use OCA\Deck\StatusException;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\Comments\IComment;
+use OCP\Comments\ICommentsManager;
+use OCP\Comments\MessageTooLongException;
+use OCP\Comments\NotFoundException as CommentNotFoundException;
+use OCP\ILogger;
+use OCP\IUserManager;
+use OutOfBoundsException;
+use Sabre\DAV\Exception\Forbidden;
+use function is_numeric;
+
+class CommentService {
+
+ /**
+ * @var ICommentsManager
+ */
+ private $commentsManager;
+ /**
+ * @var IUserManager
+ */
+ private $userManager;
+ /** @var ILogger */
+ private $logger;
+ private $userId;
+
+ public function __construct(ICommentsManager $commentsManager, PermissionService $permissionService, CardMapper $cardMapper, IUserManager $userManager, ILogger $logger, $userId) {
+ $this->commentsManager = $commentsManager;
+ $this->permissionService = $permissionService;
+ $this->cardMapper = $cardMapper;
+ $this->userManager = $userManager;
+ $this->logger = $logger;
+ $this->userId = $userId;
+ }
+
+ public function list(string $cardId, int $limit = 20, int $offset = 0): DataResponse {
+ if (!is_numeric($cardId)) {
+ throw new BadRequestException('A valid card id must be provided');
+ }
+ $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
+ $comments = $this->commentsManager->getForObject(Application::COMMENT_ENTITY_TYPE, $cardId, $limit, $offset);
+ $result = [];
+ foreach ($comments as $comment) {
+ $formattedComment = $this->formatComment($comment);
+ try {
+ if ($comment->getParentId() !== '0' && $replyTo = $this->commentsManager->get($comment->getParentId())) {
+ $formattedComment['replyTo'] = $this->formatComment($replyTo);
+ }
+ } catch (CommentNotFoundException $e) {
+ }
+ $result[] = $formattedComment;
+ }
+ return new DataResponse($result);
+ }
+
+ /**
+ * @param string $cardId
+ * @param string $message
+ * @param string $replyTo
+ * @return DataResponse
+ * @throws BadRequestException
+ * @throws NotFoundException
+ */
+ public function create(string $cardId, string $message, string $replyTo = '0'): DataResponse {
+ if (!is_numeric($cardId)) {
+ throw new BadRequestException('A valid card id must be provided');
+ }
+ $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
+ try {
+ $comment = $this->commentsManager->create('users', $this->userId, Application::COMMENT_ENTITY_TYPE, $cardId);
+ $comment->setMessage($message);
+ $comment->setVerb('comment');
+ $comment->setParentId($replyTo);
+ $this->commentsManager->save($comment);
+ return new DataResponse($this->formatComment($comment));
+ } catch (\InvalidArgumentException $e) {
+ throw new BadRequestException('Invalid input values');
+ } catch (MessageTooLongException $e) {
+ $msg = 'Message exceeds allowed character limit of ';
+ throw new BadRequestException($msg . IComment::MAX_MESSAGE_LENGTH);
+ } catch (CommentNotFoundException $e) {
+ throw new NotFoundException('Could not create comment.');
+ }
+ }
+
+ public function update(string $cardId, string $commentId, string $message): DataResponse {
+ if (!is_numeric($cardId)) {
+ throw new BadRequestException('A valid card id must be provided');
+ }
+ if (!is_numeric($commentId)) {
+ throw new BadRequestException('A valid comment id must be provided');
+ }
+ $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
+ try {
+ $comment = $this->commentsManager->get($commentId);
+
+ } catch (CommentNotFoundException $e) {
+ throw new NotFoundException('No comment found.');
+ }
+ if ($comment->getActorType() !== 'users' || $comment->getActorId() !== $this->userId) {
+ throw new NoPermissionException('Only authors are allowed to edit their comment.');
+ }
+ $comment->setMessage($message);
+ $this->commentsManager->save($comment);
+ return new DataResponse($this->formatComment($comment));
+ }
+
+ public function delete(string $cardId, string $commentId): DataResponse {
+ if (!is_numeric($cardId)) {
+ throw new BadRequestException('A valid card id must be provided');
+ }
+ if (!is_numeric($commentId)) {
+ throw new BadRequestException('A valid comment id must be provided');
+ }
+ $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
+ try {
+ $comment = $this->commentsManager->get($commentId);
+
+ } catch (CommentNotFoundException $e) {
+ throw new NotFoundException('No comment found.');
+ }
+ if ($comment->getActorType() !== 'users' || $comment->getActorId() !== $this->userId) {
+ throw new NoPermissionException('Only authors are allowed to edit their comment.');
+ }
+ $this->commentsManager->delete($commentId);
+ return new DataResponse([]);
+ }
+
+ private function formatComment(IComment $comment): array {
+ $user = $this->userManager->get($comment->getActorId());
+ $actorDisplayName = $user !== null ? $user->getDisplayName() : $comment->getActorId();
+
+ return [
+ 'id' => $comment->getId(),
+ 'objectId' => $comment->getObjectId(),
+ 'message' => $comment->getMessage(),
+ 'actorId' => $comment->getActorId(),
+ 'actorType' => $comment->getActorType(),
+ 'actorDisplayName' => $actorDisplayName,
+ 'creationDateTime' => $comment->getCreationDateTime()->format(\DateTime::ATOM),
+ 'mentions' => array_map(function($mention) {
+ try {
+ $displayName = $this->commentsManager->resolveDisplayName($mention['type'], $mention['id']);
+ } catch (OutOfBoundsException $e) {
+ $this->logger->logException($e);
+ // No displayname, upon client's discretion what to display.
+ $displayName = '';
+ }
+
+ return [
+ 'mentionId' => $mention['id'],
+ 'mentionType' => $mention['type'],
+ 'mentionDisplayName' => $displayName
+ ];
+ }, $comment->getMentions()),
+ ];
+ }
+
+}
diff --git a/package-lock.json b/package-lock.json
index 1025f2c0e..eeda19e46 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5663,6 +5663,11 @@
"integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==",
"dev": true
},
+ "blueimp-md5": {
+ "version": "2.12.0",
+ "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.12.0.tgz",
+ "integrity": "sha512-zo+HIdIhzojv6F1siQPqPFROyVy7C50KzHv/k/Iz+BtvtVzSHXiMXOpq2wCfNkeBqdCv+V8XOV96tsEt2W/3rQ=="
+ },
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
diff --git a/package.json b/package.json
index 030309e24..f6f2c22d8 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
"@nextcloud/moment": "^1.1.0",
"@nextcloud/router": "^1.0.0",
"@nextcloud/vue": "^1.4.0",
+ "blueimp-md5": "^2.12.0",
"dompurify": "^2.0.8",
"nextcloud-vue-collections": "^0.7.2",
"url-search-params-polyfill": "^8.0.0",
diff --git a/src/components/ActivityEntry.vue b/src/components/ActivityEntry.vue
index 6a5e299ca..2d65d1ece 100644
--- a/src/components/ActivityEntry.vue
+++ b/src/components/ActivityEntry.vue
@@ -26,9 +26,10 @@
- {{ getTime(activity.datetime) }}
+ {{ relativeDate(activity.datetime) }}
+
@@ -38,6 +39,7 @@ import RichText from '@juliushaertl/vue-richtext'
import { UserBubble } from '@nextcloud/vue'
import moment from '@nextcloud/moment'
import DOMPurify from 'dompurify'
+import relativeDate from '../mixins/relativeDate'
const InternalLink = {
name: 'InternalLink',
@@ -61,6 +63,7 @@ export default {
components: {
RichText,
},
+ mixins: [ relativeDate ],
props: {
activity: {
type: Object,
@@ -111,15 +114,7 @@ export default {
sanitizedMessage() {
return DOMPurify.sanitize(this.activity.message, { ALLOWED_TAGS: ['ins', 'del'], ALLOWED_ATTR: ['class'] })
},
- getTime() {
- return (timestamp) => {
- const diff = moment(this.$root.time).diff(moment(timestamp))
- if (diff >= 0 && diff < 45000) {
- return t('core', 'seconds ago')
- }
- return moment(timestamp).fromNow()
- }
- },
+
},
}
diff --git a/src/components/card/CardSidebarTabComments.vue b/src/components/card/CardSidebarTabComments.vue
index 4235e2c61..da438562b 100644
--- a/src/components/card/CardSidebarTabComments.vue
+++ b/src/components/card/CardSidebarTabComments.vue
@@ -7,6 +7,7 @@
+