Compare commits

..

3 Commits

Author SHA1 Message Date
Jakob Röhrl
7364f5dfe6 show only 6 cards
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2021-06-07 10:35:16 +02:00
Jakob Röhrl
1d12ab93f5 fix cancel
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2021-03-11 10:03:30 +01:00
Jakob Röhrl
435994fb1b nice dashboard add card button
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2021-03-04 11:08:37 +01:00
17 changed files with 143 additions and 381 deletions

View File

@@ -107,16 +107,9 @@ OC.L10N.register(
"Select the board to link to a project" : "Izberite zbirko za povezavo s projektom", "Select the board to link to a project" : "Izberite zbirko za povezavo s projektom",
"Search by board title" : "Išči po imenu zbirke", "Search by board title" : "Išči po imenu zbirke",
"Select board" : "Izbor zbirke", "Select board" : "Izbor zbirke",
"Create a new card" : "Ustvari novo nalogo",
"Select a board" : "Izbor zbirke", "Select a board" : "Izbor zbirke",
"Select a list" : "Izbor seznama", "Select a list" : "Izbor seznama",
"Card title" : "Naslov naloge",
"Cancel" : "Prekliči", "Cancel" : "Prekliči",
"Creating the new card…" : "Poteka ustvarjanje nove naloge ...",
"\"{card}\" was added to \"{board}\"" : "Naloga »{card}« je dodana v zbirko »{board}«.",
"Open card" : "Odpri nalogo",
"Close" : "Zapri",
"Create card" : "Ustvari nalogo",
"Select a card" : "Izbor naloge", "Select a card" : "Izbor naloge",
"Select the card to link to a project" : "Izbor naloge za povezavo do projekta", "Select the card to link to a project" : "Izbor naloge za povezavo do projekta",
"Link to card" : "Poveži nalogo", "Link to card" : "Poveži nalogo",
@@ -265,7 +258,6 @@ OC.L10N.register(
"upcoming cards" : "prihajajoče naloge", "upcoming cards" : "prihajajoče naloge",
"Link to a board" : "Povezava do zbirke", "Link to a board" : "Povezava do zbirke",
"Link to a card" : "Povezava do naloge", "Link to a card" : "Povezava do naloge",
"Create a card" : "Ustvari nalogo",
"Something went wrong" : "Prišlo je do napake ...", "Something went wrong" : "Prišlo je do napake ...",
"Failed to upload {name}" : "Pošiljanje {name} je spodletelo", "Failed to upload {name}" : "Pošiljanje {name} je spodletelo",
"Maximum file size of {size} exceeded" : "Omejitev velikosti datoteke {size} je prekoračena.", "Maximum file size of {size} exceeded" : "Omejitev velikosti datoteke {size} je prekoračena.",

View File

@@ -105,16 +105,9 @@
"Select the board to link to a project" : "Izberite zbirko za povezavo s projektom", "Select the board to link to a project" : "Izberite zbirko za povezavo s projektom",
"Search by board title" : "Išči po imenu zbirke", "Search by board title" : "Išči po imenu zbirke",
"Select board" : "Izbor zbirke", "Select board" : "Izbor zbirke",
"Create a new card" : "Ustvari novo nalogo",
"Select a board" : "Izbor zbirke", "Select a board" : "Izbor zbirke",
"Select a list" : "Izbor seznama", "Select a list" : "Izbor seznama",
"Card title" : "Naslov naloge",
"Cancel" : "Prekliči", "Cancel" : "Prekliči",
"Creating the new card…" : "Poteka ustvarjanje nove naloge ...",
"\"{card}\" was added to \"{board}\"" : "Naloga »{card}« je dodana v zbirko »{board}«.",
"Open card" : "Odpri nalogo",
"Close" : "Zapri",
"Create card" : "Ustvari nalogo",
"Select a card" : "Izbor naloge", "Select a card" : "Izbor naloge",
"Select the card to link to a project" : "Izbor naloge za povezavo do projekta", "Select the card to link to a project" : "Izbor naloge za povezavo do projekta",
"Link to card" : "Poveži nalogo", "Link to card" : "Poveži nalogo",
@@ -263,7 +256,6 @@
"upcoming cards" : "prihajajoče naloge", "upcoming cards" : "prihajajoče naloge",
"Link to a board" : "Povezava do zbirke", "Link to a board" : "Povezava do zbirke",
"Link to a card" : "Povezava do naloge", "Link to a card" : "Povezava do naloge",
"Create a card" : "Ustvari nalogo",
"Something went wrong" : "Prišlo je do napake ...", "Something went wrong" : "Prišlo je do napake ...",
"Failed to upload {name}" : "Pošiljanje {name} je spodletelo", "Failed to upload {name}" : "Pošiljanje {name} je spodletelo",
"Maximum file size of {size} exceeded" : "Omejitev velikosti datoteke {size} je prekoračena.", "Maximum file size of {size} exceeded" : "Omejitev velikosti datoteke {size} je prekoračena.",

View File

@@ -45,8 +45,6 @@ use OCA\Deck\Service\FullTextSearchService;
use OCA\Deck\Service\PermissionService; use OCA\Deck\Service\PermissionService;
use OCA\Deck\Sharing\DeckShareProvider; use OCA\Deck\Sharing\DeckShareProvider;
use OCA\Deck\Sharing\Listener; use OCA\Deck\Sharing\Listener;
use OCA\Deck\Listeners\RegisterChecksListener;
use OCA\Deck\Listeners\RegisterEntityListener;
use OCP\AppFramework\App; use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IBootstrap;
@@ -68,8 +66,6 @@ use OCP\IUserManager;
use OCP\Notification\IManager as NotificationManager; use OCP\Notification\IManager as NotificationManager;
use OCP\Share\IManager; use OCP\Share\IManager;
use OCP\Util; use OCP\Util;
use OCP\WorkflowEngine\Events\RegisterChecksEvent;
use OCP\WorkflowEngine\Events\RegisterEntitiesEvent;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
class Application20 extends App implements IBootstrap { class Application20 extends App implements IBootstrap {
@@ -131,9 +127,6 @@ class Application20 extends App implements IBootstrap {
$context->registerDashboardWidget(DeckWidget::class); $context->registerDashboardWidget(DeckWidget::class);
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class); $context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
$context->registerEventListener(RegisterEntitiesEvent::class, RegisterEntityListener::class);
//$context->registerEventListener(RegisterChecksEvent::class, RegisterChecksListener::class);
} }
public function registerNotifications(NotificationManager $notificationManager): void { public function registerNotifications(NotificationManager $notificationManager): void {

View File

@@ -1,43 +0,0 @@
<?php
/*
* @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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/>.
*
*/
declare(strict_types=1);
namespace OCA\Deck\Event;
use OCA\Deck\Db\Card;
class CardCreatedEvent extends \OCP\EventDispatcher\Event {
/** @var Card */
private $card;
public function __construct(Card $card) {
$this->card = $card;
}
public function getCard(): Card {
return $this->card;
}
}

View File

@@ -1,107 +0,0 @@
<?php
/*
* @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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/>.
*
*/
declare(strict_types=1);
namespace OCA\Deck\Flow;
use OCA\Deck\Event\CardCreatedEvent;
use OCP\EventDispatcher\Event;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\WorkflowEngine\EntityContext\IDisplayName;
use OCP\WorkflowEngine\EntityContext\IDisplayText;
use OCP\WorkflowEngine\EntityContext\IUrl;
use OCP\WorkflowEngine\IEntity;
use OCP\WorkflowEngine\IRuleMatcher;
class CardEntity implements IEntity, IDisplayText, IDisplayName, IUrl {
/**
* @var IL10N
*/
private $l10n;
/**
* @var IURLGenerator
*/
private $urlGenerator;
private $card;
public function __construct(IL10N $l, IURLGenerator $urlGenerator) {
$this->l10n = $l;
$this->urlGenerator = $urlGenerator;
}
/**
* @inheritDoc
*/
public function getName(): string {
return $this->l10n->t('Deck card');
}
/**
* @inheritDoc
*/
public function getIcon(): string {
return $this->urlGenerator->imagePath('deck', 'deck-dark.svg');
}
/**
* @inheritDoc
*/
public function getEvents(): array {
return [new CardEntityCreatedEvent($this->l10n)];
}
/**
* @inheritDoc
*/
public function prepareRuleMatcher(IRuleMatcher $ruleMatcher, string $eventName, Event $event): void {
if(!$event instanceof CardCreatedEvent) {
return;
}
/** @var CardCreatedEvent $event */
$ruleMatcher->setEntitySubject($this, $event->getCard());
$this->card = $event->getCard();
}
/**
* @inheritDoc
*/
public function isLegitimatedForUserId(string $userId): bool {
return true;
}
public function getDisplayName(): string {
return $this->card->getTitle();
}
public function getDisplayText(int $verbosity = 0): string {
return $this->card->getTitle() . PHP_EOL . $this->card->getDescription();
}
public function getUrl(): string {
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . '#' . '/board/1/card/' . $this->card->getId();
}
}

View File

@@ -1,48 +0,0 @@
<?php
/*
* @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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/>.
*
*/
declare(strict_types=1);
namespace OCA\Deck\Flow;
use OCA\Deck\Event\CardCreatedEvent;
use OCP\IL10N;
use OCP\WorkflowEngine\IEntityEvent;
class CardEntityCreatedEvent implements IEntityEvent {
/** @var IL10N */
private $l10n;
public function __construct(IL10N $l10n) {
$this->l10n = $l10n;
}
public function getDisplayName(): string {
return $this->l10n->t('Card created');
}
public function getEventName(): string {
return CardCreatedEvent::class;
}
}

View File

@@ -1,51 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.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\Listeners;
use OCA\FlowWebhooks\AppInfo\Application;
use OCA\FlowWebhooks\Flow\ParameterCheck;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Util;
use OCP\WorkflowEngine\Events\RegisterChecksEvent;
class RegisterChecksListener implements IEventListener {
/** @var ParameterCheck */
private $parameterCheck;
public function __construct(ParameterCheck $parameterCheck) {
$this->parameterCheck = $parameterCheck;
}
public function handle(Event $event): void {
if (!($event instanceof RegisterChecksEvent)) {
return;
}
$event->registerCheck($this->parameterCheck);
Util::addScript(Application::APP_ID, Application::APP_ID);
}
}

View File

@@ -1,47 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.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\Listeners;
use OCA\Deck\Flow\CardEntity;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\WorkflowEngine\Events\RegisterEntitiesEvent;
class RegisterEntityListener implements IEventListener {
/** @var CardEntity */
private $cardEntity;
public function __construct(CardEntity $cardEntity) {
$this->cardEntity = $cardEntity;
}
public function handle(Event $event): void {
if(!$event instanceof RegisterEntitiesEvent) {
return;
}
$event->registerEntity($this->cardEntity);
}
}

View File

@@ -35,7 +35,6 @@ use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\Acl; use OCA\Deck\Db\Acl;
use OCA\Deck\Db\ChangeHelper; use OCA\Deck\Db\ChangeHelper;
use OCA\Deck\Db\StackMapper; use OCA\Deck\Db\StackMapper;
use OCA\Deck\Event\CardCreatedEvent;
use OCA\Deck\Event\FTSEvent; use OCA\Deck\Event\FTSEvent;
use OCA\Deck\Notification\NotificationHelper; use OCA\Deck\Notification\NotificationHelper;
use OCA\Deck\Db\BoardMapper; use OCA\Deck\Db\BoardMapper;
@@ -225,7 +224,6 @@ class CardService {
$card = $this->cardMapper->insert($card); $card = $this->cardMapper->insert($card);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_CREATE); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_CREATE);
$this->changeHelper->cardChanged($card->getId(), false); $this->changeHelper->cardChanged($card->getId(), false);
$this->eventDispatcher->dispatchTyped(new CardCreatedEvent($card));
$this->eventDispatcher->dispatch( $this->eventDispatcher->dispatch(
'\OCA\Deck\Card::onCreate', '\OCA\Deck\Card::onCreate',

View File

@@ -27,9 +27,10 @@ use OCA\Deck\Db\Attachment;
use OCA\Deck\Db\AttachmentMapper; use OCA\Deck\Db\AttachmentMapper;
use OCA\Deck\StatusException; use OCA\Deck\StatusException;
use OCA\Deck\Exceptions\ConflictException; use OCA\Deck\Exceptions\ConflictException;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\StreamResponse; use OCP\AppFramework\Http\StreamResponse;
use OCP\Files\IAppData; use OCP\Files\IAppData;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\IRootFolder; use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException; use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException; use OCP\Files\NotPermittedException;
@@ -48,7 +49,6 @@ class FileService implements IAttachmentService {
private $rootFolder; private $rootFolder;
private $config; private $config;
private $attachmentMapper; private $attachmentMapper;
private $mimeTypeDetector;
public function __construct( public function __construct(
IL10N $l10n, IL10N $l10n,
@@ -57,8 +57,7 @@ class FileService implements IAttachmentService {
ILogger $logger, ILogger $logger,
IRootFolder $rootFolder, IRootFolder $rootFolder,
IConfig $config, IConfig $config,
AttachmentMapper $attachmentMapper, AttachmentMapper $attachmentMapper
IMimeTypeDetector $mimeTypeDetector
) { ) {
$this->l10n = $l10n; $this->l10n = $l10n;
$this->appData = $appData; $this->appData = $appData;
@@ -67,7 +66,6 @@ class FileService implements IAttachmentService {
$this->rootFolder = $rootFolder; $this->rootFolder = $rootFolder;
$this->config = $config; $this->config = $config;
$this->attachmentMapper = $attachmentMapper; $this->attachmentMapper = $attachmentMapper;
$this->mimeTypeDetector = $mimeTypeDetector;
} }
/** /**
@@ -227,14 +225,27 @@ class FileService implements IAttachmentService {
/** /**
* @param Attachment $attachment * @param Attachment $attachment
* @return StreamResponse * @return FileDisplayResponse|\OCP\AppFramework\Http\Response|StreamResponse
* @throws \Exception * @throws \Exception
*/ */
public function display(Attachment $attachment) { public function display(Attachment $attachment) {
$file = $this->getFileFromRootFolder($attachment); $file = $this->getFileFromRootFolder($attachment);
$response = new StreamResponse($file->fopen('rb')); if (method_exists($file, 'fopen')) {
$response->addHeader('Content-Disposition', 'attachment; filename="' . rawurldecode($file->getName()) . '"'); $response = new StreamResponse($file->fopen('r'));
$response->addHeader('Content-Type', $this->mimeTypeDetector->getSecureMimeType($file->getMimeType())); $response->addHeader('Content-Disposition', 'inline; filename="' . rawurldecode($file->getName()) . '"');
} else {
$response = new FileDisplayResponse($file);
}
// We need those since otherwise chrome won't show the PDF file with CSP rule object-src 'none'
// https://bugs.chromium.org/p/chromium/issues/detail?id=271452
$policy = new ContentSecurityPolicy();
$policy->addAllowedObjectDomain('\'self\'');
$policy->addAllowedObjectDomain('blob:');
$policy->addAllowedMediaDomain('\'self\'');
$policy->addAllowedMediaDomain('blob:');
$response->setContentSecurityPolicy($policy);
$response->addHeader('Content-Type', $file->getMimeType());
return $response; return $response;
} }

View File

@@ -26,9 +26,10 @@ namespace OCA\Deck\Service;
use OCA\Deck\Db\Attachment; use OCA\Deck\Db\Attachment;
use OCA\Deck\Sharing\DeckShareProvider; use OCA\Deck\Sharing\DeckShareProvider;
use OCA\Deck\StatusException; use OCA\Deck\StatusException;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\StreamResponse; use OCP\AppFramework\Http\StreamResponse;
use OCP\Constants; use OCP\Constants;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\IRootFolder; use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException; use OCP\Files\NotFoundException;
use OCP\IDBConnection; use OCP\IDBConnection;
@@ -49,7 +50,6 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
private $l10n; private $l10n;
private $preview; private $preview;
private $permissionService; private $permissionService;
private $mimeTypeDetector;
public function __construct( public function __construct(
IRequest $request, IRequest $request,
@@ -60,7 +60,6 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
DeckShareProvider $shareProvider, DeckShareProvider $shareProvider,
IPreview $preview, IPreview $preview,
PermissionService $permissionService, PermissionService $permissionService,
IMimeTypeDetector $mimeTypeDetector,
string $userId = null string $userId = null
) { ) {
$this->request = $request; $this->request = $request;
@@ -71,7 +70,6 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
$this->shareManager = $shareManager; $this->shareManager = $shareManager;
$this->userId = $userId; $this->userId = $userId;
$this->preview = $preview; $this->preview = $preview;
$this->mimeTypeDetector = $mimeTypeDetector;
} }
public function listAttachments(int $cardId): array { public function listAttachments(int $cardId): array {
@@ -149,10 +147,22 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
if ($file === null || $share->getSharedWith() !== (string)$attachment->getCardId()) { if ($file === null || $share->getSharedWith() !== (string)$attachment->getCardId()) {
throw new NotFoundException('File not found'); throw new NotFoundException('File not found');
} }
if (method_exists($file, 'fopen')) {
$response = new StreamResponse($file->fopen('r'));
$response->addHeader('Content-Disposition', 'inline; filename="' . rawurldecode($file->getName()) . '"');
} else {
$response = new FileDisplayResponse($file);
}
// We need those since otherwise chrome won't show the PDF file with CSP rule object-src 'none'
// https://bugs.chromium.org/p/chromium/issues/detail?id=271452
$policy = new ContentSecurityPolicy();
$policy->addAllowedObjectDomain('\'self\'');
$policy->addAllowedObjectDomain('blob:');
$policy->addAllowedMediaDomain('\'self\'');
$policy->addAllowedMediaDomain('blob:');
$response->setContentSecurityPolicy($policy);
$response = new StreamResponse($file->fopen('rb')); $response->addHeader('Content-Type', $file->getMimeType());
$response->addHeader('Content-Disposition', 'attachment; filename="' . rawurldecode($file->getName()) . '"');
$response->addHeader('Content-Type', $this->mimeTypeDetector->getSecureMimeType($file->getMimeType()));
return $response; return $response;
} }

View File

@@ -168,6 +168,7 @@ export default {
}, },
close() { close() {
this.$emit('close')
this.$root.$emit('close') this.$root.$emit('close')
}, },
async select() { async select() {

View File

@@ -28,11 +28,11 @@
:members="members" :members="members"
name-key="uid" name-key="uid"
:tab-select="true"> :tab-select="true">
<template v-slot:item="s"> <template #item="s">
<Avatar class="atwho-li--avatar" :user="s.item.uid" :size="24" /> <Avatar class="atwho-li--avatar" :user="s.item.uid" :size="24" />
<span class="atwho-li--name" v-text="s.item.displayname" /> <span class="atwho-li--name" v-text="s.item.displayname" />
</template> </template>
<template v-slot:embeddedItem="scope"> <template #embeddedItem="scope">
<span> <span>
<UserBubble v-if="scope.current.uid" <UserBubble v-if="scope.current.uid"
:data-mention-id="scope.current.uid" :data-mention-id="scope.current.uid"

View File

@@ -149,6 +149,9 @@ export default {
directives: { directives: {
ClickOutside, ClickOutside,
}, },
inject: [
'boardApi',
],
props: { props: {
board: { board: {
type: Object, type: Object,
@@ -312,9 +315,6 @@ export default {
this.updateDueSetting = null this.updateDueSetting = null
}, },
}, },
inject: [
'boardApi',
],
} }
</script> </script>

View File

@@ -21,32 +21,40 @@
--> -->
<template> <template>
<DashboardWidget :items="cards" <div>
empty-content-icon="icon-deck" <DashboardWidget :items="cards"
:empty-content-message="t('deck', 'No upcoming cards')" empty-content-icon="icon-deck"
:show-more-text="t('deck', 'upcoming cards')" :empty-content-message="t('deck', 'No upcoming cards')"
:show-more-url="showMoreUrl" :show-more-text="t('deck', 'upcoming cards')"
:loading="loading" :show-more-url="showMoreUrl"
@hide="() => {}" :loading="loading"
@markDone="() => {}"> @hide="() => {}"
<template v-slot:default="{ item }"> @markDone="() => {}">
<a :key="item.id" <template #default="{ item }">
:href="cardLink(item)" <a :key="item.id"
target="_blank" :href="cardLink(item)"
class="card"> target="_blank"
<div class="card--header"> class="card">
<DueDate class="right" :card="item" /> <div class="card--header">
<span class="title">{{ item.title }}</span> <DueDate class="right" :card="item" />
</div> <span class="title">{{ item.title }}</span>
<ul v-if="item.labels && item.labels.length" </div>
class="labels"> <ul v-if="item.labels && item.labels.length"
<li v-for="label in item.labels" :key="label.id" :style="labelStyle(label)"> class="labels">
<span>{{ label.title }}</span> <li v-for="label in item.labels" :key="label.id" :style="labelStyle(label)">
</li> <span>{{ label.title }}</span>
</ul> </li>
</a> </ul>
</template> </a>
</DashboardWidget> </template>
</DashboardWidget>
<div class="center-button">
<button @click="toggleAddCardModel">
{{ t('deck', 'Add card') }}
</button>
<CardCreateDialog v-if="showAddCardModal" @close="toggleAddCardModel" />
</div>
</div>
</template> </template>
<script> <script>
@@ -55,17 +63,20 @@ import { mapGetters } from 'vuex'
import labelStyle from './../mixins/labelStyle' import labelStyle from './../mixins/labelStyle'
import DueDate from '../components/cards/badges/DueDate' import DueDate from '../components/cards/badges/DueDate'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import CardCreateDialog from '../CardCreateDialog'
export default { export default {
name: 'Dashboard', name: 'Dashboard',
components: { components: {
DueDate, DueDate,
DashboardWidget, DashboardWidget,
CardCreateDialog,
}, },
mixins: [labelStyle], mixins: [labelStyle],
data() { data() {
return { return {
loading: false, loading: false,
showAddCardModal: false,
} }
}, },
computed: { computed: {
@@ -73,11 +84,17 @@ export default {
'assignedCardsDashboard', 'assignedCardsDashboard',
]), ]),
cards() { cards() {
const list = [ /* const list = [
...this.assignedCardsDashboard, ...this.assignedCardsDashboard,
].filter((card) => { ].filter((card) => {
return card.duedate !== null return card.duedate !== null
}) }) */
const list = this.assignedCardsDashboard.slice(0, 6)
.filter((card) => {
return card.duedate !== null
})
list.sort((a, b) => { list.sort((a, b) => {
return (new Date(a.duedate)).getTime() - (new Date(b.duedate)).getTime() return (new Date(a.duedate)).getTime() - (new Date(b.duedate)).getTime()
}) })
@@ -98,6 +115,11 @@ export default {
this.loading = false this.loading = false
}) })
}, },
methods: {
toggleAddCardModel() {
this.showAddCardModal = !this.showAddCardModal
},
},
} }
</script> </script>
@@ -145,4 +167,8 @@ export default {
.right { .right {
float: right; float: right;
} }
.center-button {
text-align: center;
}
</style> </style>

View File

@@ -25,10 +25,10 @@ namespace OCA\Deck\Service;
use OCA\Deck\Db\Attachment; use OCA\Deck\Db\Attachment;
use OCA\Deck\Db\AttachmentMapper; use OCA\Deck\Db\AttachmentMapper;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\StreamResponse; use OCP\AppFramework\Http\StreamResponse;
use OCP\Files\Folder; use OCP\Files\Folder;
use OCP\Files\IAppData; use OCP\Files\IAppData;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\IRootFolder; use OCP\Files\IRootFolder;
use OCP\Files\SimpleFS\ISimpleFile; use OCP\Files\SimpleFS\ISimpleFile;
use OCP\Files\SimpleFS\ISimpleFolder; use OCP\Files\SimpleFS\ISimpleFolder;
@@ -57,8 +57,6 @@ class FileServiceTest extends TestCase {
private $config; private $config;
/** @var AttachmentMapper|MockObject */ /** @var AttachmentMapper|MockObject */
private $attachmentMapper; private $attachmentMapper;
/** @var IMimeTypeDetector|MockObject */
private $mimeTypeDetector;
public function setUp(): void { public function setUp(): void {
parent::setUp(); parent::setUp();
@@ -69,8 +67,7 @@ class FileServiceTest extends TestCase {
$this->rootFolder = $this->createMock(IRootFolder::class); $this->rootFolder = $this->createMock(IRootFolder::class);
$this->config = $this->createMock(IConfig::class); $this->config = $this->createMock(IConfig::class);
$this->attachmentMapper = $this->createMock(AttachmentMapper::class); $this->attachmentMapper = $this->createMock(AttachmentMapper::class);
$this->mimeTypeDetector = $this->createMock(IMimeTypeDetector::class); $this->fileService = new FileService($this->l10n, $this->appData, $this->request, $this->logger, $this->rootFolder, $this->config, $this->attachmentMapper);
$this->fileService = new FileService($this->l10n, $this->appData, $this->request, $this->logger, $this->rootFolder, $this->config, $this->attachmentMapper, $this->mimeTypeDetector);
} }
public function mockGetFolder($cardId) { public function mockGetFolder($cardId) {
@@ -271,13 +268,51 @@ class FileServiceTest extends TestCase {
$file->expects($this->any()) $file->expects($this->any())
->method('fopen') ->method('fopen')
->willReturn('fileresource'); ->willReturn('fileresource');
$this->mimeTypeDetector->expects($this->once())
->method('getSecureMimeType')
->willReturn('image/jpeg');
$actual = $this->fileService->display($attachment); $actual = $this->fileService->display($attachment);
$expected = new StreamResponse('fileresource'); $expected = new StreamResponse('fileresource');
$expected->addHeader('Content-Type', 'image/jpeg'); $expected->addHeader('Content-Type', 'image/jpeg');
$expected->addHeader('Content-Disposition', 'attachment; filename="' . rawurldecode($file->getName()) . '"'); $expected->addHeader('Content-Disposition', 'inline; filename="' . rawurldecode($file->getName()) . '"');
$policy = new ContentSecurityPolicy();
$policy->addAllowedObjectDomain('\'self\'');
$policy->addAllowedObjectDomain('blob:');
$policy->addAllowedMediaDomain('\'self\'');
$policy->addAllowedMediaDomain('blob:');
$expected->setContentSecurityPolicy($policy);
$this->assertEquals($expected, $actual);
}
public function testDisplayPdf() {
$this->config->expects($this->once())
->method('getSystemValue')
->willReturn('123');
$appDataFolder = $this->createMock(Folder::class);
$deckAppDataFolder = $this->createMock(Folder::class);
$cardFolder = $this->createMock(Folder::class);
$this->rootFolder->expects($this->once())->method('get')->willReturn($appDataFolder);
$appDataFolder->expects($this->once())->method('get')->willReturn($deckAppDataFolder);
$deckAppDataFolder->expects($this->once())->method('get')->willReturn($cardFolder);
$attachment = $this->getAttachment();
$file = $this->createMock(\OCP\Files\File::class);
$cardFolder->expects($this->once())->method('get')->willReturn($file);
$file->expects($this->any())
->method('getMimeType')
->willReturn('application/pdf');
$file->expects($this->any())
->method('getName')
->willReturn('file1');
$file->expects($this->any())
->method('fopen')
->willReturn('fileresource');
$actual = $this->fileService->display($attachment);
$expected = new StreamResponse('fileresource');
$expected->addHeader('Content-Disposition', 'inline; filename="' . rawurldecode($file->getName()) . '"');
$expected->addHeader('Content-Type', 'application/pdf');
$policy = new ContentSecurityPolicy();
$policy->addAllowedObjectDomain('\'self\'');
$policy->addAllowedObjectDomain('blob:');
$policy->addAllowedMediaDomain('\'self\'');
$policy->addAllowedMediaDomain('blob:');
$expected->setContentSecurityPolicy($policy);
$this->assertEquals($expected, $actual); $this->assertEquals($expected, $actual);
} }

View File

@@ -11,7 +11,7 @@ const config = {
}, },
output: { output: {
filename: '[name].js', filename: '[name].js',
jsonpFunction: 'webpackJsonpOCADeck', // jsonpFunction: 'webpackJsonpOCADeck',
chunkFilename: '[name].js?v=[contenthash]', chunkFilename: '[name].js?v=[contenthash]',
}, },
resolve: { resolve: {