Compare commits

..

10 Commits

Author SHA1 Message Date
Jakob Röhrl
65650691b3 test
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2020-10-16 12:52:47 +02:00
Jakob Röhrl
caa201dc6c faq
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2020-10-16 11:42:54 +02:00
Jakob
083cd207af Merge branch 'master' into enh/cloneStack 2020-09-16 10:42:07 +02:00
Jakob Röhrl
24f83b875b test adjustments
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2020-09-16 10:09:58 +02:00
Jakob Röhrl
14d90e2ff2 format json
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2020-09-07 14:11:35 +02:00
Jakob Röhrl
2a4b0a3ed3 security and api
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2020-09-02 08:40:19 +02:00
Jakob Röhrl
71780b5578 some small changes
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2020-09-02 08:09:15 +02:00
Julius Härtl
c39fd43b6c Fix label assignment insertion and enrich result of clone
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2020-09-01 08:58:30 +02:00
Jakob Röhrl
e763ce1fb7 assign label
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2020-09-01 08:58:30 +02:00
Jakob Röhrl
003df010dd clone stacks
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2020-09-01 08:58:29 +02:00
26 changed files with 398 additions and 44 deletions

View File

@@ -17,7 +17,7 @@
- 🚀 Get your project organized
</description>
<version>1.1.0-beta2</version>
<version>1.1.0-beta1</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Deck</namespace>

View File

@@ -47,6 +47,7 @@ return [
['name' => 'stack#delete', 'url' => '/stacks/{stackId}', 'verb' => 'DELETE'],
['name' => 'stack#deleted', 'url' => '/{boardId}/stacks/deleted', 'verb' => 'GET'],
['name' => 'stack#archived', 'url' => '/stacks/{boardId}/archived', 'verb' => 'GET'],
['name' => 'stack#clone', 'url' => '/stacks/{stackId}/clone', 'verb' => 'POST'],
// cards
['name' => 'card#read', 'url' => '/cards/{cardId}', 'verb' => 'GET'],
@@ -97,6 +98,7 @@ return [
['name' => 'stack_api#create', 'url' => '/api/v1.0/boards/{boardId}/stacks', 'verb' => 'POST'],
['name' => 'stack_api#update', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}', 'verb' => 'PUT'],
['name' => 'stack_api#delete', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}', 'verb' => 'DELETE'],
['name' => 'stack_api#clone', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/clone', 'verb' => 'POST'],
['name' => 'card_api#get', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'GET'],
['name' => 'card_api#create', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards', 'verb' => 'POST'],

View File

@@ -492,6 +492,93 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit
##### 200 Success
### POST /boards/{boardId}/stacks/{stackId}/clone - Clone a stack
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ------------------------------------------------------- |
| boardId | Integer | The id of the board where the stack should be cloned to |
| stackId | Integer | The id of the stack |
#### Response
It will return an object of the new stack containing the new cards as well.
```json
{
"title":"l1 (copy)",
"boardId":6,
"deletedAt":0,
"lastModified":0,
"cards":[
{
"title":"ME",
"description":"123",
"stackId":73,
"type":"plain",
"lastModified":1599028559,
"lastEditor":null,
"createdAt":1599028559,
"labels":[
],
"assignedUsers":[
],
"attachments":null,
"attachmentCount":0,
"owner":{
"primaryKey":"root",
"uid":"root",
"displayname":"root",
"type":0
},
"order":0,
"archived":false,
"duedate":null,
"deletedAt":0,
"commentsUnread":0,
"id":109,
"overdue":0
},
{
"title":"ka",
"description":"",
"stackId":73,
"type":"plain",
"lastModified":1599028559,
"lastEditor":null,
"createdAt":1599028559,
"labels":[
],
"assignedUsers":[
],
"attachments":null,
"attachmentCount":0,
"owner":{
"primaryKey":"root",
"uid":"root",
"displayname":"root",
"type":0
},
"order":1,
"archived":false,
"duedate":"2020-08-26T22:00:00+00:00",
"deletedAt":0,
"commentsUnread":0,
"id":110,
"overdue":3
}
],
"order":999,
"id":73
}
```
##### 200 Success
## Cards
### GET /boards/{boardId}/stacks/{stackId}/cards/{cardId} - Get card details

View File

@@ -214,7 +214,6 @@ OC.L10N.register(
"Archived boards" : "Archivované tabule",
"Shared with you" : "Sdíleno s vámi",
"Use modal card view" : "Použít modální zobrazení karty",
"Show boards in calendar/tasks" : "Zobrazit tabule v kalendáři/úkolech",
"Limit deck usage of groups" : "Omezit využití deck na skupiny",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Omezení nastavené pro Deck brání uživatelům, kteří nejsou součástí těchto skupin, ve vytváření vlastních tabulí. Nicméně i tak ale pořád budou moci pracovat na tabulích, které jsou jim nasdíleny.",
"New board title" : "Název nové tabule",

View File

@@ -212,7 +212,6 @@
"Archived boards" : "Archivované tabule",
"Shared with you" : "Sdíleno s vámi",
"Use modal card view" : "Použít modální zobrazení karty",
"Show boards in calendar/tasks" : "Zobrazit tabule v kalendáři/úkolech",
"Limit deck usage of groups" : "Omezit využití deck na skupiny",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Omezení nastavené pro Deck brání uživatelům, kteří nejsou součástí těchto skupin, ve vytváření vlastních tabulí. Nicméně i tak ale pořád budou moci pracovat na tabulích, které jsou jim nasdíleny.",
"New board title" : "Název nové tabule",

View File

@@ -214,7 +214,6 @@ OC.L10N.register(
"Archived boards" : "Archivierte Boards",
"Shared with you" : "Mit Dir geteilt",
"Use modal card view" : "Modale Kartenansicht verwenden",
"Show boards in calendar/tasks" : "Board im Kalender/Aufgaben anzeigen",
"Limit deck usage of groups" : "Nutzung von Deck auf Gruppen einschränken",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Durch die Begrenzung von Deck werden Benutzer, die nicht Teil dieser Gruppen sind, daran gehindert, eigene Boards zu erstellen. Benutzer können weiterhin an Boards arbeiten, die für sie freigegeben wurden.",
"New board title" : "Board-Titel",

View File

@@ -212,7 +212,6 @@
"Archived boards" : "Archivierte Boards",
"Shared with you" : "Mit Dir geteilt",
"Use modal card view" : "Modale Kartenansicht verwenden",
"Show boards in calendar/tasks" : "Board im Kalender/Aufgaben anzeigen",
"Limit deck usage of groups" : "Nutzung von Deck auf Gruppen einschränken",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Durch die Begrenzung von Deck werden Benutzer, die nicht Teil dieser Gruppen sind, daran gehindert, eigene Boards zu erstellen. Benutzer können weiterhin an Boards arbeiten, die für sie freigegeben wurden.",
"New board title" : "Board-Titel",

View File

@@ -214,7 +214,6 @@ OC.L10N.register(
"Archived boards" : "Archivierte Boards",
"Shared with you" : "Mit Ihnen geteilt",
"Use modal card view" : "Modale Kartenansicht verwenden",
"Show boards in calendar/tasks" : "Board im Kalender/Aufgaben anzeigen",
"Limit deck usage of groups" : "Nutzung von Deck auf Gruppen einschränken",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Durch die Begrenzung von Deck werden Benutzer, die nicht Teil dieser Gruppen sind, daran gehindert, eigene Boards zu erstellen. Benutzer können weiterhin an Boards arbeiten, die für sie freigegeben wurden.",
"New board title" : "Board-Titel",

View File

@@ -212,7 +212,6 @@
"Archived boards" : "Archivierte Boards",
"Shared with you" : "Mit Ihnen geteilt",
"Use modal card view" : "Modale Kartenansicht verwenden",
"Show boards in calendar/tasks" : "Board im Kalender/Aufgaben anzeigen",
"Limit deck usage of groups" : "Nutzung von Deck auf Gruppen einschränken",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Durch die Begrenzung von Deck werden Benutzer, die nicht Teil dieser Gruppen sind, daran gehindert, eigene Boards zu erstellen. Benutzer können weiterhin an Boards arbeiten, die für sie freigegeben wurden.",
"New board title" : "Board-Titel",

View File

@@ -214,7 +214,6 @@ OC.L10N.register(
"Archived boards" : "Taboleiros arquivados",
"Shared with you" : "Compartido con vostede",
"Use modal card view" : "Usar a vista de tarxeta modal",
"Show boards in calendar/tasks" : "Amosar taboleiros no calendario/tarefas",
"Limit deck usage of groups" : "Limitar o uso da plataforma a grupos",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Limitando Deck bloqueará os usuarios que non formen parte destes grupos, para crear os seus propios taboleiros. Os usuarios aínda así poderán traballar en taboleiros compartidos con eles.",
"New board title" : "Novo título do taboleiro",

View File

@@ -212,7 +212,6 @@
"Archived boards" : "Taboleiros arquivados",
"Shared with you" : "Compartido con vostede",
"Use modal card view" : "Usar a vista de tarxeta modal",
"Show boards in calendar/tasks" : "Amosar taboleiros no calendario/tarefas",
"Limit deck usage of groups" : "Limitar o uso da plataforma a grupos",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Limitando Deck bloqueará os usuarios que non formen parte destes grupos, para crear os seus propios taboleiros. Os usuarios aínda así poderán traballar en taboleiros compartidos con eles.",
"New board title" : "Novo título do taboleiro",

View File

@@ -214,7 +214,6 @@ OC.L10N.register(
"Archived boards" : "Lavagne archiviate",
"Shared with you" : "Condiviso con te",
"Use modal card view" : "Usa la vista modale delle schede",
"Show boards in calendar/tasks" : "Mostra le lavagne in calendario/attività",
"Limit deck usage of groups" : "Limita utilizzo di Deck dei gruppi",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "La limitazione di Deck impedirà agli utenti che non fanno parte di tali gruppi di creare le proprie lavagne. Gli utenti saranno ancora in grado di lavorare sulle lavagne che sono state condivise con loro,",
"New board title" : "Titolo nuova lavagna",

View File

@@ -212,7 +212,6 @@
"Archived boards" : "Lavagne archiviate",
"Shared with you" : "Condiviso con te",
"Use modal card view" : "Usa la vista modale delle schede",
"Show boards in calendar/tasks" : "Mostra le lavagne in calendario/attività",
"Limit deck usage of groups" : "Limita utilizzo di Deck dei gruppi",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "La limitazione di Deck impedirà agli utenti che non fanno parte di tali gruppi di creare le proprie lavagne. Gli utenti saranno ancora in grado di lavorare sulle lavagne che sono state condivise con loro,",
"New board title" : "Titolo nuova lavagna",

View File

@@ -214,7 +214,6 @@ OC.L10N.register(
"Archived boards" : "Zarchiwizowane tablice",
"Shared with you" : "Dzielone z Tobą",
"Use modal card view" : "Użyj widoku karty modalnej",
"Show boards in calendar/tasks" : "Pokaż tablice w kalendarzu/zadaniach",
"Limit deck usage of groups" : "Ogranicz użycie tablic dla grup",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Ograniczenie Deck zablokuje użytkownikom z tych grup możliwość tworzenia własnych tablic. Użytkownicy nadal będą mogli pracować na tablicach, które zostały im udostępnione.",
"New board title" : "Tytuł nowej tablicy",

View File

@@ -212,7 +212,6 @@
"Archived boards" : "Zarchiwizowane tablice",
"Shared with you" : "Dzielone z Tobą",
"Use modal card view" : "Użyj widoku karty modalnej",
"Show boards in calendar/tasks" : "Pokaż tablice w kalendarzu/zadaniach",
"Limit deck usage of groups" : "Ogranicz użycie tablic dla grup",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Ograniczenie Deck zablokuje użytkownikom z tych grup możliwość tworzenia własnych tablic. Użytkownicy nadal będą mogli pracować na tablicach, które zostały im udostępnione.",
"New board title" : "Tytuł nowej tablicy",

View File

@@ -107,4 +107,13 @@ class StackController extends Controller {
public function deleted($boardId) {
return $this->stackService->fetchDeleted($boardId);
}
/**
* @NoAdminRequired
* @param $boardId
* @return \OCP\Deck\DB\Board
*/
public function clone($stackId, $boardId) {
return $this->stackService->clone($stackId, $boardId, $this->userId);
}
}

38
lib/Db/AssignedLabels.php Normal file
View File

@@ -0,0 +1,38 @@
<?php
/**
* @copyright Copyright (c) 2020 Jakob Röhrl <jakob.roehrl@web.de>
*
* @author Jakob Röhrl <jakob.roehrl@web.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Deck\Db;
use JsonSerializable;
class AssignedLabels extends RelationalEntity implements JsonSerializable {
public $id;
protected $labelId;
protected $cardId;
public function __construct() {
$this->addType('id', 'integer');
$this->addType('cardId', 'integer');
$this->addType('labelId', 'integer');
}
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* @copyright Copyright (c) 2020 Jakob Röhrl <jakob.roehrl@web.de>
*
* @author Jakob Röhrl <jakob.roehrl@web.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Deck\Db;
use OCP\AppFramework\Db\Entity;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUserManager;
class AssignedLabelsMapper extends DeckMapper {
private $cardMapper;
private $userManager;
/**
* @var IGroupManager
*/
private $groupManager;
public function __construct(IDBConnection $db, CardMapper $cardMapper, IUserManager $userManager, IGroupManager $groupManager) {
parent::__construct($db, 'deck_assigned_labels', AssignedLabels::class);
$this->cardMapper = $cardMapper;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
}
/**
*
* @param $cardId
* @return array|Entity
*/
public function find($cardId) {
$sql = 'SELECT * from `*PREFIX*deck_assigned_labels` where `card_id` = ?';
$labels = $this->findEntities($sql, [$cardId]);
return $labels;
}
}

View File

@@ -29,7 +29,11 @@ use OCA\Deck\Activity\ChangeSet;
use OCA\Deck\BadRequestException;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AssignedUsersMapper;
use OCA\Deck\Db\AssignedUsers;
use OCA\Deck\Db\AssignedLabelsMapper;
use OCA\Deck\Db\AssignedLabels;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\ChangeHelper;
use OCA\Deck\Db\LabelMapper;
@@ -38,6 +42,8 @@ use OCA\Deck\Db\StackMapper;
use OCA\Deck\StatusException;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use OCP\IL10N;
use OCA\Deck\Event\FTSEvent;
class StackService {
private $stackMapper;
@@ -48,11 +54,15 @@ class StackService {
private $boardService;
private $cardService;
private $assignedUsersMapper;
private $assignedLabelsMapper;
private $attachmentService;
private $activityManager;
/** @var EventDispatcherInterface */
private $eventDispatcher;
private $changeHelper;
private $l10n;
private $userId;
public function __construct(
StackMapper $stackMapper,
@@ -63,10 +73,13 @@ class StackService {
BoardService $boardService,
CardService $cardService,
AssignedUsersMapper $assignedUsersMapper,
AssignedLabelsMapper $assignedLabelsMapper,
AttachmentService $attachmentService,
ActivityManager $activityManager,
EventDispatcherInterface $eventDispatcher,
ChangeHelper $changeHelper
ChangeHelper $changeHelper,
IL10N $l10n,
$userId
) {
$this->stackMapper = $stackMapper;
$this->boardMapper = $boardMapper;
@@ -76,10 +89,13 @@ class StackService {
$this->boardService = $boardService;
$this->cardService = $cardService;
$this->assignedUsersMapper = $assignedUsersMapper;
$this->assignedLabelsMapper = $assignedLabelsMapper;
$this->attachmentService = $attachmentService;
$this->activityManager = $activityManager;
$this->eventDispatcher = $eventDispatcher;
$this->changeHelper = $changeHelper;
$this->l10n = $l10n;
$this->userId = $userId;
}
private function enrichStackWithCards($stack, $since = -1) {
@@ -365,4 +381,106 @@ class StackService {
return $result;
}
/**
* @param $id
* @param $boardId
* @return Stack
* @throws StatusException
* @throws BadRequestException
*/
public function clone($id, $boardId) {
if (is_numeric($id) === false) {
throw new BadRequestException('stack id must be a number');
}
if (is_numeric($boardId) === false) {
throw new BadRequestException('board id must be a number');
}
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_MANAGE);
$this->permissionService->checkPermission(null, $id, Acl::PERMISSION_READ);
if ($this->boardService->isArchived(null, $boardId)) {
throw new StatusException('Operation not allowed. This board is archived.');
}
$stack = $this->stackMapper->find($id);
$board = $this->boardMapper->find($boardId);
if ($stack->getBoardId() !== $board->getId()) {
throw new StatusException('Operation not allowed. Stack is not part of this board');
}
$newStack = new Stack();
// TODO: Currently cloing is only possible on the same board.
// If we change this and its possible to clone to other boards the 'copy' should be removed from title
$newStack->setTitle($stack->getTitle() . ' (' . $this->l10n->t('copy') . ')');
$newStack->setBoardId($boardId);
$newStack->setOrder(999);
$newStack = $this->stackMapper->insert($newStack);
$this->activityManager->triggerEvent(
ActivityManager::DECK_OBJECT_BOARD, $newStack, ActivityManager::SUBJECT_STACK_CREATE
);
$this->changeHelper->boardChanged($boardId);
$this->eventDispatcher->dispatch(
'\OCA\Deck\Stack::onCreate',
new GenericEvent(null, ['id' => $newStack->getId(), 'stack' => $newStack])
);
$cards = $this->cardMapper->findAll($id);
foreach ($cards as $card) {
$newCard = new Card();
$newCard->setTitle($card->getTitle());
$newCard->setStackId($newStack->getId());
$newCard->setType($card->getType());
$newCard->setOrder($card->getOrder());
$newCard->setOwner($this->userId);
$newCard->setDescription($card->getDescription());
$newCard->setDuedate($card->getDuedate());
$newCard = $this->cardMapper->insert($newCard);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_CREATE);
$this->changeHelper->cardChanged($newCard->getId(), false);
$this->eventDispatcher->dispatch('\OCA\Deck\Card::onCreate',
new FTSEvent(
null, ['id' => $newCard->getId(), 'card' => $newCard, 'userId' => $this->userId, 'stackId' => $stackId]
)
);
if ($boardId === $stack->getBoardId()) {
$assignedLabels = $this->assignedLabelsMapper->find($card->getId());
$newLabelArray = [];
foreach ($assignedLabels as $assignedLabel) {
$assignment = new AssignedLabels();
$assignment->setCardId($newCard->getId());
$assignment->setLabelId($assignedLabel->getLabelId());
$assignment = $this->assignedLabelsMapper->insert($assignment);
$newLabelArray[] = $assignment;
}
$newCard->setLabels($newLabelArray);
$assignedUsers = $this->assignedUsersMapper->find($card->getId());
$newUserArray = [];
foreach ($assignedUsers as $assignedUser) {
$assignment = new AssignedUsers();
$assignment->setCardId($newCard->getId());
$assignment->setParticipant($assignedUser->getParticipant());
$assignment->setType($assignedUser->getType());
$assignment = $this->assignedUsersMapper->insert($assignment);
$newUserArray[] = $assignment;
}
$newCard->setAssignedUsers($newUserArray);
}
}
$this->enrichStackWithCards($newStack);
return $newStack;
}
}

36
package-lock.json generated
View File

@@ -3671,9 +3671,9 @@
}
},
"@nextcloud/vue-dashboard": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@nextcloud/vue-dashboard/-/vue-dashboard-1.0.1.tgz",
"integrity": "sha512-QKOf0qm4UYobuC3djLYwICSuj29H6xnm24IHetc0AMsrfhwPNN1/CC5IWEl1uKCCvwI0NA02HXCVFLtUErlgyg==",
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/@nextcloud/vue-dashboard/-/vue-dashboard-0.1.8.tgz",
"integrity": "sha512-OGr1oK/WF9+bYHK8dE8Vjwh3IDNamN+9MSti1VO7zuUSm5A9EGCzAghR7zzCG4O43rAJEDcvnQwsYIiA6g4Yrw==",
"requires": {
"@nextcloud/vue": "^2.3.0",
"core-js": "^3.6.4",
@@ -13160,9 +13160,9 @@
"dev": true
},
"markdown-it": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-11.0.1.tgz",
"integrity": "sha512-aU1TzmBKcWNNYvH9pjq6u92BML+Hz3h5S/QpfTFwiQF852pLT+9qHsrhM9JYipkOXZxGn+sGH8oyJE9FD9WezQ==",
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-11.0.0.tgz",
"integrity": "sha512-+CvOnmbSubmQFSA9dKz1BRiaSMV7rhexl3sngKqFyXSagoA3fBdJQ8oZWtRy2knXdpDXaBw44euz37DeJQ9asg==",
"requires": {
"argparse": "^1.0.7",
"entities": "~2.0.0",
@@ -13172,9 +13172,9 @@
},
"dependencies": {
"entities": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
"integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ=="
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.2.tgz",
"integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw=="
}
}
},
@@ -13584,9 +13584,9 @@
}
},
"moment": {
"version": "2.28.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.28.0.tgz",
"integrity": "sha512-Z5KOjYmnHyd/ukynmFd/WwyXHd7L4J9vTI/nn5Ap9AVUgaAE15VvQ9MOGmJJygEUklupqIrFnor/tjTwRU+tQw=="
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
},
"move-concurrently": {
"version": "1.0.1",
@@ -19335,9 +19335,9 @@
"integrity": "sha512-xhq95Mxun060bRnsOoLE2Be6BR7jYwuC89kDe18+GmCLVrRA/dU0jrGb12Xu6NjmKs+iTW0AA6saSEmEW4cR7g=="
},
"vue-jest": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/vue-jest/-/vue-jest-3.0.7.tgz",
"integrity": "sha512-PIOxFM+wsBMry26ZpfBvUQ/DGH2hvp5khDQ1n51g3bN0TwFwTy4J85XVfxTRMukqHji/GnAoGUnlZ5Ao73K62w==",
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/vue-jest/-/vue-jest-3.0.6.tgz",
"integrity": "sha512-VyuM8wR0vAlYCbPRY+PhIqRU5yUyBnUmwYTo4IFScs2+tiuis5VBItU0PGC8Wcx6qJwKB5jq5p7WFhabzMFMgQ==",
"dev": true,
"requires": {
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
@@ -20170,9 +20170,9 @@
}
},
"webpack-merge": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.1.4.tgz",
"integrity": "sha512-LSmRD59mxREGkCBm9PCW3AaV4doDqxykGlx1NvioEE0FgkT2GQI54Wyvg39ptkiq2T11eRVoV39udNPsQvK+QQ==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.1.2.tgz",
"integrity": "sha512-/slG0Kh0OKTf0zxdFJlhQHzv8bU9gUYVK5DkBjB3i/yoc1Xx4ADG0KITGO5S/6cqn2Ug43+8VR6Sz8daA/c+5g==",
"dev": true,
"requires": {
"clone-deep": "^4.0.1",

View File

@@ -40,13 +40,13 @@
"@nextcloud/moment": "^1.1.1",
"@nextcloud/router": "^1.2.0",
"@nextcloud/vue": "^2.6.5",
"@nextcloud/vue-dashboard": "^1.0.1",
"@nextcloud/vue-dashboard": "^0.1.8",
"blueimp-md5": "^2.18.0",
"dompurify": "^2.0.15",
"lodash": "^4.17.20",
"markdown-it": "^11.0.1",
"markdown-it": "^11.0.0",
"markdown-it-task-lists": "^2.1.1",
"moment": "^2.28.0",
"moment": "^2.27.0",
"nextcloud-vue-collections": "^0.8.1",
"p-queue": "^6.6.1",
"url-search-params-polyfill": "^8.1.0",
@@ -103,13 +103,13 @@
"stylelint-scss": "^3.18.0",
"stylelint-webpack-plugin": "^2.1.0",
"url-loader": "^4.1.0",
"vue-jest": "^3.0.7",
"vue-jest": "^3.0.6",
"vue-loader": "^15.9.3",
"vue-template-compiler": "^2.6.12",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^5.1.4"
"webpack-merge": "^5.1.2"
},
"jest": {
"moduleFileExtensions": [

View File

@@ -42,11 +42,14 @@
value="">
</form>
</transition>
<Actions v-if="canManage && !isArchived" :force-menu="true">
<ActionButton icon="icon-archive" @click="modalArchivAllCardsShow=true">
<Actions v-if="!isArchived" :force-menu="true">
<ActionButton v-if="canEdit" icon="icon-archive" @click="modalArchivAllCardsShow=true">
{{ t('deck', 'Archive all cards') }}
</ActionButton>
<ActionButton icon="icon-delete" @click="deleteStack(stack)">
<ActionButton v-if="canManage" icon="icon-clone" @click="cloneStack(stack)">
{{ t('deck', 'Clone list') }}
</ActionButton>
<ActionButton v-if="canManage" icon="icon-delete" @click="deleteStack(stack)">
{{ t('deck', 'Delete list') }}
</ActionButton>
</Actions>
@@ -212,7 +215,6 @@ export default {
this.$store.dispatch('deleteStack', stack)
},
archiveAllCardsFromStack(stack) {
this.stackTransfer.total = this.cardsByStack.length
this.cardsByStack.forEach((card, index) => {
this.stackTransfer.current = index
@@ -220,6 +222,13 @@ export default {
})
this.modalArchivAllCardsShow = false
},
cloneStack(stack) {
try {
this.$store.dispatch('cloneStack', stack)
} catch (e) {
showError('Could not clone stack: ' + e.response.data.message)
}
},
startEditing(stack) {
this.copiedStack = Object.assign({}, stack)
this.editing = true

View File

@@ -140,4 +140,19 @@ export class StackApi {
})
}
cloneStack(stack) {
return axios.post(this.url(`/stacks/${stack.id}/clone`), stack)
.then(
(response) => {
return Promise.resolve(response.data)
},
(err) => {
return Promise.reject(err)
}
)
.catch((err) => {
return Promise.reject(err)
})
}
}

View File

@@ -97,6 +97,15 @@ export default {
commit('addStack', createdStack)
})
},
cloneStack({ commit }, stack) {
apiClient.cloneStack(stack)
.then((stack) => {
for (const j in stack.cards) {
commit('addCard', stack.cards[j])
}
commit('addStack', stack)
})
},
deleteStack({ commit }, stack) {
apiClient.deleteStack(stack.id)
.then((stack) => {

View File

@@ -22,8 +22,6 @@
<template>
<DashboardWidget :items="cards"
empty-content-icon="icon-deck"
:empty-content-message="t('deck', 'No upcoming cards')"
:show-more-text="t('deck', 'upcoming cards')"
:show-more-url="showMoreUrl"
:loading="loading"
@@ -46,11 +44,21 @@
</ul>
</a>
</template>
<template v-slot:empty-content>
<EmptyContent
id="deck-widget-empty-content"
icon="icon-deck">
<template #desc>
{{ t('deck', 'No upcoming cards') }}
</template>
</EmptyContent>
</template>
</DashboardWidget>
</template>
<script>
import { DashboardWidget } from '@nextcloud/vue-dashboard'
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
import { mapGetters } from 'vuex'
import labelStyle from './../mixins/labelStyle'
import DueDate from '../components/cards/badges/DueDate'
@@ -60,6 +68,7 @@ export default {
name: 'Dashboard',
components: {
DueDate,
EmptyContent,
DashboardWidget,
},
mixins: [ labelStyle ],

View File

@@ -33,8 +33,10 @@ use OCA\Deck\Db\Label;
use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Db\Stack;
use OCA\Deck\Db\StackMapper;
use OCA\Deck\Db\AssignedLabelsMapper;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use \Test\TestCase;
use OCP\IL10N;
/**
* Class StackServiceTest
@@ -70,6 +72,9 @@ class StackServiceTest extends TestCase {
private $changeHelper;
/** @var EventDispatcherInterface */
private $eventDispatcher;
private $l10n;
private $userId;
private $assignedLabelsMapper;
public function setUp(): void {
parent::setUp();
@@ -85,6 +90,10 @@ class StackServiceTest extends TestCase {
$this->activityManager = $this->createMock(ActivityManager::class);
$this->changeHelper = $this->createMock(ChangeHelper::class);
$this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
$this->l10n = $this->createMock(IL10N::class);
$this->userId = "admin";
$this->assignedLabelsMapper = $this->createMock(AssignedLabelsMapper::class);
$this->stackService = new StackService(
$this->stackMapper,
@@ -98,7 +107,10 @@ class StackServiceTest extends TestCase {
$this->attachmentService,
$this->activityManager,
$this->eventDispatcher,
$this->changeHelper
$this->changeHelper,
$this->l10n,
$this->userId,
$this->assassignedLabelsMapper
);
}