Add option to configure notification level per board
Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
@@ -127,6 +127,8 @@ return [
|
|||||||
'ocs' => [
|
'ocs' => [
|
||||||
['name' => 'Config#get', 'url' => '/api/v1.0/config', 'verb' => 'GET'],
|
['name' => 'Config#get', 'url' => '/api/v1.0/config', 'verb' => 'GET'],
|
||||||
['name' => 'Config#setValue', 'url' => '/api/v1.0/config/{key}', 'verb' => 'POST'],
|
['name' => 'Config#setValue', 'url' => '/api/v1.0/config/{key}', 'verb' => 'POST'],
|
||||||
|
['name' => 'Config#setBoardValue', 'url' => '/api/v1.0/config/board/{id}/{key}', 'verb' => 'POST'],
|
||||||
|
|
||||||
|
|
||||||
['name' => 'comments_api#list', 'url' => '/api/v1.0/cards/{cardId}/comments', 'verb' => 'GET'],
|
['name' => 'comments_api#list', 'url' => '/api/v1.0/cards/{cardId}/comments', 'verb' => 'GET'],
|
||||||
['name' => 'comments_api#create', 'url' => '/api/v1.0/cards/{cardId}/comments', 'verb' => 'POST'],
|
['name' => 'comments_api#create', 'url' => '/api/v1.0/cards/{cardId}/comments', 'verb' => 'POST'],
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
@include icon-black-white('filter_set', 'deck', 1);
|
@include icon-black-white('filter_set', 'deck', 1);
|
||||||
@include icon-black-white('attach', 'deck', 1);
|
@include icon-black-white('attach', 'deck', 1);
|
||||||
@include icon-black-white('reply', 'deck', 1);
|
@include icon-black-white('reply', 'deck', 1);
|
||||||
|
@include icon-black-white('notifications-dark', 'deck', 1);
|
||||||
|
|
||||||
.icon-toggle-compact-collapsed {
|
.icon-toggle-compact-collapsed {
|
||||||
@include icon-color('toggle-view-expand', 'deck', $color-black);
|
@include icon-color('toggle-view-expand', 'deck', $color-black);
|
||||||
|
|||||||
4
img/notifications-dark.svg
Normal file
4
img/notifications-dark.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewBox="0 0 16 16">
|
||||||
|
<path d="m8 2c-0.5523 0-1 0.4477-1 1 0 0.0472 0.021 0.0873 0.0273 0.1328-1.7366 0.4362-3.0273 1.9953-3.0273 3.8672v2l-1 1v1h10v-1l-1-1v-2c0-1.8719-1.291-3.431-3.0273-3.8672 0.0063-0.0455 0.0273-0.0856 0.0273-0.1328 0-0.5523-0.4477-1-1-1zm-2 10c0 1.1046 0.8954 2 2 2s2-0.8954 2-2z" fill="#000"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 456 B |
@@ -37,6 +37,8 @@ class Board extends RelationalEntity {
|
|||||||
protected $deletedAt = 0;
|
protected $deletedAt = 0;
|
||||||
protected $lastModified = 0;
|
protected $lastModified = 0;
|
||||||
|
|
||||||
|
protected $settings = [];
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->addType('id', 'integer');
|
$this->addType('id', 'integer');
|
||||||
$this->addType('shared', 'integer');
|
$this->addType('shared', 'integer');
|
||||||
@@ -49,6 +51,7 @@ class Board extends RelationalEntity {
|
|||||||
$this->addRelation('users');
|
$this->addRelation('users');
|
||||||
$this->addRelation('permissions');
|
$this->addRelation('permissions');
|
||||||
$this->addRelation('stacks');
|
$this->addRelation('stacks');
|
||||||
|
$this->addRelation('settings');
|
||||||
$this->addResolvable('owner');
|
$this->addResolvable('owner');
|
||||||
$this->shared = -1;
|
$this->shared = -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ namespace OCA\Deck\Service;
|
|||||||
|
|
||||||
use OCA\Deck\Activity\ActivityManager;
|
use OCA\Deck\Activity\ActivityManager;
|
||||||
use OCA\Deck\Activity\ChangeSet;
|
use OCA\Deck\Activity\ChangeSet;
|
||||||
|
use OCA\Deck\AppInfo\Application;
|
||||||
use OCA\Deck\Db\Acl;
|
use OCA\Deck\Db\Acl;
|
||||||
use OCA\Deck\Db\AclMapper;
|
use OCA\Deck\Db\AclMapper;
|
||||||
use OCA\Deck\Db\AssignedUsersMapper;
|
use OCA\Deck\Db\AssignedUsersMapper;
|
||||||
@@ -37,6 +38,7 @@ use OCA\Deck\Db\StackMapper;
|
|||||||
use OCA\Deck\NoPermissionException;
|
use OCA\Deck\NoPermissionException;
|
||||||
use OCA\Deck\Notification\NotificationHelper;
|
use OCA\Deck\Notification\NotificationHelper;
|
||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\IConfig;
|
||||||
use OCP\IGroupManager;
|
use OCP\IGroupManager;
|
||||||
use OCP\IL10N;
|
use OCP\IL10N;
|
||||||
use OCA\Deck\Db\Board;
|
use OCA\Deck\Db\Board;
|
||||||
@@ -52,6 +54,8 @@ class BoardService {
|
|||||||
private $stackMapper;
|
private $stackMapper;
|
||||||
private $labelMapper;
|
private $labelMapper;
|
||||||
private $aclMapper;
|
private $aclMapper;
|
||||||
|
/** @var IConfig */
|
||||||
|
private $config;
|
||||||
private $l10n;
|
private $l10n;
|
||||||
private $permissionService;
|
private $permissionService;
|
||||||
private $notificationHelper;
|
private $notificationHelper;
|
||||||
@@ -66,9 +70,11 @@ class BoardService {
|
|||||||
|
|
||||||
private $boardsCache = null;
|
private $boardsCache = null;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
BoardMapper $boardMapper,
|
BoardMapper $boardMapper,
|
||||||
StackMapper $stackMapper,
|
StackMapper $stackMapper,
|
||||||
|
IConfig $config,
|
||||||
IL10N $l10n,
|
IL10N $l10n,
|
||||||
LabelMapper $labelMapper,
|
LabelMapper $labelMapper,
|
||||||
AclMapper $aclMapper,
|
AclMapper $aclMapper,
|
||||||
@@ -85,6 +91,7 @@ class BoardService {
|
|||||||
$this->boardMapper = $boardMapper;
|
$this->boardMapper = $boardMapper;
|
||||||
$this->stackMapper = $stackMapper;
|
$this->stackMapper = $stackMapper;
|
||||||
$this->labelMapper = $labelMapper;
|
$this->labelMapper = $labelMapper;
|
||||||
|
$this->config = $config;
|
||||||
$this->aclMapper = $aclMapper;
|
$this->aclMapper = $aclMapper;
|
||||||
$this->l10n = $l10n;
|
$this->l10n = $l10n;
|
||||||
$this->permissionService = $permissionService;
|
$this->permissionService = $permissionService;
|
||||||
@@ -151,6 +158,7 @@ class BoardService {
|
|||||||
'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE] ?? false,
|
'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE] ?? false,
|
||||||
'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE] ?? false
|
'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE] ?? false
|
||||||
]);
|
]);
|
||||||
|
$this->enrichWithBoardSettings($item);
|
||||||
$result[$item->getId()] = $item;
|
$result[$item->getId()] = $item;
|
||||||
}
|
}
|
||||||
$this->boardsCache = $result;
|
$this->boardsCache = $result;
|
||||||
@@ -190,6 +198,7 @@ class BoardService {
|
|||||||
'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE] ?? false
|
'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE] ?? false
|
||||||
]);
|
]);
|
||||||
$this->enrichWithUsers($board);
|
$this->enrichWithUsers($board);
|
||||||
|
$this->enrichWithBoardSettings($board);
|
||||||
$this->boardsCache[$board->getId()] = $board;
|
$this->boardsCache[$board->getId()] = $board;
|
||||||
return $board;
|
return $board;
|
||||||
}
|
}
|
||||||
@@ -476,6 +485,14 @@ class BoardService {
|
|||||||
return [$edit, $share, $manage];
|
return [$edit, $share, $manage];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function enrichWithBoardSettings(Board $board) {
|
||||||
|
$settings = [
|
||||||
|
'notify-due' => $this->config->getUserValue($this->userId, Application::APP_ID, 'board:' . $board->getId() . ':notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED),
|
||||||
|
'calendar' => $this->config->getUserValue($this->userId, Application::APP_ID, 'board:' . $board->getId() . ':calendar', true),
|
||||||
|
];
|
||||||
|
$board->setSettings($settings);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $boardId
|
* @param $boardId
|
||||||
* @param $type
|
* @param $type
|
||||||
|
|||||||
@@ -27,12 +27,18 @@ declare(strict_types=1);
|
|||||||
namespace OCA\Deck\Service;
|
namespace OCA\Deck\Service;
|
||||||
|
|
||||||
use OCA\Deck\AppInfo\Application;
|
use OCA\Deck\AppInfo\Application;
|
||||||
|
use OCA\Deck\BadRequestException;
|
||||||
use OCA\Deck\NoPermissionException;
|
use OCA\Deck\NoPermissionException;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
use OCP\IGroup;
|
use OCP\IGroup;
|
||||||
use OCP\IGroupManager;
|
use OCP\IGroupManager;
|
||||||
|
|
||||||
class ConfigService {
|
class ConfigService {
|
||||||
|
public const SETTING_BOARD_NOTIFICATION_DUE_OFF = 'off';
|
||||||
|
public const SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED = 'assigned';
|
||||||
|
public const SETTING_BOARD_NOTIFICATION_DUE_ALL = 'all';
|
||||||
|
public const SETTING_BOARD_NOTIFICATION_DUE_DEFAULT = self::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED;
|
||||||
|
|
||||||
private $config;
|
private $config;
|
||||||
private $userId;
|
private $userId;
|
||||||
private $groupManager;
|
private $groupManager;
|
||||||
@@ -52,9 +58,7 @@ class ConfigService {
|
|||||||
'calendar' => $this->get('calendar')
|
'calendar' => $this->get('calendar')
|
||||||
];
|
];
|
||||||
if ($this->groupManager->isAdmin($this->userId)) {
|
if ($this->groupManager->isAdmin($this->userId)) {
|
||||||
$data = [
|
$data['groupLimit'] = $this->get('groupLimit');
|
||||||
'groupLimit' => $this->get('groupLimit'),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
@@ -77,7 +81,8 @@ class ConfigService {
|
|||||||
|
|
||||||
public function set($key, $value) {
|
public function set($key, $value) {
|
||||||
$result = null;
|
$result = null;
|
||||||
switch ($key) {
|
[$scope, $id] = explode(':', $key, 2);
|
||||||
|
switch ($scope) {
|
||||||
case 'groupLimit':
|
case 'groupLimit':
|
||||||
if (!$this->groupManager->isAdmin($this->userId)) {
|
if (!$this->groupManager->isAdmin($this->userId)) {
|
||||||
throw new NoPermissionException('You must be admin to set the group limit');
|
throw new NoPermissionException('You must be admin to set the group limit');
|
||||||
@@ -88,6 +93,13 @@ class ConfigService {
|
|||||||
$this->config->setUserValue($this->userId, Application::APP_ID, 'calendar', (int)$value);
|
$this->config->setUserValue($this->userId, Application::APP_ID, 'calendar', (int)$value);
|
||||||
$result = $value;
|
$result = $value;
|
||||||
break;
|
break;
|
||||||
|
case 'board':
|
||||||
|
[$boardId, $boardConfigKey] = explode(':', $key);
|
||||||
|
if (!in_array($value, [self::SETTING_BOARD_NOTIFICATION_DUE_ALL, self::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED, self::SETTING_BOARD_NOTIFICATION_DUE_OFF], true)) {
|
||||||
|
throw new BadRequestException('Board notification option must be one of: off, assigned, all');
|
||||||
|
}
|
||||||
|
$this->config->setUserValue($this->userId, Application::APP_ID, $key, $value);
|
||||||
|
$result = $value;
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,39 +34,65 @@
|
|||||||
style="opacity: 0.5" />
|
style="opacity: 0.5" />
|
||||||
|
|
||||||
<template v-if="!deleted" slot="actions">
|
<template v-if="!deleted" slot="actions">
|
||||||
<ActionButton v-if="canManage && !board.archived"
|
<ActionButton v-if="!showDueSettings"
|
||||||
|
icon="icon-more"
|
||||||
|
:close-after-click="true"
|
||||||
|
@click="actionDetails">
|
||||||
|
{{ t('deck', 'Board details') }}
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton v-if="canManage && !board.archived && !showDueSettings"
|
||||||
icon="icon-rename"
|
icon="icon-rename"
|
||||||
:close-after-click="true"
|
:close-after-click="true"
|
||||||
@click="actionEdit">
|
@click="actionEdit">
|
||||||
{{ t('deck', 'Edit board') }}
|
{{ t('deck', 'Edit board') }}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton v-if="canManage && !board.archived"
|
<ActionButton v-if="canManage && !board.archived && !showDueSettings"
|
||||||
icon="icon-clone"
|
icon="icon-clone"
|
||||||
:close-after-click="true"
|
:close-after-click="true"
|
||||||
@click="actionClone">
|
@click="actionClone">
|
||||||
{{ t('deck', 'Clone board ') }}
|
{{ t('deck', 'Clone board ') }}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton v-if="canManage && board.archived"
|
<ActionButton v-if="canManage && board.archived && !showDueSettings"
|
||||||
icon="icon-archive"
|
icon="icon-archive"
|
||||||
:close-after-click="true"
|
:close-after-click="true"
|
||||||
@click="actionUnarchive">
|
@click="actionUnarchive">
|
||||||
{{ t('deck', 'Unarchive board ') }}
|
{{ t('deck', 'Unarchive board ') }}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton v-if="canManage && !board.archived"
|
<ActionButton v-if="canManage && !board.archived && !showDueSettings"
|
||||||
icon="icon-archive"
|
icon="icon-archive"
|
||||||
:close-after-click="true"
|
:close-after-click="true"
|
||||||
@click="actionArchive">
|
@click="actionArchive">
|
||||||
{{ t('deck', 'Archive board ') }}
|
{{ t('deck', 'Archive board ') }}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton v-if="canManage"
|
|
||||||
|
<ActionButton :icon="!showDueSettings ? 'icon-notifications-dark' : 'icon-view-previous' " @click="showDueSettings=!showDueSettings">
|
||||||
|
{{ t('deck', 'Due date reminders') }}
|
||||||
|
</ActionButton>
|
||||||
|
<ActionRadio v-if="showDueSettings"
|
||||||
|
name="notification"
|
||||||
|
:checked="board.settings['notify-due'] === 'all'"
|
||||||
|
@change="updateSetting('notify-due', 'all')">
|
||||||
|
{{ t('deck', 'All cards') }}
|
||||||
|
</ActionRadio>
|
||||||
|
<ActionRadio v-if="showDueSettings"
|
||||||
|
name="notification"
|
||||||
|
:checked="board.settings['notify-due'] === 'assigned'"
|
||||||
|
@change="updateSetting('notify-due', 'assigned')">
|
||||||
|
{{ t('deck', 'Assigned cards') }}
|
||||||
|
</ActionRadio>
|
||||||
|
<ActionRadio v-if="showDueSettings"
|
||||||
|
name="notification"
|
||||||
|
:checked="board.settings['notify-due'] === 'off'"
|
||||||
|
@change="updateSetting('notify-due', 'off')">
|
||||||
|
{{ t('deck', 'No notifications') }}
|
||||||
|
</ActionRadio>
|
||||||
|
|
||||||
|
<ActionButton v-if="canManage && !showDueSettings"
|
||||||
icon="icon-delete"
|
icon="icon-delete"
|
||||||
:close-after-click="true"
|
:close-after-click="true"
|
||||||
@click="actionDelete">
|
@click="actionDelete">
|
||||||
{{ t('deck', 'Delete board ') }}
|
{{ t('deck', 'Delete board ') }}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton icon="icon-more" :close-after-click="true" @click="actionDetails">
|
|
||||||
{{ t('deck', 'Board details') }}
|
|
||||||
</ActionButton>
|
|
||||||
</template>
|
</template>
|
||||||
</AppNavigationItem>
|
</AppNavigationItem>
|
||||||
<div v-else-if="editing" class="board-edit">
|
<div v-else-if="editing" class="board-edit">
|
||||||
@@ -82,7 +108,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { AppNavigationIconBullet, AppNavigationCounter, AppNavigationItem, ColorPicker, Actions, ActionButton } from '@nextcloud/vue'
|
import { AppNavigationIconBullet, AppNavigationCounter, AppNavigationItem, ColorPicker, Actions, ActionButton, ActionRadio } from '@nextcloud/vue'
|
||||||
import ClickOutside from 'vue-click-outside'
|
import ClickOutside from 'vue-click-outside'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -94,6 +120,7 @@ export default {
|
|||||||
ColorPicker,
|
ColorPicker,
|
||||||
Actions,
|
Actions,
|
||||||
ActionButton,
|
ActionButton,
|
||||||
|
ActionRadio,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
ClickOutside,
|
ClickOutside,
|
||||||
@@ -114,6 +141,7 @@ export default {
|
|||||||
undoTimeoutHandle: null,
|
undoTimeoutHandle: null,
|
||||||
editTitle: '',
|
editTitle: '',
|
||||||
editColor: '',
|
editColor: '',
|
||||||
|
showDueSettings: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -230,6 +258,11 @@ export default {
|
|||||||
route.name = 'board.details'
|
route.name = 'board.details'
|
||||||
this.$router.push(route)
|
this.$router.push(route)
|
||||||
},
|
},
|
||||||
|
async updateSetting(key, value) {
|
||||||
|
const setting = {}
|
||||||
|
setting['board:' + this.board.id + ':' + key] = value
|
||||||
|
await this.$store.dispatch('setConfig', setting)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
inject: [
|
inject: [
|
||||||
'boardApi',
|
'boardApi',
|
||||||
|
|||||||
Reference in New Issue
Block a user