Compare commits

..

133 Commits

Author SHA1 Message Date
Nextcloud bot
c0a8a00131 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-30 02:40:00 +00:00
Nextcloud bot
b686d446b1 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-29 02:41:12 +00:00
Nextcloud bot
8e60717cca [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-28 02:41:26 +00:00
Nextcloud bot
b8b4d708f3 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-24 02:41:21 +00:00
Nextcloud bot
5bbd88be0b [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-23 02:39:32 +00:00
Nextcloud bot
60e18f3556 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-22 02:40:31 +00:00
Julius Härtl
4aef4db2c9 Merge pull request #3732 from nextcloud/backport/3497/stable21
[stable21] [stable23] Use explicit cast to make use of index
2022-04-20 16:20:47 +02:00
Julius Härtl
bb1022ac62 Use explicit cast to make use of index
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-04-19 19:43:40 +00:00
Nextcloud bot
d36cc8e8b3 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-17 02:38:13 +00:00
Nextcloud bot
2221ed53b0 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-14 02:42:27 +00:00
Nextcloud bot
ae3a67bd43 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-13 02:40:18 +00:00
Julius Härtl
c640b31384 Merge pull request #3715 from nextcloud/backport/3670/stable21
[stable21] Properly check for the stack AND setting board permissions
2022-04-11 18:27:58 +02:00
Julius Härtl
546a3008b5 Properly check for the stack AND setting board permissions
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-04-11 14:35:40 +00:00
Nextcloud bot
c25bd37971 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-09 02:39:20 +00:00
Nextcloud bot
36028cdfeb [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-06 02:39:56 +00:00
Nextcloud bot
85ea22ff0c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-04-05 02:39:58 +00:00
Nextcloud bot
8cf08ddd08 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-31 02:41:44 +00:00
Nextcloud bot
5668abacec [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-30 02:39:35 +00:00
Nextcloud bot
5ffdc3ec60 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-29 02:39:57 +00:00
Nextcloud bot
7d72a67c4c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-28 02:39:04 +00:00
Nextcloud bot
bab74ced75 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-25 03:07:31 +00:00
Nextcloud bot
f6a58bb8b7 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-24 02:38:38 +00:00
Nextcloud bot
5dacdf070e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-18 02:38:35 +00:00
Julius Härtl
890291ccaa Merge pull request #3643 from nextcloud/backport/3635/stable21
[stable21] 🐛 Fix missing files sidebar
2022-03-17 08:19:51 +01:00
Vinicius Reis
89771438f8 fix style-lint
Signed-off-by: Vinicius Reis <vinicius.reis@nextcloud.com>
2022-03-17 00:10:40 -03:00
Nextcloud bot
508bf95378 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-17 02:41:23 +00:00
Julius Härtl
c15160dad1 Merge pull request #3631 from nextcloud/bugfix/node-engines
Add node/npm versions to package.json
2022-03-15 21:52:13 +01:00
Vinicius Reis
7590f1d625 🐛 Fix missing files sidebar
Signed-off-by: Vinicius Reis <vinicius.reis@nextcloud.com>
2022-03-14 14:11:06 +00:00
Julius Härtl
c7f09cbf82 Add node/npm versions to package.json
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-10 10:07:14 +01:00
Julius Härtl
4e14dfb15f Merge pull request #3628 from nextcloud/release/1.4.8
Release 1.4.8
2022-03-10 09:54:13 +01:00
Julius Härtl
84fe5bd9ef Run tests against proper stable version
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-10 09:44:13 +01:00
Julius Härtl
8cebc35573 Bump version to 1.4.8
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-10 09:28:48 +01:00
Julius Härtl
37b88d0afc Create appstore-build-publish.yml 2022-03-04 13:58:42 +01:00
Julius Härtl
647bcd3167 Merge pull request #3616 from nextcloud/backport/3612/stable21
[stable21] Make insert attachment buttom easy to click
2022-03-02 21:05:05 +01:00
Luka Trovic
30629aaacc fix: make insert attachment buttom easy to click
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-02 16:24:26 +00:00
Nextcloud bot
0fbda02b80 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-19 02:38:04 +00:00
Nextcloud bot
4763a0621c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-17 02:38:55 +00:00
Nextcloud bot
fc7eda5706 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-08 02:38:09 +00:00
Nextcloud bot
2fdf50620e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-06 02:37:03 +00:00
Nextcloud bot
f20a91553f [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-05 02:37:31 +00:00
Nextcloud bot
8283aa67f0 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-01 02:37:29 +00:00
Nextcloud bot
ababa900bb [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-31 02:37:29 +00:00
Nextcloud bot
e147b43eb9 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-24 02:37:14 +00:00
Nextcloud bot
65675cdbde [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-19 02:38:16 +00:00
Julien Veyssier
d32e942908 Merge pull request #3545 from nextcloud/backport/3541/stable21
[stable21] Fix confusion between stackId and boardId in StackService
2022-01-18 17:04:19 +01:00
Julien Veyssier
32a65e856c fix confusion between stackId and boardId in StackService::update()
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
2022-01-18 15:23:29 +00:00
Nextcloud bot
01522e69ea [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-18 02:56:07 +00:00
Nextcloud bot
49d356f04f [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-13 02:37:12 +00:00
Julius Härtl
04e5310643 Merge pull request #3525 from nextcloud/backport/3502/stable21 2022-01-12 08:39:43 +01:00
Nextcloud bot
a293467b59 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-12 02:37:28 +00:00
Luka Trovic
c2546206a3 exclude deleted boards in the selection for target
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-11 08:01:01 +00:00
Nextcloud bot
9c406a34c5 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-11 02:37:43 +00:00
Nextcloud bot
360b4f57f5 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-10 02:37:44 +00:00
Nextcloud bot
460d06fe10 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-08 02:36:44 +00:00
Nextcloud bot
b3b2ee1966 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-06 02:38:53 +00:00
Nextcloud bot
7092dc06ab [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-05 02:38:18 +00:00
Nextcloud bot
c47829a3d9 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-04 02:38:12 +00:00
Nextcloud bot
54479eee20 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-03 02:38:08 +00:00
Nextcloud bot
cdd788a6b8 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-02 02:37:48 +00:00
Nextcloud bot
f5242cd10c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-01 02:37:50 +00:00
Nextcloud bot
f0299486d6 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-31 02:38:07 +00:00
Julius Härtl
38921cade8 Merge pull request #3461 from nextcloud/backport/3459/stable1.4 2021-11-30 16:32:02 +01:00
Julius Härtl
8a3e679c33 Fix cursor generation if no results are found
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-30 12:22:12 +00:00
Jonas
d1b81e697f Merge pull request #3442 from nextcloud/backport/3428/stable1.4
[stable1.4] Allow to download an attachment without navigating to the files app
2021-11-22 19:11:06 +01:00
Julius Härtl
215fcf61bc Allow to download an attachment without navigating to the files app
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-22 17:40:47 +00:00
Julius Härtl
e418373503 Bump version to 1.4.7
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-10 20:42:09 +01:00
Julius Härtl
ca22b0ad2c Bump version to 1.4.6
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-10 16:12:11 +01:00
Julius Härtl
e4cbc694d4 Merge pull request #3408 from nextcloud/backport/3384/stable1.4
[stable1.4] Keep exceptions http response generic
2021-11-05 19:53:35 +01:00
Julius Härtl
f53e51fc4e Keep exceptions http response generic and return the request ID for further tracing
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-05 17:02:30 +00:00
Julius Härtl
dcbbb22dda Merge pull request #3393 from nextcloud/backport/3391/stable1.4 2021-10-27 14:16:23 +02:00
Paweł Kuffel
e85042e1b4 use displayname instead of uid for mentions
Signed-off-by: Paweł Kuffel <pawel@kuffel.io>
2021-10-27 11:59:25 +00:00
Julius Härtl
a720669354 Merge pull request #3379 from nextcloud/backport/3324/stable1.4 2021-10-22 20:24:34 +02:00
Julius Härtl
216b9445d3 Merge pull request #3385 from Artem4590/backport/3323/stable1.4 2021-10-22 20:24:17 +02:00
Artem Lavrukhin
b21faa8501 [stable1.4] Extend drag-and-drop zone in card sidebar
Signed-off-by: Artem Lavrukhin <lavryha4590@gmail.com>
2021-10-21 14:48:19 +03:00
Lera Dmitrieva
1bc28c68a5 Fix menu button position in card modal
Signed-off-by: Lera Dmitrieva <dmit.valerya@yandex.ru>
2021-10-12 10:25:31 +00:00
Julius Härtl
f78f8bfd7f Merge pull request #3367 from nextcloud/backport/3364/stable1.4 2021-10-06 11:40:26 +02:00
Julius Härtl
01bddf029e Fix optional parameter order
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-10-06 09:24:23 +00:00
Julius Härtl
bdead3cdd5 Merge pull request #3360 from nextcloud/enh/stable1.4-paginated-search-for-boards-and-cards 2021-10-05 08:24:10 +02:00
Julien Veyssier
88d164b411 use distinct pagination cursor for cards and boards, use cursor and limit in SearchService::searchBoards()
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
2021-10-04 17:30:45 +02:00
Julius Härtl
1638c3d350 Merge pull request #3359 from nextcloud/backport/stable1.4/2935 2021-10-04 16:25:10 +02:00
Joas Schilling
454d515192 Rich object string parameters for notifications
Signed-off-by: Joas Schilling <coding@schilljs.com>
2021-10-04 14:21:23 +02:00
Julius Härtl
e60219c9df Bump version to 1.4.5
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-09-14 21:10:51 +02:00
Julius Härtl
5c8c73f2ac Merge pull request #3318 from nextcloud/backport/3316/stable1.4
[stable1.4] Additional check for stacks
2021-09-14 21:08:34 +02:00
Julius Härtl
fad63ac6f5 Additional check for stacks
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-09-14 19:01:30 +00:00
Julius Härtl
31eb8d6698 Bump version to 1.4.4
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-09-09 14:25:50 +02:00
Julius Härtl
40967a4ee6 Merge pull request #3307 from nextcloud/backport/3299/stable1.4 2021-09-08 18:42:20 +02:00
Julius Härtl
bfe9b05d69 Return false instead of throwing when getting calendar integration setting
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-09-08 18:31:16 +02:00
Julius Härtl
82e3400162 Merge pull request #3304 from nextcloud/backport/3298/stable1.4
[stable1.4] Delete file shares through attachments API
2021-09-08 18:14:31 +02:00
Julius Härtl
a886b4ee78 Delete file shares through attachments API
Previously the file was deleted in the file structure of the user is not
expected as the file might not only be related to the card.

Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-09-08 17:58:01 +02:00
Julius Härtl
618fb50618 Merge pull request #3301 from nextcloud/backport/3294/stable1.4
[stable1.4] Fix print style issues
2021-09-07 13:14:53 +02:00
Michael Weimann
f7aae7912d fix print style issues
Signed-off-by: Michael Weimann <mail@michael-weimann.eu>
2021-09-06 13:58:44 +00:00
Julius Härtl
2976604b7b Merge pull request #3227 from nextcloud/backport/3217/stable1.4
[stable1.4] Additional circle level check
2021-08-04 18:41:06 +02:00
Julius Härtl
bbe482586b Check circle level
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-08-03 18:20:18 +02:00
Julius Härtl
ff61238487 Pin CI to mariadb 10.5
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-08-03 18:18:52 +02:00
Julius Härtl
9e2dcb686f Bump version to 1.4.3
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-07-09 12:05:19 +02:00
Julius Härtl
fcc96ca98d Merge pull request #3169 from nextcloud/backport/3161/stable1.4
[stable1.4] Reduce duplicate queries when fetching user boards an permissions
2021-07-06 07:54:18 +02:00
Julius Härtl
a43cee8a5d Reduce duplicate queries when fetching user boards an permissions
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-07-05 18:28:01 +00:00
Julius Härtl
f4ccc506af Merge pull request #3164 from nextcloud/backport/3151/stable1.4
[stable1.4] Always log generic exceptions
2021-07-05 16:21:19 +02:00
Julius Härtl
fee49f3699 Always log generic exceptions
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-07-02 15:55:32 +00:00
Julius Härtl
d43c7a48cc Merge pull request #3153 from nextcloud/backport/3152/stable1.4
[stable1.4] Only offer stack creation in emptycontent with proper permissions
2021-06-25 15:56:36 +02:00
Julius Härtl
c0fad295b5 Only offer stack creation in emptycontent with proper permissions
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-06-25 13:38:48 +00:00
Julius Härtl
cb1314f067 Merge pull request #3143 from nextcloud/backport/3142/stable1.4
[stable1.4] Always pass user id in share provider
2021-06-21 13:31:49 +02:00
Julius Härtl
ba68e4c2f7 Always pass user id in share provider
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-06-21 07:34:30 +00:00
Julius Härtl
bd8fd6a66b Bump version to 1.4.2
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-05-03 14:10:54 +02:00
Julius Härtl
0eba8d0840 Merge pull request #3040 from nextcloud/backport/3038/stable1.4
[stable1.4] Get attachment from the user node instead of the share source
2021-05-03 09:02:33 -01:00
Julius Härtl
8fc95dc40d Merge pull request #3039 from nextcloud/backport/3037/stable1.4
[stable1.4] Catch any error during circle detail fetching
2021-05-03 09:02:26 -01:00
Julius Härtl
ecd3e25588 Get attachment from the user node instead of the share source
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-05-03 09:44:20 +00:00
Julius Härtl
914f912612 Catch any error during circle detail fetching
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-05-03 11:43:43 +02:00
Christoph Wurst
e68f723095 Merge pull request #3031 from nextcloud/backport/3016/stable1.4
[stable1.4] Allow searching for filters without a query to match all that have a given filter set
2021-04-30 15:30:21 +02:00
Julius Härtl
5f71be2e7f Add test case for special characters
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-30 13:13:39 +00:00
Julius Härtl
bc2a72f035 Catch canceled requests and show better loading indication
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-30 13:13:39 +00:00
Julius Härtl
cf4be82827 Fix handling of quotes in queries
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-30 13:13:39 +00:00
Julius Härtl
23580705aa Allow searching for filters without a query to match all that have a given filter set
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-30 13:13:39 +00:00
Christoph Wurst
65c8c394a8 Merge pull request #3030 from nextcloud/backport/3014
Proper error handling when fetching comments fails
2021-04-30 15:12:10 +02:00
Julius Härtl
422788a6a3 Show comment counter and highlight if unread comments are available
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-30 10:39:22 +02:00
Julius Härtl
2d5e29de5d Allow to cancel repies and adapt comment ui to talk
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-30 10:39:22 +02:00
Julius Härtl
2a307b92a7 Wrap lines properly in comment text
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-30 10:38:09 +02:00
Julius Härtl
2d8dbc70ad Proper error handling when fetching comments fails
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-30 10:37:54 +02:00
Julius Härtl
cfee259b38 Bump version to 1.4.1
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-20 11:34:48 +02:00
Julius Härtl
f94cdb3ebb Merge pull request #2994 from nextcloud/backport/2950/stable1.4 2021-04-20 07:44:48 -01:00
Julius Härtl
1ed50fdca6 Fix tests
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-20 10:17:39 +02:00
Julius Härtl
56e460004f Filter out current user when emitting share notifications to groups
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-20 10:17:39 +02:00
Julius Härtl
a95f78d188 Remove notification on unshare/unassign and add type hints
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-20 10:17:38 +02:00
Julius Härtl
df09a9a7b2 Remove app code check
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-20 10:16:53 +02:00
Julius Härtl
990ee2aef9 Merge pull request #3008 from nextcloud/backport/3005/stable1.4
[stable1.4] Do not query the lookupserver when looking for sharees
2021-04-19 10:38:34 -01:00
Julius Härtl
486ecd12db Merge pull request #3006 from nextcloud/backport/3003/stable1.4
[stable1.4] Only import debounce
2021-04-19 09:02:42 -01:00
Julius Härtl
c9cdd7bb11 Do not query the lookupserver when looking for sharees
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-19 10:02:05 +00:00
Julius Härtl
2c753fd084 Only import debounce
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-19 09:22:50 +00:00
Julius Härtl
79d2d2f3f5 Merge pull request #2990 from nextcloud/backport/2989/stable1.4 2021-04-16 13:26:08 -01:00
Julius Härtl
24d9b55bfc Cast column when comparing comment object_id with the card id
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-16 13:54:19 +00:00
Julius Härtl
28cd9fcf77 Add test for unified comments search
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-16 13:54:19 +00:00
Christoph Wurst
d8a36f0602 Merge pull request #2984 from nextcloud/backport/2983/stable1.4
[stable1.4] Fix codemirror description width
2021-04-14 20:39:03 +02:00
Julius Härtl
de06033dcd Fix codemirror description width
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-14 18:08:19 +00:00
124 changed files with 18128 additions and 28779 deletions

View File

@@ -1,12 +1,8 @@
module.exports = {
extends: [
'@nextcloud',
'@nextcloud'
],
rules: {
'jsdoc/require-param-description': ['off'],
'jsdoc/require-param-type': ['off'],
'jsdoc/check-param-names': ['off'],
'jsdoc/no-undefined-types': ['off'],
'jsdoc/require-property-description' : ['off']
},
'valid-jsdoc': ['off'],
}
}

View File

@@ -39,13 +39,3 @@ updates:
versions:
- "< 16"
- ">= 15.a"
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
day: saturday
time: "03:00"
timezone: Europe/Paris
open-pull-requests-limit: 10
reviewers:
- juliushaertl

25
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- "1. to develop"
- "2. developing"
- "3. to review"
- "discussion"
- "bounty"
- "bug"
- "enhancement"
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.

View File

@@ -9,18 +9,16 @@ jobs:
strategy:
matrix:
node-version: [14.x]
node-version: [12.x]
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7
run: npm i -g npm@7
- name: Setup PHP
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@v1
with:
php-version: '7.4'
tools: composer

View File

@@ -1,46 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
name: Rebase command
on:
issue_comment:
types: created
jobs:
rebase:
runs-on: ubuntu-latest
# On pull requests and if the comment starts with `/rebase`
if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/rebase')
steps:
- name: Add reaction on start
uses: peter-evans/create-or-update-comment@v1
with:
token: ${{ secrets.COMMAND_BOT_PAT }}
repository: ${{ github.event.repository.full_name }}
comment-id: ${{ github.event.comment.id }}
reaction-type: "+1"
- name: Checkout the latest code
uses: actions/checkout@v2.4.0
with:
fetch-depth: 0
token: ${{ secrets.COMMAND_BOT_PAT }}
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.5
env:
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
- name: Add reaction on failure
uses: peter-evans/create-or-update-comment@v1
if: failure()
with:
token: ${{ secrets.COMMAND_BOT_PAT }}
repository: ${{ github.event.repository.full_name }}
comment-id: ${{ github.event.comment.id }}
reaction-type: "-1"

View File

@@ -1,29 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
name: Dependabot
on:
pull_request_target:
branches:
- master
- stable*
jobs:
auto-approve-merge:
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
# Github actions bot approve
- uses: hmarr/auto-approve-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
# Nextcloud bot approve and merge request
- uses: ahmadnassri/action-dependabot-auto-merge@v2
with:
target: patch
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}

View File

@@ -1,20 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
name: Pull request checks
on: pull_request
jobs:
commit-message-check:
name: Block fixup and squash commits
runs-on: ubuntu-latest
steps:
- name: Run check
uses: xt0rted/block-autosquash-commits-action@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -19,7 +19,7 @@ jobs:
matrix:
php-versions: ['7.4']
databases: ['sqlite', 'mysql', 'pgsql']
server-versions: ['stable23']
server-versions: ['stable21']
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
@@ -43,7 +43,7 @@ jobs:
steps:
- name: Checkout server
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2
with:
repository: nextcloud/server
ref: ${{ matrix.server-versions }}
@@ -56,12 +56,12 @@ jobs:
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
- name: Checkout app
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2
with:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
tools: phpunit

View File

@@ -13,13 +13,13 @@ jobs:
strategy:
matrix:
php-versions: ['7.3', '7.4']
php-versions: ['7.2', '7.3', '7.4']
name: php${{ matrix.php-versions }} lint
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v2
- name: Set up php${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@v1
with:
php-version: ${{ matrix.php-versions }}
coverage: none
@@ -31,9 +31,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2.4.0
uses: actions/checkout@master
- name: Set up php
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@master
with:
php-version: 7.4
coverage: none
@@ -47,16 +47,14 @@ jobs:
strategy:
matrix:
node-version: [14.x]
node-version: [12.x]
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v2
- name: Use node ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7
run: npm i -g npm@7
- name: Install dependencies
run: npm ci
- name: ESLint
@@ -67,19 +65,16 @@ jobs:
strategy:
matrix:
node-version: [14.x]
node-versions: [12.x]
name: stylelint node${{ matrix.node-version }}
name: stylelint node${{ matrix.node-versions }}
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v2
- name: Set up node ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
- name: Set up node ${{ matrix.node-versions }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7
run: npm i -g npm@7
node-versions: ${{ matrix.node-versions }}
- name: Install dependencies
run: npm ci

View File

@@ -14,18 +14,16 @@ jobs:
strategy:
matrix:
node-version: [14.x]
node-version: [12.x]
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7
run: npm i -g npm@7
- name: Setup PHP
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@v1
with:
php-version: '7.4'
tools: composer

View File

@@ -9,16 +9,14 @@ jobs:
strategy:
matrix:
node-version: [14.x]
node-version: [12.x]
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7
run: npm i -g npm@7
- name: install dependencies
run: |
npm ci

View File

@@ -20,7 +20,7 @@ jobs:
matrix:
php-versions: ['7.3', '7.4']
databases: ['sqlite', 'mysql', 'pgsql']
server-versions: ['stable23']
server-versions: ['stable21']
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
@@ -44,7 +44,7 @@ jobs:
steps:
- name: Checkout server
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2
with:
repository: nextcloud/server
ref: ${{ matrix.server-versions }}
@@ -57,12 +57,12 @@ jobs:
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
- name: Checkout app
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2
with:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
tools: phpunit

View File

@@ -12,13 +12,13 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
ocp-version: [ 'dev-stable23' ]
ocp-version: [ 'dev-stable21' ]
name: Nextcloud ${{ matrix.ocp-version }}
steps:
- name: Checkout
uses: actions/checkout@v2.4.0
uses: actions/checkout@master
- name: Set up php
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@master
with:
php-version: 7.4
tools: composer:v1

View File

@@ -1,81 +1,79 @@
# Changelog
All notable changes to this project will be documented in this file.
## 1.6.2
### Added
- Transfer ownership @juliushaertl [#3664](https://github.com/nextcloud/deck/pull/3664)
## 1.4.8
### Fixed
- 🐛 Fix missing files sidebar [#3641](https://github.com/nextcloud/deck/pull/3641)
- Add a missing translation - not found in transifex [#3706](https://github.com/nextcloud/deck/pull/3706)
- Fix: Check all circle shares for permissions [#3717](https://github.com/nextcloud/deck/pull/3717)
- Sort boards non case sensitive [#3663](https://github.com/nextcloud/deck/pull/3663)
- Use explicit cast to make use of index @juliushaertl [#3497](https://github.com/nextcloud/deck/pull/3497)
- Fix paramter replacements when creating deck cards from talk messages @juliushaertl [#3741](https://github.com/nextcloud/deck/pull/3741)
- Fix hidden attachment icon on archived cards [#3734](https://github.com/nextcloud/deck/pull/3734)
- Fix text selection in dark mode and modal view [#3766](https://github.com/nextcloud/deck/pull/3766)
- Fix cursor generation if no results are found [#3461](https://api.github.com/repos/nextcloud/deck/pulls/3461)
- Allow to download an attachment without navigating to the files app [#3442](https://api.github.com/repos/nextcloud/deck/pulls/3442)
- Exclude deleted boards in the selection for target [#3525](https://api.github.com/repos/nextcloud/deck/pulls/3525)
- Make insert attachment buttom easy to click [#3616](https://api.github.com/repos/nextcloud/deck/pulls/3616)
- Fix confusion between stackId and boardId in StackService [#3545](https://api.github.com/repos/nextcloud/deck/pulls/3545)
### Other
- Properly check for the stack AND setting board permissions [#3713](https://github.com/nextcloud/deck/pull/3713)
- Add missing indices [#3755](https://github.com/nextcloud/deck/pull/3755)
## 1.6.1
## 1.4.7
### Fixed
- Exclude deleted boards in the selection for target [#3523](https://api.github.com/repos/nextcloud/deck/pulls/3523)
- CardApiController: Fix order of optional parameters [#3520](https://api.github.com/repos/nextcloud/deck/pulls/3520)
- Fix cursor generation if no results are found [#3462](https://api.github.com/repos/nextcloud/deck/pulls/3462)
- Fix CalDAV blocking and modernize circles API usage [#3526](https://api.github.com/repos/nextcloud/deck/pulls/3526)
- Fix overview card listing [#3463](https://api.github.com/repos/nextcloud/deck/pulls/3463)
- Generate fixed link for activity emails [#3626](https://api.github.com/repos/nextcloud/deck/pulls/3626)
- return the selector for collections [#3618](https://api.github.com/repos/nextcloud/deck/pulls/3618)
- Fix confusion between stackId and boardId in StackService [#3543](https://api.github.com/repos/nextcloud/deck/pulls/3543)
- Fix talk integration [#3537](https://api.github.com/repos/nextcloud/deck/pulls/3537)
- Make insert attachment buttom easy to click [#3614](https://api.github.com/repos/nextcloud/deck/pulls/3614)
- Fix release asset build
## 1.6.0
### Added
- #3449 Cache most frequent queries
- #3177 Use async import for vue component on collections entrypoint @juliushaertl
- #2791 Open description links in new tab @fm-sys
- #3344 Improve combined search @eneiluj
- #3362 Improve search performance @eneiluj
- #2710 Due date shortcuts in the datepicker @jakobroehrl
## 1.4.6
### Fixed
- #3446 Switch to QBMapper in BoardMapper
- #3433 Fix event name for updating the description
- #3463 Fix overview card listing
- #3440 Allow to download an attachment without navigating to the files app
- #3462 Fix cursor generation if no results are found
- #3161 Reduce duplicate queries when fetching user boards an permissions @juliushaertl
- #3151 Always log generic exceptions @juliushaertl
- #3217 Move circle checks to a unified service and improve member checks @juliushaertl
- #3225 Check for null value to avoid TypeError in the group manager @juliushaertl
- #3263 Defer obtaining the user session in the config service @juliushaertl
- #3294 Fix print style issues @weeman1337
- #3299 Return false instead of throwing when getting calendar setting @juliushaertl
- #3298 Delete file shares through attachments API @juliushaertl
- #3343 Fix search pagination cursor @eneiluj
- #3326 add autofocus on board edit @weeman1337
- #3323 Extend drag-and-drop zone in card sidebar @old-green-frog
- #3364 Fix optional parameter order @juliushaertl
- #3324 Fix menu button position in card modal @valerydmitrieva
- #3391 Use displayname instead of uid for mentions (reopened against master) @kffl
- #3316 Additional check for stacks @juliushaertl
- #3357 Revert "Fix search pagination cursor" @juliushaertl
- #3327 Do not show both bullets and checkboxes for checklists @Themanwhosmellslikesugar
- #3375 Show absolute dates when printing @weeman1337
- #3376 Print assignee names @weeman1337
- #3384 Keep exceptions http response generic @juliushaertl
- #3379 Fix menu button position in card modal
- #3360 Improve combined search @eneiluj
- #3367 Fix optional parameter order
- #3393 Use displayname instead of uid for mentions
- #3359 Rich object string parameters for notifications @juliushaertl
- #3385 Extend drag-and-drop zone in card sidebar @Artem4590
- #3408 Keep exceptions http response generic
## 1.4.5
### Fixed
- #3318 Additional check for stacks
## 1.4.4
### Fixed
- #3301 Fix print style issues
- #3307 Return false instead of throwing when getting calendar setting
- #3227 Additional circle level check
- #3304 Delete file shares through attachments API
## 1.4.3 - 2021-07-09
### Fixed
* [#3143](https://github.com/nextcloud/deck/pull/3143) Always pass user id in share provider
* [#3153](https://github.com/nextcloud/deck/pull/3153) Only offer stack creation in emptycontent with proper permissions
* [#3164](https://github.com/nextcloud/deck/pull/3164) Always log generic exceptions
* [#3169](https://github.com/nextcloud/deck/pull/3169) Reduce duplicate queries when fetching user boards an permissions
## 1.4.2 - 2021-05-03
### Fixed
* [#3030](https://github.com/nextcloud/deck/pull/3030) Proper error handling when fetching comments fails
* [#3031](https://github.com/nextcloud/deck/pull/3031) Allow searching for filters without a query to match all that have a given filter set
* [#3039](https://github.com/nextcloud/deck/pull/3039) Catch any error during circle detail fetching
* [#3040](https://github.com/nextcloud/deck/pull/3040) Get attachment from the user node instead of the share source
## 1.4.1 - 2021-04-20
### Fixed
* [#2984](https://github.com/nextcloud/deck/pull/2984) Fix codemirror description width
* [#2990](https://github.com/nextcloud/deck/pull/2990) Fix unified comments search with postgres
* [#2994](https://github.com/nextcloud/deck/pull/2994) Remove notification on unshare and add type hints
* [#3006](https://github.com/nextcloud/deck/pull/3006) Only import debounce
* [#3008](https://github.com/nextcloud/deck/pull/3008) Do not query the lookupserver when looking for sharees
## 1.4.0 - 2021-04-13

View File

@@ -50,7 +50,8 @@ ifeq (, $(shell which phpunit 2> /dev/null))
php $(build_tools_directory)/phpunit.phar -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
php $(build_tools_directory)/phpunit.phar -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
else
phpunit -c tests/phpunit.integration.xml --testsuite=integration-database --coverage-clover build/php-integration.coverage.xml
phpunit -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
endif
test-integration:

View File

@@ -15,18 +15,12 @@ Deck is a kanban style organization tool aimed at personal planning and project
- Keep track of changes in the activity stream
- Get your project organized
![Deck - Manage cards on your board](http://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-2.png)
### Mobile apps
- [Nextcloud Deck app for Android](https://github.com/stefan-niedermann/nextcloud-deck) - It is available in [F-Droid](https://f-droid.org/de/packages/it.niedermann.nextcloud.deck/) and the [Google Play Store](https://play.google.com/store/apps/details?id=it.niedermann.nextcloud.deck.play)
- The [Nextcloud Deck app for Android](https://github.com/stefan-niedermann/nextcloud-deck) is available in the [Google Play Store](https://play.google.com/store/apps/details?id=it.niedermann.nextcloud.deck.play)
### 3rd-Party Integrations
![Deck - Manage cards on your board](http://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-2.png)
- [trello-to-deck](https://github.com/maxammann/trello-to-deck) - Migrates cards from Trello
- [mail2deck](https://github.com/newroco/mail2deck) - Provides an "email in" solution
- [A-deck](https://github.com/leoossa/A-deck) - Chrome Extension that allows to create new card in selected stack based on current tab
-
## Installation/Update
This app is supposed to work on the two latest Nextcloud versions.

View File

@@ -16,7 +16,7 @@
- 🚀 Get your project organized
</description>
<version>1.6.2</version>
<version>1.4.8</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Deck</namespace>
@@ -35,7 +35,7 @@
<database min-version="9.4">pgsql</database>
<database>sqlite</database>
<database min-version="5.5">mysql</database>
<nextcloud min-version="23" max-version="23"/>
<nextcloud min-version="21" max-version="21"/>
</dependencies>
<background-jobs>
<job>OCA\Deck\Cron\DeleteCron</job>
@@ -44,7 +44,6 @@
</background-jobs>
<commands>
<command>OCA\Deck\Command\UserExport</command>
<command>OCA\Deck\Command\TransferOwnership</command>
</commands>
<activity>
<settings>

View File

@@ -25,7 +25,6 @@
return [
'routes' => [
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
['name' => 'page#redirectToCard', 'url' => '/card/{cardId}', 'verb' => 'GET'],
// boards
['name' => 'board#index', 'url' => '/boards', 'verb' => 'GET'],
@@ -39,7 +38,6 @@ return [
['name' => 'board#updateAcl', 'url' => '/boards/{boardId}/acl/{aclId}', 'verb' => 'PUT'],
['name' => 'board#deleteAcl', 'url' => '/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
['name' => 'board#clone', 'url' => '/boards/{boardId}/clone', 'verb' => 'POST'],
['name' => 'board#transferOwner', 'url' => '/boards/{boardId}/transferOwner', 'verb' => 'PUT'],
// stacks
['name' => 'stack#index', 'url' => '/stacks/{boardId}', 'verb' => 'GET'],

View File

@@ -1,3 +1,11 @@
const babelConfig = require('@nextcloud/babel-config')
module.exports = babelConfig
module.exports = {
plugins: ['@babel/plugin-syntax-dynamic-import'],
presets: [
[
'@babel/preset-env',
{
modules: false
}
]
]
}

View File

@@ -13,7 +13,7 @@
},
"require-dev": {
"roave/security-advisories": "dev-master",
"christophwurst/nextcloud": "^22@dev",
"christophwurst/nextcloud": "^21@dev",
"phpunit/phpunit": "^8",
"nextcloud/coding-standard": "^0.5.0",
"symfony/event-dispatcher": "^4.0",
@@ -21,9 +21,6 @@
"php-parallel-lint/php-parallel-lint": "^1.2"
},
"config": {
"platform": {
"php": "7.3"
},
"optimize-autoloader": true,
"classmap-authoritative": true
},

1321
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,6 @@ Overall, Deck is easy to use. You can create boards, add users, share the Deck,
3. [Handle cards options](#3-handle-cards-options)
4. [Archive old tasks](#4-archive-old-tasks)
5. [Manage your board](#5-manage-your-board)
6. [New owner for the deck entities](#8-new-owner-for-the-deck-entities)
### 1. Create my first board
In this example, we're going to create a board and share it with an other nextcloud user.
@@ -91,22 +90,4 @@ For example the search `project tag:ToDo assigned:alice assigned:bob` will retur
Other text tokens will be used to perform a case-insensitive search on the card title and description
In addition, quotes can be used to pass a query with spaces, e.g. `"Exact match with spaces"` or `title:"My card"`.
### 8. New owner for the deck entities
You can transfer ownership of boards, cards, etc to a new user, using `occ` command `deck:transfer-ownership`
```bash
php occ deck:transfer-ownership previousOwner newOwner
```
The transfer will preserve card details linked to the old owner, which can also be remapped by using the `--remap` option on the occ command.
```bash
php occ deck:transfer-ownership --remap previousOwner newOwner
```
Individual boards can be transferred by adding the id of the board to the command:
```bash
php occ deck:transfer-ownership previousOwner newOwner 123
```
In addition wuotes can be used to pass a query with spaces, e.g. `"Exact match with spaces"` or `title:"My card"`.

View File

@@ -173,8 +173,11 @@ OC.L10N.register(
"Owner" : "Besitzer",
"Delete" : "Löschen",
"Failed to create share with {displayName}" : "Fehler beim Erstellen der Freigabe mit dem Namen {displayName}",
"Are you sure you want to transfer the board {title} to {user}?" : "Möchtest Du wirklich das Board {title} an {user} übertragen?",
"Transfer the board." : "Board übertragen",
"Transfer" : "Übertragen",
"The board has been transferred to {user}" : "Das Board wurde an {user} übertragen",
"Failed to transfer the board to {user}" : "Das Board konnte nicht an {user} übertragen werden",
"Add a new list" : "Eine neue Liste hinzufügen",
"Archive all cards" : "Alle Karten archivieren",
"Delete list" : "Liste löschen",

View File

@@ -171,8 +171,11 @@
"Owner" : "Besitzer",
"Delete" : "Löschen",
"Failed to create share with {displayName}" : "Fehler beim Erstellen der Freigabe mit dem Namen {displayName}",
"Are you sure you want to transfer the board {title} to {user}?" : "Möchtest Du wirklich das Board {title} an {user} übertragen?",
"Transfer the board." : "Board übertragen",
"Transfer" : "Übertragen",
"The board has been transferred to {user}" : "Das Board wurde an {user} übertragen",
"Failed to transfer the board to {user}" : "Das Board konnte nicht an {user} übertragen werden",
"Add a new list" : "Eine neue Liste hinzufügen",
"Archive all cards" : "Alle Karten archivieren",
"Delete list" : "Liste löschen",

View File

@@ -173,8 +173,11 @@ OC.L10N.register(
"Owner" : "Besitzer",
"Delete" : "Löschen",
"Failed to create share with {displayName}" : "Fehler beim Erstellen der Freigabe mit dem Namen {displayName}",
"Are you sure you want to transfer the board {title} to {user}?" : "Möchten Sie wirklich das Board {title} auf {user} übertragen?",
"Transfer the board." : "Board übertragen.",
"Transfer" : "Übertragen",
"The board has been transferred to {user}" : "Das Board wurde auf {user} übertragen",
"Failed to transfer the board to {user}" : "Das Board konnte nicht auf {user} übertragen werden",
"Add a new list" : "Eine neue Liste hinzufügen",
"Archive all cards" : "Alle Karten archivieren",
"Delete list" : "Liste löschen",

View File

@@ -171,8 +171,11 @@
"Owner" : "Besitzer",
"Delete" : "Löschen",
"Failed to create share with {displayName}" : "Fehler beim Erstellen der Freigabe mit dem Namen {displayName}",
"Are you sure you want to transfer the board {title} to {user}?" : "Möchten Sie wirklich das Board {title} auf {user} übertragen?",
"Transfer the board." : "Board übertragen.",
"Transfer" : "Übertragen",
"The board has been transferred to {user}" : "Das Board wurde auf {user} übertragen",
"Failed to transfer the board to {user}" : "Das Board konnte nicht auf {user} übertragen werden",
"Add a new list" : "Eine neue Liste hinzufügen",
"Archive all cards" : "Alle Karten archivieren",
"Delete list" : "Liste löschen",

View File

@@ -173,8 +173,11 @@ OC.L10N.register(
"Owner" : "Właściciel",
"Delete" : "Usuń",
"Failed to create share with {displayName}" : "Nie udało się utworzyć udostępnienia dla {displayName}",
"Are you sure you want to transfer the board {title} to {user}?" : "Czy na pewno chcesz przenieść tablicę {title} do {user}?",
"Transfer the board." : "Przeniesienie tablicy.",
"Transfer" : "Przenieś",
"The board has been transferred to {user}" : "Tablica została przeniesiona do {user}",
"Failed to transfer the board to {user}" : "Nie udało się przenieść tablicy do {user}",
"Add a new list" : "Dodaj nową listę",
"Archive all cards" : "Zarchiwizuj wszystkie karty",
"Delete list" : "Usuń listę",

View File

@@ -171,8 +171,11 @@
"Owner" : "Właściciel",
"Delete" : "Usuń",
"Failed to create share with {displayName}" : "Nie udało się utworzyć udostępnienia dla {displayName}",
"Are you sure you want to transfer the board {title} to {user}?" : "Czy na pewno chcesz przenieść tablicę {title} do {user}?",
"Transfer the board." : "Przeniesienie tablicy.",
"Transfer" : "Przenieś",
"The board has been transferred to {user}" : "Tablica została przeniesiona do {user}",
"Failed to transfer the board to {user}" : "Nie udało się przenieść tablicy do {user}",
"Add a new list" : "Dodaj nową listę",
"Archive all cards" : "Zarchiwizuj wszystkie karty",
"Delete list" : "Usuń listę",

View File

@@ -173,8 +173,11 @@ OC.L10N.register(
"Owner" : "Sahibi",
"Delete" : "Sil",
"Failed to create share with {displayName}" : "{displayName} ile paylaşılamadı",
"Are you sure you want to transfer the board {title} to {user}?" : "{title} panosunu {user} kullanıcısına aktarmak istediğinize emin misiniz?",
"Transfer the board." : "Panoyu aktar.",
"Transfer" : "Aktar",
"The board has been transferred to {user}" : "Pano {user} kullanıcısına aktarıldı",
"Failed to transfer the board to {user}" : "Pano {user} kullanıcısına aktarılamadı",
"Add a new list" : "Yeni liste ekle",
"Archive all cards" : "Tüm kartları arşivle",
"Delete list" : "Listeyi sil",

View File

@@ -171,8 +171,11 @@
"Owner" : "Sahibi",
"Delete" : "Sil",
"Failed to create share with {displayName}" : "{displayName} ile paylaşılamadı",
"Are you sure you want to transfer the board {title} to {user}?" : "{title} panosunu {user} kullanıcısına aktarmak istediğinize emin misiniz?",
"Transfer the board." : "Panoyu aktar.",
"Transfer" : "Aktar",
"The board has been transferred to {user}" : "Pano {user} kullanıcısına aktarıldı",
"Failed to transfer the board to {user}" : "Pano {user} kullanıcısına aktarılamadı",
"Add a new list" : "Yeni liste ekle",
"Archive all cards" : "Tüm kartları arşivle",
"Delete list" : "Listeyi sil",

View File

@@ -173,8 +173,11 @@ OC.L10N.register(
"Owner" : "所有者",
"Delete" : "刪除",
"Failed to create share with {displayName}" : "無法為 {displayName} 創建分享",
"Are you sure you want to transfer the board {title} to {user}?" : "您確定要將面板 {title} 轉讓給 {user} 嗎?",
"Transfer the board." : "轉移面板。",
"Transfer" : "轉移",
"The board has been transferred to {user}" : "面板已轉讓給 {user}",
"Failed to transfer the board to {user}" : "未能將面板轉移給 {user}",
"Add a new list" : "添加一張新清單",
"Archive all cards" : "封存所有卡片",
"Delete list" : "刪除清單",

View File

@@ -171,8 +171,11 @@
"Owner" : "所有者",
"Delete" : "刪除",
"Failed to create share with {displayName}" : "無法為 {displayName} 創建分享",
"Are you sure you want to transfer the board {title} to {user}?" : "您確定要將面板 {title} 轉讓給 {user} 嗎?",
"Transfer the board." : "轉移面板。",
"Transfer" : "轉移",
"The board has been transferred to {user}" : "面板已轉讓給 {user}",
"Failed to transfer the board to {user}" : "未能將面板轉移給 {user}",
"Add a new list" : "添加一張新清單",
"Archive all cards" : "封存所有卡片",
"Delete list" : "刪除清單",

View File

@@ -35,7 +35,6 @@ use OCP\IConfig;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\L10N\IFactory;
use OCA\Deck\Service\CardService;
class DeckProvider implements IProvider {
@@ -53,10 +52,8 @@ class DeckProvider implements IProvider {
private $l10nFactory;
/** @var IConfig */
private $config;
/** @var CardService */
private $cardService;
public function __construct(IURLGenerator $urlGenerator, ActivityManager $activityManager, IUserManager $userManager, ICommentsManager $commentsManager, IFactory $l10n, IConfig $config, $userId, CardService $cardService) {
public function __construct(IURLGenerator $urlGenerator, ActivityManager $activityManager, IUserManager $userManager, ICommentsManager $commentsManager, IFactory $l10n, IConfig $config, $userId) {
$this->userId = $userId;
$this->urlGenerator = $urlGenerator;
$this->activityManager = $activityManager;
@@ -64,7 +61,6 @@ class DeckProvider implements IProvider {
$this->userManager = $userManager;
$this->l10nFactory = $l10n;
$this->config = $config;
$this->cardService = $cardService;
}
/**
@@ -135,7 +131,7 @@ class DeckProvider implements IProvider {
if (array_key_exists('board', $subjectParams)) {
$archivedParam = $subjectParams['card']['archived'] ? 'archived/' : '';
$card['link'] = $this->cardService->getRedirectUrlForCard($event->getObjectId());
$card['link'] = $this->deckUrl('/board/' . $subjectParams['board']['id'] . '/' . $archivedParam . 'card/' . $event->getObjectId());
}
$params['card'] = $card;
}

View File

@@ -207,7 +207,7 @@ class Application extends App implements IBootstrap {
// Talk integration has its own entrypoint which already includes collections handling
return;
}
Util::addScript('deck', 'deck-collections');
Util::addScript('deck', 'collections');
});
}
}

View File

@@ -100,9 +100,6 @@ class ResourceProvider implements IProvider {
if ($board->getOwner() === $user->getUID()) {
return true;
}
if ($board->getAcl() === null) {
return false;
}
return $this->permissionService->userCan($board->getAcl(), Acl::PERMISSION_READ, $user->getUID());
}

View File

@@ -127,9 +127,6 @@ class ResourceProviderCard implements IProvider {
if ($board->getOwner() === $user->getUID()) {
return true;
}
if ($board->getAcl() === null) {
return false;
}
return $this->permissionService->userCan($board->getAcl(), Acl::PERMISSION_READ, $user->getUID());
}

View File

@@ -1,105 +0,0 @@
<?php
namespace OCA\Deck\Command;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Service\BoardService;
use OCA\Deck\Service\PermissionService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
final class TransferOwnership extends Command {
protected $boardService;
protected $boardMapper;
protected $permissionService;
protected $questionHelper;
public function __construct(BoardService $boardService, BoardMapper $boardMapper, PermissionService $permissionService, QuestionHelper $questionHelper) {
parent::__construct();
$this->boardService = $boardService;
$this->boardMapper = $boardMapper;
$this->permissionService = $permissionService;
$this->questionHelper = $questionHelper;
}
protected function configure() {
$this
->setName('deck:transfer-ownership')
->setDescription('Change owner of deck boards')
->addArgument(
'owner',
InputArgument::REQUIRED,
'Owner uid'
)
->addArgument(
'newOwner',
InputArgument::REQUIRED,
'New owner uid'
)
->addArgument(
'boardId',
InputArgument::OPTIONAL,
'Single board ID'
)
->addOption(
'remap',
'r',
InputOption::VALUE_NONE,
'Reassign card details of the old owner to the new one'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$owner = $input->getArgument('owner');
$newOwner = $input->getArgument('newOwner');
$boardId = $input->getArgument('boardId');
$remapAssignment = $input->getOption('remap');
$this->boardService->setUserId($owner);
$this->permissionService->setUserId($owner);
try {
$board = $boardId ? $this->boardMapper->find($boardId) : null;
} catch (\Exception $e) {
$output->writeln("Could not find a board for the provided id.");
return 1;
}
if ($boardId !== null && $board->getOwner() !== $owner) {
$output->writeln("$owner is not the owner of the board $boardId (" . $board->getTitle() . ")");
return 1;
}
if ($boardId) {
$output->writeln("Transfer board " . $board->getTitle() . " from ". $board->getOwner() ." to $newOwner");
} else {
$output->writeln("Transfer all boards from $owner to $newOwner");
}
$question = new ConfirmationQuestion('Do you really want to continue? (y/n) ', false);
if (!$this->questionHelper->ask($input, $output, $question)) {
return 1;
}
if ($boardId) {
$this->boardService->transferBoardOwnership($boardId, $newOwner, $remapAssignment);
$output->writeln("<info>Board " . $board->getTitle() . " from ". $board->getOwner() ." transferred to $newOwner completed</info>");
return 0;
}
foreach ($this->boardService->transferOwnership($owner, $newOwner, $remapAssignment) as $board) {
$output->writeln(" - " . $board->getTitle() . " transferred");
}
$output->writeln("<info>All boards from $owner to $newOwner transferred</info>");
return 0;
}
}

View File

@@ -24,12 +24,9 @@
namespace OCA\Deck\Controller;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\Board;
use OCA\Deck\Service\BoardService;
use OCA\Deck\Service\PermissionService;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
class BoardController extends ApiController {
@@ -153,20 +150,9 @@ class BoardController extends ApiController {
/**
* @NoAdminRequired
* @param $boardId
* @return Board
* @return \OCP\Deck\DB\Board
*/
public function clone($boardId) {
return $this->boardService->clone($boardId, $this->userId);
}
/**
* @NoAdminRequired
*/
public function transferOwner(int $boardId, string $newOwner): DataResponse {
if ($this->permissionService->userIsBoardOwner($boardId, $this->userId)) {
return new DataResponse($this->boardService->transferBoardOwnership($boardId, $newOwner), HTTP::STATUS_OK);
}
return new DataResponse([], HTTP::STATUS_UNAUTHORIZED);
}
}

View File

@@ -94,8 +94,8 @@ class CardApiController extends ApiController {
*
* Update a card
*/
public function update($title, $type, $owner, $description = '', $order = 0, $duedate = null, $archived = null) {
$card = $this->cardService->update($this->request->getParam('cardId'), $title, $this->request->getParam('stackId'), $type, $owner, $description, $order, $duedate, 0, $archived);
public function update($title, $type, $order = 0, $description = '', $owner, $duedate = null, $archived = null) {
$card = $this->cardService->update($this->request->getParam('cardId'), $title, $this->request->getParam('stackId'), $type, $order, $description, $owner, $duedate, 0, $archived);
return new DataResponse($card, HTTP::STATUS_OK);
}

View File

@@ -95,7 +95,7 @@ class CardController extends Controller {
* @return \OCP\AppFramework\Db\Entity
*/
public function update($id, $title, $stackId, $type, $order, $description, $duedate, $deletedAt) {
return $this->cardService->update($id, $title, $stackId, $type, $this->userId, $description, $order, $duedate, $deletedAt);
return $this->cardService->update($id, $title, $stackId, $type, $order, $description, $this->userId, $duedate, $deletedAt);
}
/**

View File

@@ -34,20 +34,12 @@ use OCP\IInitialStateService;
use OCP\IRequest;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Controller;
use OCA\Deck\Db\CardMapper;
use OCP\IURLGenerator;
use \OCP\AppFramework\Http\RedirectResponse;
use OCA\Deck\Db\Acl;
use OCA\Deck\Service\CardService;
class PageController extends Controller {
private $permissionService;
private $initialState;
private $configService;
private $eventDispatcher;
private $cardMapper;
private $urlGenerator;
private $cardService;
public function __construct(
$AppName,
@@ -55,10 +47,7 @@ class PageController extends Controller {
PermissionService $permissionService,
IInitialStateService $initialStateService,
ConfigService $configService,
IEventDispatcher $eventDispatcher,
CardMapper $cardMapper,
IURLGenerator $urlGenerator,
CardService $cardService
IEventDispatcher $eventDispatcher
) {
parent::__construct($AppName, $request);
@@ -66,9 +55,6 @@ class PageController extends Controller {
$this->initialState = $initialStateService;
$this->configService = $configService;
$this->eventDispatcher = $eventDispatcher;
$this->cardMapper = $cardMapper;
$this->urlGenerator = $urlGenerator;
$this->cardService = $cardService;
}
/**
@@ -99,17 +85,4 @@ class PageController extends Controller {
return $response;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function redirectToCard($cardId): RedirectResponse {
try {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
return new RedirectResponse($this->cardService->getCardUrl($cardId));
} catch (\Exception $e) {
return new RedirectResponse($this->urlGenerator->linkToRouteAbsolute('deck.page.index'));
}
}
}

View File

@@ -79,6 +79,6 @@ class DeckWidget implements IWidget {
* @inheritDoc
*/
public function load(): void {
\OCP\Util::addScript('deck', 'deck-dashboard');
\OCP\Util::addScript('deck', 'dashboard');
}
}

View File

@@ -25,7 +25,6 @@ namespace OCA\Deck\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
class AclMapper extends DeckMapper implements IPermissionMapper {
@@ -45,9 +44,9 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
return ($row['owner'] === $userId);
}
public function findBoardId($id): ?int {
public function findBoardId($aclId): ?int {
try {
$entity = $this->find($id);
$entity = $this->find($aclId);
return $entity->getBoardId();
} catch (DoesNotExistException | MultipleObjectsReturnedException $e) {
}
@@ -58,16 +57,4 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
$sql = 'SELECT * from *PREFIX*deck_board_acl WHERE type = ? AND participant = ?';
return $this->findEntities($sql, [$type, $participant]);
}
/**
* @throws \OCP\DB\Exception
*/
public function deleteParticipantFromBoard(int $boardId, int $type, string $participant): void {
$qb = $this->db->getQueryBuilder();
$qb->delete('deck_board_acl')
->where($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('participant', $qb->createNamedParameter($participant, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)));
$qb->executeStatement();
}
}

View File

@@ -29,7 +29,6 @@ use OCA\Deck\NotFoundException;
use OCA\Deck\Service\CirclesService;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUserManager;
@@ -84,8 +83,8 @@ class AssignmentMapper extends QBMapper implements IPermissionMapper {
return $this->cardMapper->isOwner($userId, $cardId);
}
public function findBoardId($id): ?int {
return $this->cardMapper->findBoardId($id);
public function findBoardId($cardId): ?int {
return $this->cardMapper->findBoardId($cardId);
}
/**
@@ -147,39 +146,4 @@ class AssignmentMapper extends QBMapper implements IPermissionMapper {
}
return null;
}
public function remapAssignedUser(int $boardId, string $userId, string $newUserId): void {
$subQuery = $this->db->getQueryBuilder();
$subQuery->selectAlias('a.id', 'id')
->from('deck_assigned_users', 'a')
->innerJoin('a', 'deck_cards', 'c', 'c.id = a.card_id')
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
->where($subQuery->expr()->eq('a.type', $subQuery->createNamedParameter(Assignment::TYPE_USER, IQueryBuilder::PARAM_INT)))
->andWhere($subQuery->expr()->eq('a.participant', $subQuery->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
->andWhere($subQuery->expr()->eq('s.board_id', $subQuery->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->setMaxResults(1000);
$qb = $this->db->getQueryBuilder();
$qb->update('deck_assigned_users')
->set('participant', $qb->createParameter('participant'))
->where($qb->expr()->in('id', $qb->createParameter('ids')));
$moreResults = true;
do {
$result = $subQuery->executeQuery();
$ids = array_map(function ($item) {
return $item['id'];
}, $result->fetchAll());
if (count($ids) === 0 || $result->rowCount() === 0) {
$moreResults = false;
}
$qb->setParameter('participant', $newUserId, IQueryBuilder::PARAM_STR);
$qb->setParameter('ids', $ids, IQueryBuilder::PARAM_INT_ARRAY);
$qb->executeStatement();
} while ($moreResults === true);
$result->closeCursor();
}
}

View File

@@ -28,10 +28,8 @@ class Board extends RelationalEntity {
protected $owner;
protected $color;
protected $archived = false;
/** @var Label[]|null */
protected $labels = null;
/** @var Acl[]|null */
protected $acl = null;
protected $labels = [];
protected $acl = [];
protected $permissions = [];
protected $users = [];
protected $shared;
@@ -63,10 +61,6 @@ class Board extends RelationalEntity {
if ($this->shared === -1) {
unset($json['shared']);
}
// FIXME: Ideally the API responses should follow the internal data structure and return null if the labels/acls have not been fetched from the db
// however this would be a breaking change for consumers of the API
$json['acl'] = $this->acl ?? [];
$json['labels'] = $this->labels ?? [];
return $json;
}
@@ -74,27 +68,21 @@ class Board extends RelationalEntity {
* @param Label[] $labels
*/
public function setLabels($labels) {
$this->labels = $labels;
foreach ($labels as $l) {
$this->labels[] = $l;
}
}
/**
* @param Acl[] $acl
*/
public function setAcl($acl) {
$this->acl = $acl;
foreach ($acl as $a) {
$this->acl[] = $a;
}
}
public function getETag() {
return md5((string)$this->getLastModified());
}
/** @returns Acl[]|null */
public function getAcl(): ?array {
return $this->acl;
}
/** @returns Label[]|null */
public function getLabels(): ?array {
return $this->labels;
}
}

View File

@@ -24,28 +24,23 @@
namespace OCA\Deck\Db;
use OC\Cache\CappedMemoryCache;
use OCA\Deck\Service\CirclesService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IUserManager;
use OCP\IGroupManager;
use Psr\Log\LoggerInterface;
class BoardMapper extends QBMapper implements IPermissionMapper {
class BoardMapper extends DeckMapper implements IPermissionMapper {
private $labelMapper;
private $aclMapper;
private $stackMapper;
private $userManager;
private $groupManager;
private $circlesService;
private $logger;
/** @var CappedMemoryCache */
private $circlesEnabled;
private $userBoardCache;
/** @var CappedMemoryCache */
private $boardCache;
public function __construct(
IDBConnection $db,
@@ -54,7 +49,6 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
StackMapper $stackMapper,
IUserManager $userManager,
IGroupManager $groupManager,
CirclesService $circlesService,
LoggerInterface $logger
) {
parent::__construct($db, 'deck_boards', Board::class);
@@ -63,11 +57,12 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
$this->stackMapper = $stackMapper;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->circlesService = $circlesService;
$this->logger = $logger;
$this->userBoardCache = new CappedMemoryCache();
$this->boardCache = new CappedMemoryCache();
$this->circlesEnabled = \OC::$server->getAppManager()->isEnabledForUser('circles');
}
@@ -75,52 +70,40 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
* @param $id
* @param bool $withLabels
* @param bool $withAcl
* @return Board
* @return \OCP\AppFramework\Db\Entity
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws DoesNotExistException
*/
public function find($id, $withLabels = false, $withAcl = false): Board {
if (!isset($this->boardCache[$id])) {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('deck_boards')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
->orderBy('id');
$this->boardCache[$id] = $this->findEntity($qb);
}
// FIXME is this necessary? it was NOT done with the old mapper
// $this->mapOwner($board);
public function find($id, $withLabels = false, $withAcl = false) {
$sql = 'SELECT id, title, owner, color, archived, deleted_at, last_modified FROM `*PREFIX*deck_boards` ' .
'WHERE `id` = ?';
$board = $this->findEntity($sql, [$id]);
// Add labels
if ($withLabels && $this->boardCache[$id]->getLabels() === null) {
if ($withLabels) {
$labels = $this->labelMapper->findAll($id);
$this->boardCache[$id]->setLabels($labels);
$board->setLabels($labels);
}
// Add acl
if ($withAcl && $this->boardCache[$id]->getAcl() === null) {
if ($withAcl) {
$acl = $this->aclMapper->findAll($id);
$this->boardCache[$id]->setAcl($acl);
$board->setAcl($acl);
}
return $this->boardCache[$id];
return $board;
}
public function findAllForUser(string $userId, ?int $since = null, bool $includeArchived = true, ?int $before = null,
?string $term = null): array {
$useCache = ($since === -1 && $includeArchived === true && $before === null && $term === null);
public function findAllForUser(string $userId, int $since = -1, $includeArchived = true): array {
$useCache = ($since === -1 && $includeArchived === true);
if (!isset($this->userBoardCache[$userId]) || !$useCache) {
$groups = $this->groupManager->getUserGroupIds(
$this->userManager->get($userId)
);
$userBoards = $this->findAllByUser($userId, null, null, $since, $includeArchived, $before, $term);
$groupBoards = $this->findAllByGroups($userId, $groups, null, null, $since, $includeArchived, $before, $term);
$circleBoards = $this->findAllByCircles($userId, null, null, $since, $includeArchived, $before, $term);
$userBoards = $this->findAllByUser($userId, null, null, $since, $includeArchived);
$groupBoards = $this->findAllByGroups($userId, $groups, null, null, $since, $includeArchived);
$circleBoards = $this->findAllByCircles($userId, null, null, $since, $includeArchived);
$allBoards = array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
foreach ($allBoards as $board) {
$this->boardCache[$board->getId()] = $board;
}
if ($useCache) {
$this->userBoardCache[$userId] = $allBoards;
}
@@ -137,91 +120,19 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
* @param null $offset
* @return array
*/
public function findAllByUser(string $userId, ?int $limit = null, ?int $offset = null, ?int $since = null,
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
// FIXME this used to be a UNION to get boards owned by $userId and the user shares in one single query
// Is it possible with the query builder?
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
// this does not work in MySQL/PostgreSQL
//->selectAlias('0', 'shared')
->from('deck_boards', 'b')
->where($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
public function findAllByUser($userId, $limit = null, $offset = null, $since = -1, $includeArchived = true) {
// FIXME: One moving to QBMapper we should allow filtering the boards probably by method chaining for additional where clauses
$sql = 'SELECT id, title, owner, color, archived, deleted_at, 0 as shared, last_modified FROM `*PREFIX*deck_boards` WHERE owner = ? AND last_modified > ?';
if (!$includeArchived) {
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
$sql .= ' AND NOT archived AND deleted_at = 0';
}
if ($since !== null) {
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
}
if ($before !== null) {
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
}
if ($term !== null) {
$qb->andWhere(
$qb->expr()->iLike(
'title',
$qb->createNamedParameter(
'%' . $this->db->escapeLikeParameter($term) . '%',
IQueryBuilder::PARAM_STR
)
)
);
}
$qb->orderBy('b.id');
if ($limit !== null) {
$qb->setMaxResults($limit);
}
if ($offset !== null) {
$qb->setFirstResult($offset);
}
$entries = $this->findEntities($qb);
foreach ($entries as $entry) {
$entry->setShared(0);
}
// shared with user
$qb->resetQueryParts();
$qb->select('b.id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
//->selectAlias('1', 'shared')
->from('deck_boards', 'b')
->innerJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'))
->where($qb->expr()->eq('acl.participant', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_USER, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->neq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
$sql .= ' UNION ' .
'SELECT boards.id, title, owner, color, archived, deleted_at, 1 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' .
'JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE acl.participant=? AND acl.type=? AND boards.owner != ? AND last_modified > ?';
if (!$includeArchived) {
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
$sql .= ' AND NOT archived AND deleted_at = 0';
}
if ($since !== null) {
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
}
if ($before !== null) {
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
}
if ($term !== null) {
$qb->andWhere(
$qb->expr()->iLike(
'title',
$qb->createNamedParameter(
'%' . $this->db->escapeLikeParameter($term) . '%',
IQueryBuilder::PARAM_STR
)
)
);
}
$qb->orderBy('b.id');
if ($limit !== null) {
$qb->setMaxResults($limit);
}
if ($offset !== null) {
$qb->setFirstResult($offset);
}
$sharedEntries = $this->findEntities($qb);
foreach ($sharedEntries as $entry) {
$entry->setShared(1);
}
$entries = array_merge($entries, $sharedEntries);
$entries = $this->findEntities($sql, [$userId, $since, $userId, Acl::PERMISSION_TYPE_USER, $userId, $since], $limit, $offset);
/* @var Board $entry */
foreach ($entries as $entry) {
$acl = $this->aclMapper->findAll($entry->id);
@@ -230,19 +141,9 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
return $entries;
}
public function findAllByOwner(string $userId, ?int $limit = null, ?int $offset = null) {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('deck_boards')
->where($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
->orderBy('id');
if ($limit !== null) {
$qb->setMaxResults($limit);
}
if ($offset !== null) {
$qb->setFirstResult($offset);
}
return $this->findEntities($qb);
public function findAllByOwner(string $userId, int $limit = null, int $offset = null) {
$sql = 'SELECT * FROM `*PREFIX*deck_boards` WHERE owner = ?';
return $this->findEntities($sql, [$userId], $limit, $offset);
}
/**
@@ -254,57 +155,23 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
* @param null $offset
* @return array
*/
public function findAllByGroups(string $userId, array $groups, ?int $limit = null, ?int $offset = null, ?int $since = null,
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
public function findAllByGroups($userId, $groups, $limit = null, $offset = null, $since = -1,$includeArchived = true) {
if (count($groups) <= 0) {
return [];
}
$qb = $this->db->getQueryBuilder();
$qb->select('b.id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
//->selectAlias('2', 'shared')
->from('deck_boards', 'b')
->innerJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'))
->where($qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_GROUP, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->neq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
$or = $qb->expr()->orx();
$sql = 'SELECT boards.id, title, owner, color, archived, deleted_at, 2 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' .
'INNER JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE owner != ? AND type=? AND (';
for ($i = 0, $iMax = count($groups); $i < $iMax; $i++) {
$or->add(
$qb->expr()->eq('acl.participant', $qb->createNamedParameter($groups[$i], IQueryBuilder::PARAM_STR))
);
$sql .= 'acl.participant = ? ';
if (count($groups) > 1 && $i < count($groups) - 1) {
$sql .= ' OR ';
}
}
$qb->andWhere($or);
$sql .= ')';
if (!$includeArchived) {
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
}
if ($since !== null) {
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
}
if ($before !== null) {
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
}
if ($term !== null) {
$qb->andWhere(
$qb->expr()->iLike(
'title',
$qb->createNamedParameter(
'%' . $this->db->escapeLikeParameter($term) . '%',
IQueryBuilder::PARAM_STR
)
)
);
}
$qb->orderBy('b.id');
if ($limit !== null) {
$qb->setMaxResults($limit);
}
if ($offset !== null) {
$qb->setFirstResult($offset);
}
$entries = $this->findEntities($qb);
foreach ($entries as $entry) {
$entry->setShared(2);
$sql .= ' AND NOT archived AND deleted_at = 0';
}
$entries = $this->findEntities($sql, array_merge([$userId, Acl::PERMISSION_TYPE_GROUP], $groups), $limit, $offset);
/* @var Board $entry */
foreach ($entries as $entry) {
$acl = $this->aclMapper->findAll($entry->id);
@@ -313,59 +180,30 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
return $entries;
}
public function findAllByCircles(string $userId, ?int $limit = null, ?int $offset = null, ?int $since = null,
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
$circles = $this->circlesService->getUserCircles($userId);
public function findAllByCircles($userId, $limit = null, $offset = null, $since = -1,$includeArchived = true) {
if (!$this->circlesEnabled) {
return [];
}
$circles = array_map(function ($circle) {
return $circle->getUniqueId();
}, \OCA\Circles\Api\v1\Circles::joinedCircles($userId, true));
if (count($circles) === 0) {
return [];
}
$qb = $this->db->getQueryBuilder();
$qb->select('b.id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
//->selectAlias('2', 'shared')
->from('deck_boards', 'b')
->innerJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'))
->where($qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_CIRCLE, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->neq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
$or = $qb->expr()->orx();
$sql = 'SELECT boards.id, title, owner, color, archived, deleted_at, 2 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' .
'INNER JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE owner != ? AND type=? AND (';
for ($i = 0, $iMax = count($circles); $i < $iMax; $i++) {
$or->add(
$qb->expr()->eq('acl.participant', $qb->createNamedParameter($circles[$i], IQueryBuilder::PARAM_STR))
);
$sql .= 'acl.participant = ? ';
if (count($circles) > 1 && $i < count($circles) - 1) {
$sql .= ' OR ';
}
}
$qb->andWhere($or);
$sql .= ')';
if (!$includeArchived) {
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
}
if ($since !== null) {
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
}
if ($before !== null) {
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
}
if ($term !== null) {
$qb->andWhere(
$qb->expr()->iLike(
'title',
$qb->createNamedParameter(
'%' . $this->db->escapeLikeParameter($term) . '%',
IQueryBuilder::PARAM_STR
)
)
);
}
$qb->orderBy('b.id');
if ($limit !== null) {
$qb->setMaxResults($limit);
}
if ($offset !== null) {
$qb->setFirstResult($offset);
}
$entries = $this->findEntities($qb);
foreach ($entries as $entry) {
$entry->setShared(2);
$sql .= ' AND NOT archived AND deleted_at = 0';
}
$entries = $this->findEntities($sql, array_merge([$userId, Acl::PERMISSION_TYPE_CIRCLE], $circles), $limit, $offset);
/* @var Board $entry */
foreach ($entries as $entry) {
$acl = $this->aclMapper->findAll($entry->id);
@@ -374,26 +212,21 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
return $entries;
}
public function findAll(): array {
$qb = $this->db->getQueryBuilder();
$qb->select('id')
->from('deck_boards');
return $this->findEntities($qb);
public function findAll() {
$sql = 'SELECT id from *PREFIX*deck_boards;';
return $this->findEntities($sql);
}
public function findToDelete() {
// add buffer of 5 min
$timeLimit = time() - (60 * 5);
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
->from('deck_boards')
->where($qb->expr()->gt('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->lt('deleted_at', $qb->createNamedParameter($timeLimit, IQueryBuilder::PARAM_INT)));
return $this->findEntities($qb);
$sql = 'SELECT id, title, owner, color, archived, deleted_at, last_modified FROM `*PREFIX*deck_boards` ' .
'WHERE `deleted_at` > 0 AND `deleted_at` < ?';
return $this->findEntities($sql, [$timeLimit]);
}
public function delete(/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
\OCP\AppFramework\Db\Entity $entity): \OCP\AppFramework\Db\Entity {
\OCP\AppFramework\Db\Entity $entity) {
// delete acl
$acl = $this->aclMapper->findAll($entity->getId());
foreach ($acl as $item) {
@@ -444,11 +277,11 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
return null;
}
if ($acl->getType() === Acl::PERMISSION_TYPE_CIRCLE) {
if (!$this->circlesService->isCirclesEnabled()) {
if (!$this->circlesEnabled) {
return null;
}
try {
$circle = $this->circlesService->getCircle($acl->getParticipant());
$circle = \OCA\Circles\Api\v1\Circles::detailsCircle($acl->getParticipant(), true);
if ($circle) {
return new Circle($circle);
}
@@ -475,34 +308,4 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
return null;
});
}
/**
* @throws \OCP\DB\Exception
*/
public function transferOwnership(string $ownerId, string $newOwnerId, $boardId = null): void {
$qb = $this->db->getQueryBuilder();
$qb->update('deck_boards')
->set('owner', $qb->createNamedParameter($newOwnerId, IQueryBuilder::PARAM_STR))
->where($qb->expr()->eq('owner', $qb->createNamedParameter($ownerId, IQueryBuilder::PARAM_STR)));
if ($boardId !== null) {
$qb->andWhere($qb->expr()->eq('id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)));
}
$qb->executeStatement();
}
/**
* Reset cache for a given board or a given user
*/
public function flushCache(?int $boardId = null, ?string $userId = null) {
if ($boardId) {
unset($this->boardCache[$boardId]);
} else {
$this->boardCache = null;
}
if ($userId) {
unset($this->userBoardCache[$userId]);
} else {
$this->userBoardCache = null;
}
}
}

View File

@@ -30,8 +30,6 @@ use OCA\Deck\Search\Query\SearchQuery;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUser;
@@ -48,8 +46,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
private $groupManager;
/** @var IManager */
private $notificationManager;
/** @var ICache */
private $cache;
private $databaseType;
private $database4ByteSupport;
@@ -59,7 +55,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
IUserManager $userManager,
IGroupManager $groupManager,
IManager $notificationManager,
ICacheFactory $cacheFactory,
$databaseType = 'sqlite3',
$database4ByteSupport = true
) {
@@ -68,7 +63,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->notificationManager = $notificationManager;
$this->cache = $cacheFactory->createDistributed('deck-cardMapper');
$this->databaseType = $databaseType;
$this->database4ByteSupport = $database4ByteSupport;
}
@@ -81,9 +75,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
$description = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $entity->getDescription());
$entity->setDescription($description);
}
$entity = parent::insert($entity);
$this->cache->remove('findBoardId:' . $entity->getId());
return $entity;
return parent::insert($entity);
}
public function update(Entity $entity, $updateModified = true): Entity {
@@ -115,10 +107,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
} catch (Exception $e) {
}
}
// Invalidate cache when the card may be moved to a different board
if (isset($updatedFields['stackId'])) {
$this->cache->remove('findBoardId:' . $entity->getId());
}
return parent::update($entity);
}
@@ -242,18 +230,16 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb);
}
public function findToMeOrNotAssignedCards($boardId, $username) {
public function findAssignedCards($boardId, $username) {
$qb = $this->db->getQueryBuilder();
$qb->select('c.*')
->from('deck_cards', 'c')
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
->leftJoin('c', 'deck_assigned_users', 'u', 'c.id = u.card_id')
->innerJoin('c', 'deck_assigned_users', 'u', 'c.id = u.card_id')
->where($qb->expr()->eq('s.board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->orX(
$qb->expr()->eq('u.participant', $qb->createNamedParameter($username, IQueryBuilder::PARAM_STR)),
$qb->expr()->isNull('u.participant'))
)
->andWhere($qb->expr()->eq('u.participant', $qb->createNamedParameter($username, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq('u.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_USER, IQueryBuilder::PARAM_INT)))
// Filter out archived/deleted cards and board
->andWhere($qb->expr()->eq('c.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
@@ -491,8 +477,8 @@ class CardMapper extends QBMapper implements IPermissionMapper {
}
return $qb->createNamedParameter($dateTime, IQueryBuilder::PARAM_DATE);
}
public function searchRaw($boardIds, $term, $limit = null, $offset = null) {
$qb = $this->queryCardsByBoards($boardIds)
@@ -518,8 +504,9 @@ class CardMapper extends QBMapper implements IPermissionMapper {
}
public function delete(Entity $entity): Entity {
// delete assigned labels
$this->labelMapper->deleteLabelAssignmentsForCard($entity->getId());
$this->cache->remove('findBoardId:' . $entity->getId());
// delete card
return parent::delete($entity);
}
@@ -557,23 +544,12 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return ($row['owner'] === $userId);
}
public function findBoardId($id): ?int {
$result = $this->cache->get('findBoardId:' . $id);
if ($result === null) {
try {
$qb = $this->db->getQueryBuilder();
$qb->select('board_id')
->from('deck_stacks', 's')
->innerJoin('s', 'deck_cards', 'c', 'c.stack_id = s.id')
->where($qb->expr()->eq('c.id', $qb->createNamedParameter($id)));
$queryResult = $qb->executeQuery();
$result = $queryResult->fetchOne();
} catch (\Exception $e) {
$result = false;
}
$this->cache->set('findBoardId:' . $id, $result);
}
return $result !== false ? $result : null;
public function findBoardId($cardId): ?int {
$sql = 'SELECT id FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_stacks` WHERE id IN (SELECT stack_id FROM `*PREFIX*deck_cards` WHERE id = ?))';
$stmt = $this->db->prepare($sql);
$stmt->bindParam(1, $cardId, \PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchColumn() ?? null;
}
public function mapOwner(Card &$card) {
@@ -586,47 +562,4 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return null;
});
}
public function transferOwnership(string $ownerId, string $newOwnerId, int $boardId = null): void {
$params = [
'owner' => $ownerId,
'newOwner' => $newOwnerId
];
$sql = "UPDATE `*PREFIX*{$this->tableName}` SET `owner` = :newOwner WHERE `owner` = :owner";
$stmt = $this->db->executeQuery($sql, $params);
$stmt->closeCursor();
}
public function remapCardOwner(int $boardId, string $userId, string $newUserId): void {
$subQuery = $this->db->getQueryBuilder();
$subQuery->selectAlias('c.id', 'id')
->from('deck_cards', 'c')
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
->where($subQuery->expr()->eq('c.owner', $subQuery->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
->andWhere($subQuery->expr()->eq('s.board_id', $subQuery->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->setMaxResults(1000);
$qb = $this->db->getQueryBuilder();
$qb->update('deck_cards')
->set('owner', $qb->createParameter('owner'))
->where($qb->expr()->in('id', $qb->createParameter('ids')));
$moreResults = true;
do {
$result = $subQuery->executeQuery();
$ids = array_map(function ($item) {
return $item['id'];
}, $result->fetchAll());
if (count($ids) === 0 || $result->rowCount() === 0) {
$moreResults = false;
}
$qb->setParameter('owner', $newUserId, IQueryBuilder::PARAM_STR);
$qb->setParameter('ids', $ids, IQueryBuilder::PARAM_INT_ARRAY);
$qb->executeStatement();
} while ($moreResults === true);
$result->closeCursor();
}
}

View File

@@ -36,8 +36,8 @@ class Circle extends RelationalObject {
public function getObjectSerialization() {
return [
'uid' => $this->object->getUniqueId(),
'displayname' => $this->object->getDisplayName(),
'typeString' => '',
'displayname' => $this->object->getName(),
'typeString' => $this->object->getTypeString(),
'circleOwner' => $this->object->getOwner(),
'type' => 7
];

View File

@@ -101,9 +101,9 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
return ($row['owner'] === $userId);
}
public function findBoardId($id): ?int {
public function findBoardId($labelId): ?int {
try {
$entity = $this->find($id);
$entity = $this->find($labelId);
return $entity->getBoardId();
} catch (DoesNotExistException $e) {
} catch (MultipleObjectsReturnedException $e) {

View File

@@ -75,9 +75,9 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
return ($row['owner'] === $userId);
}
public function findBoardId($id): ?int {
public function findBoardId($stackId): ?int {
try {
$entity = $this->find($id);
$entity = $this->find($stackId);
return $entity->getBoardId();
} catch (DoesNotExistException $e) {
} catch (MultipleObjectsReturnedException $e) {

View File

@@ -51,11 +51,11 @@ class BeforeTemplateRenderedListener implements IEventListener {
$pathInfo = $this->request->getPathInfo();
if (strpos($pathInfo, '/apps/calendar') === 0) {
Util::addScript('deck', 'deck-calendar');
Util::addScript('deck', 'calendar');
}
if (strpos($pathInfo, '/call/') === 0 || strpos($pathInfo, '/apps/spreed') === 0) {
Util::addScript('deck', 'deck-talk');
Util::addScript('deck', 'talk');
}
}
}

View File

@@ -1,99 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Your name <your@email.com>
*
* @author Your name <your@email.com>
*
* @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\Migration;
use Closure;
use Doctrine\DBAL\Schema\SchemaException;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version10800Date20220422061816 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
* @throws SchemaException
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
$schema = $schemaClosure();
$indexAdded = $this->addIndex($schema,
'deck_boards',
'idx_owner_modified',
[ 'owner', 'last_modified' ]
);
$indexAdded = $this->addIndex($schema,
'deck_board_acl',
'idx_participant_type',
[ 'participant', 'type']
) || $indexAdded;
$indexAdded = $this->addIndex($schema,
'deck_cards',
'idx_due_notified_archived_deleted', [
'duedate', 'notified', 'archived', 'deleted_at'
],
) || $indexAdded;
$indexAdded = $this->addIndex($schema,
'deck_cards',
'idx_last_editor', [
'last_editor', 'description_prev'
], [],
// Adding a partial index on the description_prev as it is only used for a NULL check
['lengths' => [null, 1]]
) || $indexAdded;
$indexAdded = $this->addIndex($schema,
'deck_attachment',
'idx_cardid_deletedat',
[ 'card_id', 'deleted_at']
) || $indexAdded;
$indexAdded = $this->addIndex($schema,
'deck_assigned_users',
'idx_card_participant',
[ 'card_id', 'participant']
) || $indexAdded;
return $indexAdded ? $schema : null;
}
private function addIndex(ISchemaWrapper $schema, string $table, string $indexName, array $columns, array $flags = [], array $options = []): bool {
$table = $schema->getTable($table);
if (!$table->hasIndex($indexName)) {
$table->addIndex($columns, $indexName, $flags, $options);
return true;
}
return false;
}
}

View File

@@ -238,7 +238,7 @@ class DeckProvider implements IFullTextSearchProvider {
*
* @param ISearchRequest $request
*/
public function improveSearchRequest(ISearchRequest $searchRequest) {
public function improveSearchRequest(ISearchRequest $request) {
}

View File

@@ -24,14 +24,12 @@
namespace OCA\Deck\Service;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OCA\Deck\Activity\ActivityManager;
use OCA\Deck\Activity\ChangeSet;
use OCA\Deck\AppInfo\Application;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\ChangeHelper;
use OCA\Deck\Db\IPermissionMapper;
use OCA\Deck\Db\Label;
@@ -52,7 +50,6 @@ use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\LabelMapper;
use OCP\IUserManager;
use OCA\Deck\BadRequestException;
use OCP\IURLGenerator;
class BoardService {
private $boardMapper;
@@ -71,10 +68,8 @@ class BoardService {
private $activityManager;
private $eventDispatcher;
private $changeHelper;
private $cardMapper;
private $boardsCache = null;
private $urlGenerator;
public function __construct(
@@ -87,13 +82,11 @@ class BoardService {
PermissionService $permissionService,
NotificationHelper $notificationHelper,
AssignmentMapper $assignedUsersMapper,
CardMapper $cardMapper,
IUserManager $userManager,
IGroupManager $groupManager,
ActivityManager $activityManager,
IEventDispatcher $eventDispatcher,
ChangeHelper $changeHelper,
IURLGenerator $urlGenerator,
$userId
) {
$this->boardMapper = $boardMapper;
@@ -111,8 +104,6 @@ class BoardService {
$this->eventDispatcher = $eventDispatcher;
$this->changeHelper = $changeHelper;
$this->userId = $userId;
$this->urlGenerator = $urlGenerator;
$this->cardMapper = $cardMapper;
}
/**
@@ -127,9 +118,8 @@ class BoardService {
/**
* Get all boards that are shared with a user, their groups or circles
*/
public function getUserBoards(?int $since = null, bool $includeArchived = true, ?int $before = null,
?string $term = null): array {
return $this->boardMapper->findAllForUser($this->userId, $since, $includeArchived, $before, $term);
public function getUserBoards(int $since = -1, bool $includeArchived = true): array {
return $this->boardMapper->findAllForUser($this->userId, $since, $includeArchived);
}
/**
@@ -188,11 +178,9 @@ class BoardService {
/** @var Board $board */
$board = $this->boardMapper->find($boardId, true, true);
$this->boardMapper->mapOwner($board);
if ($board->getAcl() !== null) {
foreach ($board->getAcl() as $acl) {
if ($acl !== null) {
$this->boardMapper->mapAcl($acl);
}
foreach ($board->getAcl() as &$acl) {
if ($acl !== null) {
$this->boardMapper->mapAcl($acl);
}
}
$permissions = $this->permissionService->matchPermissions($board);
@@ -524,14 +512,11 @@ class BoardService {
$acl->setPermissionManage($manage);
$newAcl = $this->aclMapper->insert($acl);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE, [], $this->userId);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE);
$this->notificationHelper->sendBoardShared((int)$boardId, $acl);
$this->boardMapper->mapAcl($newAcl);
$this->changeHelper->boardChanged($boardId);
$board = $this->boardMapper->find($boardId);
$this->clearBoardFromCache($board);
// TODO: use the dispatched event for this
try {
$resourceProvider = \OC::$server->query(\OCA\Deck\Collaboration\Resources\ResourceProvider::class);
@@ -682,43 +667,6 @@ class BoardService {
return $newBoard;
}
public function transferBoardOwnership(int $boardId, string $newOwner, bool $changeContent = false): Board {
\OC::$server->getDatabaseConnection()->beginTransaction();
try {
$board = $this->boardMapper->find($boardId);
$previousOwner = $board->getOwner();
$this->clearBoardFromCache($board);
$this->aclMapper->deleteParticipantFromBoard($boardId, Acl::PERMISSION_TYPE_USER, $newOwner);
if (!$changeContent) {
try {
$this->addAcl($boardId, Acl::PERMISSION_TYPE_USER, $previousOwner, true, true, true);
} catch (UniqueConstraintViolationException $e) {
}
}
$this->boardMapper->transferOwnership($previousOwner, $newOwner, $boardId);
// Optionally also change user assignments and card owner information
if ($changeContent) {
$this->assignedUsersMapper->remapAssignedUser($boardId, $previousOwner, $newOwner);
$this->cardMapper->remapCardOwner($boardId, $previousOwner, $newOwner);
}
\OC::$server->getDatabaseConnection()->commit();
return $this->boardMapper->find($boardId);
} catch (\Throwable $e) {
\OC::$server->getDatabaseConnection()->rollBack();
throw $e;
}
}
public function transferOwnership(string $owner, string $newOwner, bool $changeContent = false): \Generator {
$boards = $this->boardMapper->findAllByUser($owner);
foreach ($boards as $board) {
if ($board->getOwner() === $owner) {
yield $this->transferBoardOwnership($board->getId(), $newOwner, $changeContent);
}
}
}
private function enrichWithStacks($board, $since = -1) {
$stacks = $this->stackMapper->findAll($board->getId(), null, null, $since);
@@ -746,23 +694,4 @@ class BoardService {
}
$board->setUsers(array_values($boardUsers));
}
public function getBoardUrl($endpoint) {
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . '#' . $endpoint;
}
private function clearBoardsCache() {
$this->boardsCache = null;
}
/**
* Clean a given board data from the Cache
*/
private function clearBoardFromCache(Board $board) {
$boardId = $board->getId();
$boardOwnerId = $board->getOwner();
$this->boardMapper->flushCache($boardId, $boardOwnerId);
unset($this->boardsCache[$boardId]);
}
}

View File

@@ -37,7 +37,6 @@ use OCA\Deck\Db\StackMapper;
use OCA\Deck\Event\CardCreatedEvent;
use OCA\Deck\Event\CardDeletedEvent;
use OCA\Deck\Event\CardUpdatedEvent;
use OCA\Deck\NoPermissionException;
use OCA\Deck\Notification\NotificationHelper;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\LabelMapper;
@@ -46,7 +45,6 @@ use OCA\Deck\BadRequestException;
use OCP\Comments\ICommentsManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUserManager;
use OCP\IURLGenerator;
class CardService {
private $cardMapper;
@@ -64,7 +62,6 @@ class CardService {
private $changeHelper;
private $eventDispatcher;
private $userManager;
private $urlGenerator;
public function __construct(
CardMapper $cardMapper,
@@ -81,7 +78,6 @@ class CardService {
IUserManager $userManager,
ChangeHelper $changeHelper,
IEventDispatcher $eventDispatcher,
IURLGenerator $urlGenerator,
$userId
) {
$this->cardMapper = $cardMapper;
@@ -99,7 +95,6 @@ class CardService {
$this->changeHelper = $changeHelper;
$this->eventDispatcher = $eventDispatcher;
$this->currentUser = $userId;
$this->urlGenerator = $urlGenerator;
}
public function enrich($card) {
@@ -159,12 +154,7 @@ class CardService {
}
public function findCalendarEntries($boardId) {
try {
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
} catch (NoPermissionException $e) {
\OC::$server->getLogger()->error('Unable to check permission for a previously obtained board ' . $boardId, ['exception' => $e]);
return [];
}
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
$cards = $this->cardMapper->findCalendarEntries($boardId);
foreach ($cards as $card) {
$this->enrich($card);
@@ -267,9 +257,9 @@ class CardService {
* @param $title
* @param $stackId
* @param $type
* @param $owner
* @param $description
* @param $order
* @param $description
* @param $owner
* @param $duedate
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException
@@ -278,7 +268,7 @@ class CardService {
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/
public function update($id, $title, $stackId, $type, $owner, $description = '', $order = 0, $duedate = null, $deletedAt = null, $archived = null) {
public function update($id, $title, $stackId, $type, $order = 0, $description = '', $owner, $duedate = null, $deletedAt = null, $archived = null) {
if (is_numeric($id) === false) {
throw new BadRequestException('card id must be a number');
}
@@ -607,13 +597,27 @@ class CardService {
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
}
public function getCardUrl($cardId) {
$boardId = $this->cardMapper->findBoardId($cardId);
/**
*
* @return array
* @throws \OCA\Deck\NoPermissionException
* @throws BadRequestException
*/
public function findAllWithDue($userId) {
$cards = $this->cardMapper->findAllWithDue($userId);
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . "#/board/$boardId/card/$cardId";
return $cards;
}
public function getRedirectUrlForCard($cardId) {
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . "card/$cardId";
/**
*
* @return array
* @throws \OCA\Deck\NoPermissionException
* @throws BadRequestException
*/
public function findAssignedCards($userId) {
$cards = $this->cardMapper->findAssignedCards($userId);
return $cards;
}
}

View File

@@ -26,12 +26,8 @@ declare(strict_types=1);
namespace OCA\Deck\Service;
use OCA\Circles\CirclesManager;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Member;
use OCA\Circles\Model\Probes\CircleProbe;
use OCA\Circles\Api\v1\Circles;
use OCP\App\IAppManager;
use Throwable;
/**
* Wrapper around circles app API since it is not in a public namespace so we need to make sure that
@@ -44,66 +40,24 @@ class CirclesService {
$this->circlesEnabled = $appManager->isEnabledForUser('circles');
}
public function isCirclesEnabled(): bool {
return $this->circlesEnabled;
}
public function getCircle(string $circleId): ?Circle {
public function getCircle($circleId) {
if (!$this->circlesEnabled) {
return null;
}
try {
// Enforce current user condition since we always want the full list of members
/** @var CirclesManager $circlesManager */
$circlesManager = \OC::$server->get(CirclesManager::class);
$circlesManager->startSuperSession();
return $circlesManager->getCircle($circleId);
} catch (Throwable $e) {
}
return null;
return \OCA\Circles\Api\v1\Circles::detailsCircle($circleId, true);
}
public function isUserInCircle(string $circleId, string $userId): bool {
public function isUserInCircle($circleId, $userId): bool {
if (!$this->circlesEnabled) {
return false;
}
try {
/** @var CirclesManager $circlesManager */
$circlesManager = \OC::$server->get(CirclesManager::class);
$federatedUser = $circlesManager->getFederatedUser($userId, Member::TYPE_USER);
$circlesManager->startSession($federatedUser);
$circle = $circlesManager->getCircle($circleId);
$member = $circle->getInitiator();
return $member !== null && $member->getLevel() >= Member::LEVEL_MEMBER;
} catch (Throwable $e) {
$member = \OCA\Circles\Api\v1\Circles::getMember($circleId, $userId, 1, true);
return $member->getLevel() >= Circles::LEVEL_MEMBER;
} catch (\Exception $e) {
}
return false;
}
/**
* @param string $userId
* @return string[] circle single ids
*/
public function getUserCircles(string $userId): array {
if (!$this->circlesEnabled) {
return [];
}
try {
/** @var CirclesManager $circlesManager */
$circlesManager = \OC::$server->get(CirclesManager::class);
$federatedUser = $circlesManager->getFederatedUser($userId, Member::TYPE_USER);
$circlesManager->startSession($federatedUser);
$probe = new CircleProbe();
$probe->mustBeMember();
return array_map(function (Circle $circle) {
return $circle->getSingleId();
}, $circlesManager->getCircles($probe));
} catch (Throwable $e) {
}
return [];
}
}

View File

@@ -46,29 +46,20 @@ class ConfigService {
public function __construct(
IConfig $config,
IGroupManager $groupManager
IGroupManager $groupManager,
IUserSession $userSession
) {
// Session is required here in order to make the tests properly inject the userId later on
$this->userId = $userSession->getUser() ? $userSession->getUser()->getUID() : null;
$this->groupManager = $groupManager;
$this->config = $config;
}
public function getUserId() {
if (!$this->userId) {
$user = \OC::$server->get(IUserSession::class)->getUser();
$this->userId = $user ? $user->getUID() : null;
}
return $this->userId;
}
public function getAll(): array {
if ($this->getUserId() === null) {
return [];
}
$data = [
'calendar' => $this->isCalendarEnabled()
];
if ($this->groupManager->isAdmin($this->getUserId())) {
if ($this->groupManager->isAdmin($this->userId)) {
$data['groupLimit'] = $this->get('groupLimit');
}
return $data;
@@ -79,47 +70,43 @@ class ConfigService {
[$scope] = explode(':', $key, 2);
switch ($scope) {
case 'groupLimit':
if ($this->getUserId() === null || !$this->groupManager->isAdmin($this->getUserId())) {
if (!$this->groupManager->isAdmin($this->userId)) {
throw new NoPermissionException('You must be admin to get the group limit');
}
return $this->getGroupLimit();
case 'calendar':
if ($this->getUserId() === null) {
if ($this->userId === null) {
return false;
}
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', true);
return (bool)$this->config->getUserValue($this->userId, Application::APP_ID, 'calendar', true);
}
}
public function isCalendarEnabled(int $boardId = null): bool {
if ($this->getUserId() === null) {
if ($this->userId === null) {
return false;
}
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', true);
$defaultState = (bool)$this->config->getUserValue($this->userId, Application::APP_ID, 'calendar', true);
if ($boardId === null) {
return $defaultState;
}
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':calendar', $defaultState);
return (bool)$this->config->getUserValue($this->userId, Application::APP_ID, 'board:' . $boardId . ':calendar', $defaultState);
}
public function set($key, $value) {
if ($this->getUserId() === null) {
throw new NoPermissionException('Must be logged in to set user config');
}
$result = null;
[$scope] = explode(':', $key, 2);
switch ($scope) {
case 'groupLimit':
if (!$this->groupManager->isAdmin($this->getUserId())) {
if (!$this->groupManager->isAdmin($this->userId)) {
throw new NoPermissionException('You must be admin to set the group limit');
}
$result = $this->setGroupLimit($value);
break;
case 'calendar':
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'calendar', (string)$value);
$this->config->setUserValue($this->userId, Application::APP_ID, 'calendar', (int)$value);
$result = $value;
break;
case 'board':
@@ -127,7 +114,7 @@ class ConfigService {
if ($boardConfigKey === 'notify-due' && !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->getUserId(), Application::APP_ID, $key, (string)$value);
$this->config->setUserValue($this->userId, Application::APP_ID, $key, $value);
$result = $value;
}
return $result;
@@ -169,10 +156,6 @@ class ConfigService {
}
public function getAttachmentFolder(): string {
if ($this->getUserId() === null) {
throw new NoPermissionException('Must be logged in get the attachment folder');
}
return $this->config->getUserValue($this->getUserId(), 'deck', 'attachment_folder', '/Deck');
return $this->config->getUserValue($this->userId, 'deck', 'attachment_folder', '/Deck');
}
}

View File

@@ -59,7 +59,7 @@ class FullTextSearchService {
/** @var CardMapper */
private $cardMapper;
public function __construct(
BoardMapper $boardMapper, StackMapper $stackMapper, CardMapper $cardMapper
) {
@@ -187,6 +187,6 @@ class FullTextSearchService {
* @return Board[]
*/
private function getBoardsFromUser(string $userId): array {
return $this->boardMapper->findAllByUser($userId, null, null, null);
return $this->boardMapper->findAllByUser($userId, null, null, -1);
}
}

View File

@@ -114,7 +114,7 @@ class OverviewService {
$service = $this;
if (count($userBoard->getAcl()) === 0) {
// private board: get cards with due date
// get cards with due date
$findCards[] = array_map(static function ($card) use ($service, $userBoard, $userId) {
$service->enrich($card, $userId);
$cardData = $card->jsonSerialize();
@@ -122,13 +122,13 @@ class OverviewService {
return $cardData;
}, $this->cardMapper->findAllWithDue($userBoard->getId()));
} else {
// shared board: get all my assigned or unassigned cards
// get assigned cards
$findCards[] = array_map(static function ($card) use ($service, $userBoard, $userId) {
$service->enrich($card, $userId);
$cardData = $card->jsonSerialize();
$cardData['boardId'] = $userBoard->getId();
return $cardData;
}, $this->cardMapper->findToMeOrNotAssignedCards($userBoard->getId(), $userId));
}, $this->cardMapper->findAssignedCards($userBoard->getId(), $userId));
}
}
return $findCards;

View File

@@ -33,7 +33,6 @@ use OCA\Deck\Db\IPermissionMapper;
use OCA\Deck\Db\User;
use OCA\Deck\NoPermissionException;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\IConfig;
use OCP\IGroupManager;
@@ -43,8 +42,6 @@ use OCP\Share\IManager;
class PermissionService {
/** @var CirclesService */
private $circlesService;
/** @var BoardMapper */
private $boardMapper;
/** @var AclMapper */
@@ -64,11 +61,11 @@ class PermissionService {
/** @var array */
private $users = [];
private $circlesEnabled = false;
private $boardCache;
public function __construct(
ILogger $logger,
CirclesService $circlesService,
AclMapper $aclMapper,
BoardMapper $boardMapper,
IUserManager $userManager,
@@ -77,7 +74,6 @@ class PermissionService {
IConfig $config,
$userId
) {
$this->circlesService = $circlesService;
$this->aclMapper = $aclMapper;
$this->boardMapper = $boardMapper;
$this->logger = $logger;
@@ -88,6 +84,9 @@ class PermissionService {
$this->userId = $userId;
$this->boardCache = new CappedMemoryCache();
$this->circlesEnabled = \OC::$server->getAppManager()->isEnabledForUser('circles') &&
(version_compare(\OC::$server->getAppManager()->getAppVersion('circles'), '0.17.1') >= 0);
}
/**
@@ -117,7 +116,7 @@ class PermissionService {
*/
public function matchPermissions(Board $board) {
$owner = $this->userIsBoardOwner($board->getId());
$acls = $board->getAcl() ?? [];
$acls = $board->getAcl();
return [
Acl::PERMISSION_READ => $owner || $this->userCan($acls, Acl::PERMISSION_READ),
Acl::PERMISSION_EDIT => $owner || $this->userCan($acls, Acl::PERMISSION_EDIT),
@@ -155,7 +154,7 @@ class PermissionService {
}
try {
$acls = $this->getBoard($boardId)->getAcl() ?? [];
$acls = $this->getBoard($boardId)->getAcl();
$result = $this->userCan($acls, $permission, $userId);
if ($result) {
return true;
@@ -211,11 +210,10 @@ class PermissionService {
return $acl->getPermission($permission);
}
if ($this->circlesService->isCirclesEnabled() && $acl->getType() === Acl::PERMISSION_TYPE_CIRCLE) {
if ($this->circlesEnabled && $acl->getType() === Acl::PERMISSION_TYPE_CIRCLE) {
try {
if ($this->circlesService->isUserInCircle($acl->getParticipant(), $userId) && $acl->getPermission($permission)) {
return true;
}
$member = \OCA\Circles\Api\v1\Circles::getMember($acl->getParticipant(), $this->userId, 1, true);
return $member->getLevel() >= Member::LEVEL_MEMBER && $acl->getPermission($permission);
} catch (\Exception $e) {
$this->logger->info('Member not found in circle that was accessed. This should not happen.');
}
@@ -243,7 +241,6 @@ class PermissionService {
if (array_key_exists((string) $boardId, $this->users) && !$refresh) {
return $this->users[(string) $boardId];
}
try {
$board = $this->boardMapper->find($boardId);
} catch (DoesNotExistException $e) {
@@ -281,19 +278,15 @@ class PermissionService {
}
}
if ($this->circlesService->isCirclesEnabled() && $acl->getType() === Acl::PERMISSION_TYPE_CIRCLE) {
if ($this->circlesEnabled && $acl->getType() === Acl::PERMISSION_TYPE_CIRCLE) {
try {
$circle = $this->circlesService->getCircle($acl->getParticipant());
$circle = \OCA\Circles\Api\v1\Circles::detailsCircle($acl->getParticipant(), true);
if ($circle === null) {
$this->logger->info('No circle found for acl rule ' . $acl->getId());
continue;
}
foreach ($circle->getInheritedMembers() as $member) {
if ($member->getUserType() !== 1 || $member->getLevel() < Member::LEVEL_MEMBER) {
// deck currently only supports user members in circles
continue;
}
foreach ($circle->getMembers() as $member) {
$user = $this->userManager->get($member->getUserId());
if ($user === null) {
$this->logger->info('No user found for circle member ' . $member->getUserId());
@@ -311,10 +304,6 @@ class PermissionService {
}
public function canCreate() {
if ($this->userId === null) {
return false;
}
$groups = $this->getGroupLimitList();
if (count($groups) === 0) {
return true;
@@ -335,13 +324,4 @@ class PermissionService {
}
return $groups;
}
/**
* Set a different user than the current one, e.g. when no user is available in occ
*
* @param string $userId
*/
public function setUserId(string $userId): void {
$this->userId = $userId;
}
}

View File

@@ -90,19 +90,25 @@ class SearchService {
}
public function searchBoards(string $term, ?int $limit, ?int $cursor): array {
$boards = $this->boardService->getUserBoards(null, true, $cursor, mb_strtolower($term));
$boards = $this->boardService->getUserBoards();
// get boards that have a lastmodified date which is lower than the cursor
// and which match the search term
$filteredBoards = array_filter($boards, static function (Board $board) use ($term, $cursor) {
return (
($cursor === null || $board->getLastModified() < $cursor)
&& mb_stripos(mb_strtolower($board->getTitle()), mb_strtolower($term)) > -1
);
});
// sort the boards, recently modified first
usort($boards, function ($boardA, $boardB) {
usort($filteredBoards, function ($boardA, $boardB) {
$ta = $boardA->getLastModified();
$tb = $boardB->getLastModified();
return $ta === $tb
? 0
: ($ta > $tb ? -1 : 1);
});
// limit the number of results
return array_slice($boards, 0, $limit);
return array_slice($filteredBoards, 0, $limit);
}
public function searchComments(string $term, ?int $limit = null, ?int $cursor = null): array {

View File

@@ -35,7 +35,6 @@ use OCA\Deck\Db\ChangeHelper;
use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Db\Stack;
use OCA\Deck\Db\StackMapper;
use OCA\Deck\NoPermissionException;
use OCA\Deck\StatusException;
class StackService {
@@ -143,12 +142,7 @@ class StackService {
}
public function findCalendarEntries($boardId) {
try {
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ);
} catch (NoPermissionException $e) {
\OC::$server->getLogger()->error('Unable to check permission for a previously obtained board ' . $boardId, ['exception' => $e]);
return [];
}
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ);
return $this->stackMapper->findAll($boardId);
}
@@ -181,7 +175,6 @@ class StackService {
if (array_key_exists($card->id, $labels)) {
$cards[$cardIndex]->setLabels($labels[$card->id]);
}
$cards[$cardIndex]->setAttachmentCount($this->attachmentService->count($card->getId()));
}
$stacks[$stackIndex]->setCards($cards);
}

42195
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "deck",
"description": "",
"version": "1.6.2",
"version": "1.4.8",
"authors": [
{
"name": "Julius Härtl",
@@ -29,35 +29,34 @@
},
"dependencies": {
"@babel/polyfill": "^7.12.1",
"@babel/runtime": "^7.16.0",
"@babel/runtime": "^7.13.10",
"@juliushaertl/vue-richtext": "^1.0.1",
"@nextcloud/auth": "^1.3.0",
"@nextcloud/axios": "^1.7.0",
"@nextcloud/dialogs": "^3.1.2",
"@nextcloud/event-bus": "^2.1.1",
"@nextcloud/files": "^2.1.0",
"@nextcloud/initial-state": "^1.2.1",
"@nextcloud/axios": "^1.6.0",
"@nextcloud/dialogs": "^3.1.1",
"@nextcloud/event-bus": "^1.2.0",
"@nextcloud/files": "^1.1.0",
"@nextcloud/initial-state": "^1.2.0",
"@nextcloud/l10n": "^1.4.1",
"@nextcloud/moment": "^1.1.1",
"@nextcloud/router": "^2.0.0",
"@nextcloud/vue": "^4.2.0",
"@nextcloud/vue-dashboard": "^2.0.1",
"blueimp-md5": "^2.19.0",
"dompurify": "^2.3.3",
"@nextcloud/router": "^1.2.0",
"@nextcloud/vue": "^3.8.0",
"@nextcloud/vue-dashboard": "^1.1.0",
"blueimp-md5": "^2.18.0",
"dompurify": "^2.2.7",
"lodash": "^4.17.21",
"markdown-it": "^12.2.0",
"markdown-it": "^12.0.4",
"markdown-it-task-lists": "^2.1.1",
"markdown-it-link-attributes": "^3.0.0",
"moment": "^2.29.1",
"nextcloud-vue-collections": "^0.9.0",
"p-queue": "^6.6.2",
"url-search-params-polyfill": "^8.1.1",
"vue": "^2.6.14",
"vue": "^2.6.12",
"vue-at": "^2.5.0-beta.2",
"vue-click-outside": "^1.1.0",
"vue-easymde": "^2.0.0",
"vue-easymde": "^1.4.0",
"vue-infinite-loading": "^2.4.5",
"vue-router": "^3.5.3",
"vue-router": "^3.5.1",
"vue-smooth-dnd": "^0.8.1",
"vuex": "^3.6.2",
"vuex-router-sync": "^5.0.0"
@@ -66,20 +65,57 @@
"extends @nextcloud/browserslist-config"
],
"engines": {
"node": ">=14.0.0",
"npm": ">=7.0.0"
"node": ">=10.0.0"
},
"devDependencies": {
"@nextcloud/babel-config": "^1.0.0",
"@nextcloud/browserslist-config": "^2.2.0",
"@nextcloud/eslint-config": "^6.1.0",
"@nextcloud/stylelint-config": "^1.0.0-beta.0",
"@nextcloud/webpack-vue-config": "^4.1.2",
"@relative-ci/agent": "^3.0.0",
"@vue/test-utils": "^1.2.2",
"jest": "^27.3.1",
"@babel/core": "^7.13.14",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.13.12",
"@nextcloud/browserslist-config": "^1.0.0",
"@nextcloud/eslint-config": "^2.2.0",
"@nextcloud/eslint-plugin": "^1.5.0",
"@nextcloud/webpack-vue-config": "^1.4.1",
"@relative-ci/agent": "^1.5.0",
"@vue/test-utils": "^1.1.3",
"acorn": "^8.1.0",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.6.3",
"babel-loader": "^8.2.2",
"css-loader": "^4.3.0",
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.1",
"eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^4.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-standard": "^4.1.0",
"eslint-plugin-vue": "^6.2.2",
"file-loader": "^6.2.0",
"jest": "^26.6.3",
"jest-serializer-vue": "^2.0.2",
"vue-jest": "^3.0.7"
"minimist": "^1.2.5",
"node-sass": "^4.14.1",
"raw-loader": "^4.0.2",
"sass-loader": "^10.1.1",
"style-loader": "^1.3.0",
"stylelint": "^13.12.0",
"stylelint-config-recommended": "^4.0.0",
"stylelint-config-recommended-scss": "^4.2.0",
"stylelint-scss": "^3.19.0",
"stylelint-webpack-plugin": "^2.1.1",
"url-loader": "^4.1.1",
"vue-jest": "^3.0.7",
"vue-loader": "^15.9.6",
"vue-template-compiler": "^2.6.12",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.7.3"
},
"engines": {
"node": "^14.0.0",
"npm": "^7.0.0"
},
"jest": {
"moduleFileExtensions": [

View File

@@ -59,11 +59,6 @@ export default {
Content,
AppContent,
},
provide() {
return {
boardApi,
}
},
data() {
return {
addButton: {
@@ -117,6 +112,11 @@ export default {
this.$router.push({ name: 'board' })
},
},
provide() {
return {
boardApi,
}
},
}
</script>

View File

@@ -72,10 +72,10 @@
</div>
<div v-else id="modal-inner">
<EmptyContent v-if="creating" icon="icon-loading">
{{ t('deck', 'Creating the new card ') }}
{{ t('deck', 'Creating the new card') }}
</EmptyContent>
<EmptyContent v-else-if="created" icon="icon-checkmark">
{{ t('deck', 'Card "{card}" was added to "{board}"', { card: pendingTitle, board: selectedBoard.title }) }}
{{ t('deck', '"{card}" was added to "{board}"', { card: pendingTitle, board: selectedBoard.title }) }}
<template #desc>
<button class="primary" @click="openNewCard">
{{ t('deck', 'Open card') }}
@@ -168,7 +168,6 @@ export default {
},
close() {
this.$emit('close')
this.$root.$emit('close')
},
async select() {

View File

@@ -79,7 +79,7 @@ export default {
parameters.after.name = moment(dateTime).format('L LTS')
}
Object.keys(parameters).forEach(function(key, index) {
Object.keys(parameters).map(function(key, index) {
const { type } = parameters[key]
switch (type) {
case 'highlight':

View File

@@ -84,7 +84,7 @@ export default {
params.append('object_id', '' + this.objectId)
params.append('limit', ACTIVITY_FETCH_LIMIT)
const response = await axios.get(generateOcsUrl(`apps/activity/api/v2/activity/${this.filter}`) + '?' + params)
const response = await axios.get(generateOcsUrl('apps/activity/api/v2/activity') + this.filter + '?' + params)
let activities = response.data.ocs.data
if (this.filter === 'deck') {
// We need to manually filter activities here, since currently we use two different types and there is no way

View File

@@ -25,12 +25,6 @@
<div v-if="overviewName" class="board-title">
<div class="board-bullet icon-calendar-dark" />
<h2>{{ overviewName }}</h2>
<Actions>
<ActionButton icon="icon-add" @click="clickShowAddCardModel">
{{ t('deck', 'Add card') }}
</ActionButton>
</Actions>
<CardCreateDialog v-if="showAddCardModal" @close="clickHideAddCardModel" />
</div>
<div v-else-if="board" class="board-title">
<div :style="{backgroundColor: '#' + board.color}" class="board-bullet" />
@@ -76,108 +70,110 @@
<ActionButton v-else icon="icon-filter" />
</Actions>
<div v-if="filterVisible" class="filter">
<h3>{{ t('deck', 'Filter by tag') }}</h3>
<div v-for="label in labelsSorted" :key="label.id" class="filter--item">
<input
:id="label.id"
v-model="filter.tags"
type="checkbox"
class="checkbox"
:value="label.id"
@change="setFilter">
<label :for="label.id"><span class="label" :style="labelStyle(label)">{{ label.title }}</span></label>
</div>
<template>
<div v-if="filterVisible" class="filter">
<h3>{{ t('deck', 'Filter by tag') }}</h3>
<div v-for="label in labelsSorted" :key="label.id" class="filter--item">
<input
:id="label.id"
v-model="filter.tags"
type="checkbox"
class="checkbox"
:value="label.id"
@change="setFilter">
<label :for="label.id"><span class="label" :style="labelStyle(label)">{{ label.title }}</span></label>
</div>
<h3>{{ t('deck', 'Filter by assigned user') }}</h3>
<div class="filter--item">
<input
id="unassigned"
v-model="filter.unassigned"
type="checkbox"
class="checkbox"
value="unassigned"
@change="setFilter"
@click="beforeSetFilter">
<label for="unassigned">{{ t('deck', 'Unassigned') }}</label>
</div>
<div v-for="user in board.users" :key="user.uid" class="filter--item">
<input
:id="user.uid"
v-model="filter.users"
type="checkbox"
class="checkbox"
:value="user.uid"
@change="setFilter">
<label :for="user.uid"><Avatar :user="user.uid" :size="24" :disable-menu="true" /> {{ user.displayname }}</label>
</div>
<h3>{{ t('deck', 'Filter by assigned user') }}</h3>
<div class="filter--item">
<input
id="unassigned"
v-model="filter.unassigned"
type="checkbox"
class="checkbox"
value="unassigned"
@change="setFilter"
@click="beforeSetFilter">
<label for="unassigned">{{ t('deck', 'Unassigned') }}</label>
</div>
<div v-for="user in board.users" :key="user.uid" class="filter--item">
<input
:id="user.uid"
v-model="filter.users"
type="checkbox"
class="checkbox"
:value="user.uid"
@change="setFilter">
<label :for="user.uid"><Avatar :user="user.uid" :size="24" :disable-menu="true" /> {{ user.displayname }}</label>
</div>
<h3>{{ t('deck', 'Filter by due date') }}</h3>
<h3>{{ t('deck', 'Filter by due date') }}</h3>
<div class="filter--item">
<input
id="overdue"
v-model="filter.due"
type="radio"
class="radio"
value="overdue"
@change="setFilter"
@click="beforeSetFilter">
<label for="overdue">{{ t('deck', 'Overdue') }}</label>
<div class="filter--item">
<input
id="overdue"
v-model="filter.due"
type="radio"
class="radio"
value="overdue"
@change="setFilter"
@click="beforeSetFilter">
<label for="overdue">{{ t('deck', 'Overdue') }}</label>
</div>
<div class="filter--item">
<input
id="dueToday"
v-model="filter.due"
type="radio"
class="radio"
value="dueToday"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueToday">{{ t('deck', 'Next 24 hours') }}</label>
</div>
<div class="filter--item">
<input
id="dueWeek"
v-model="filter.due"
type="radio"
class="radio"
value="dueWeek"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueWeek">{{ t('deck', 'Next 7 days') }}</label>
</div>
<div class="filter--item">
<input
id="dueMonth"
v-model="filter.due"
type="radio"
class="radio"
value="dueMonth"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueMonth">{{ t('deck', 'Next 30 days') }}</label>
</div>
<div class="filter--item">
<input
id="noDue"
v-model="filter.due"
type="radio"
class="radio"
value="noDue"
@change="setFilter"
@click="beforeSetFilter">
<label for="noDue">{{ t('deck', 'No due date') }}</label>
</div>
<Button :disabled="!isFilterActive" @click="clearFilter">
{{ t('deck', 'Clear filter') }}
</Button>
</div>
<div class="filter--item">
<input
id="dueToday"
v-model="filter.due"
type="radio"
class="radio"
value="dueToday"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueToday">{{ t('deck', 'Next 24 hours') }}</label>
</div>
<div class="filter--item">
<input
id="dueWeek"
v-model="filter.due"
type="radio"
class="radio"
value="dueWeek"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueWeek">{{ t('deck', 'Next 7 days') }}</label>
</div>
<div class="filter--item">
<input
id="dueMonth"
v-model="filter.due"
type="radio"
class="radio"
value="dueMonth"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueMonth">{{ t('deck', 'Next 30 days') }}</label>
</div>
<div class="filter--item">
<input
id="noDue"
v-model="filter.due"
type="radio"
class="radio"
value="noDue"
@change="setFilter"
@click="beforeSetFilter">
<label for="noDue">{{ t('deck', 'No due date') }}</label>
</div>
<Button :disabled="!isFilterActive" @click="clearFilter">
{{ t('deck', 'Clear filter') }}
</Button>
</div>
</template>
</Popover>
<Actions>
@@ -210,12 +206,11 @@
import { mapState, mapGetters } from 'vuex'
import { Actions, ActionButton, Popover, Avatar } from '@nextcloud/vue'
import labelStyle from '../mixins/labelStyle'
import CardCreateDialog from '../CardCreateDialog'
export default {
name: 'Controls',
components: {
Actions, ActionButton, Popover, Avatar, CardCreateDialog,
Actions, ActionButton, Popover, Avatar,
},
mixins: [labelStyle],
props: {
@@ -238,7 +233,6 @@ export default {
showArchived: false,
isAddStackVisible: false,
filter: { tags: [], users: [], due: '', unassigned: false },
showAddCardModal: false,
}
},
@@ -324,12 +318,6 @@ export default {
this.$store.dispatch('setFilter', { ...filterReset })
this.filter = filterReset
},
clickShowAddCardModel() {
this.showAddCardModal = true
},
clickHideAddCardModel() {
this.showAddCardModal = false
},
},
}
</script>

View File

@@ -53,9 +53,6 @@
<ActionCheckbox v-if="canManage" :checked="acl.permissionManage" @change="clickManageAcl(acl)">
{{ t('deck', 'Can manage') }}
</ActionCheckbox>
<ActionCheckbox v-if="acl.type === 0 && isCurrentUser(board.owner.uid)" :checked="acl.owner" @change="clickTransferOwner(acl.participant.uid)">
{{ t('deck', 'Owner') }}
</ActionCheckbox>
<ActionButton v-if="canManage" icon="icon-delete" @click="clickDeleteAcl(acl)">
{{ t('deck', 'Delete') }}
</ActionButton>
@@ -75,7 +72,7 @@ import { Avatar, Multiselect, Actions, ActionButton, ActionCheckbox } from '@nex
import { CollectionList } from 'nextcloud-vue-collections'
import { mapGetters, mapState } from 'vuex'
import { getCurrentUser } from '@nextcloud/auth'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { showError } from '@nextcloud/dialogs'
import debounce from 'lodash/debounce'
export default {
@@ -100,7 +97,6 @@ export default {
isSearching: false,
addAcl: null,
addAclForAPI: null,
newOwner: null,
}
},
computed: {
@@ -198,38 +194,6 @@ export default {
clickDeleteAcl(acl) {
this.$store.dispatch('deleteAclFromCurrentBoard', acl)
},
clickTransferOwner(newOwner) {
OC.dialogs.confirmDestructive(
t('deck', 'Are you sure you want to transfer the board {title} for {user} ?', { title: this.board.title, user: newOwner }),
t('deck', 'Transfer the board.'),
{
type: OC.dialogs.YES_NO_BUTTONS,
confirm: t('deck', 'Transfer'),
confirmClasses: 'error',
cancel: t('deck', 'Cancel'),
},
async (result) => {
if (result) {
try {
this.isLoading = true
await this.$store.dispatch('transferOwnership', {
boardId: this.board.id,
newOwner,
})
const successMessage = t('deck', 'Transfer the board for {user} successfully', { user: newOwner })
showSuccess(successMessage)
this.$router.push({ name: 'main' })
} catch (e) {
const errorMessage = t('deck', 'Failed to transfer the board for {user}', { user: newOwner.user })
showError(errorMessage)
} finally {
this.isLoading = false
}
}
},
true
)
},
},
}
</script>

View File

@@ -264,14 +264,12 @@ export default {
<style lang="scss" scoped>
@use 'sass:math';
@import './../../css/variables';
.stack {
width: $stack-width + $stack-spacing*3;
margin-left: math.div($stack-spacing, 2);
margin-right: math.div($stack-spacing, 2);
margin-left: $stack-spacing/2;
margin-right: $stack-spacing/2;
}
.stack__header {

View File

@@ -43,22 +43,24 @@
<li v-if="addLabel" class="editing">
<!-- New Tag -->
<form class="label-form" @submit.prevent="clickAddLabel">
<ColorPicker class="color-picker-wrapper" :value="'#' + addLabelObj.color" @input="updateColor">
<div :style="{ backgroundColor: '#' + addLabelObj.color }" class="color0 icon-colorpicker" />
</ColorPicker>
<input v-model="addLabelObj.title" type="text">
<input v-tooltip="{content: missingDataLabel, show: !addLabelObjValidated, trigger: 'manual' }"
:disabled="!addLabelObjValidated"
type="submit"
value=""
class="icon-confirm">
<Actions>
<ActionButton icon="icon-close" @click="addLabel=false">
{{ t('deck', 'Cancel') }}
</ActionButton>
</Actions>
</form>
<template>
<form class="label-form" @submit.prevent="clickAddLabel">
<ColorPicker class="color-picker-wrapper" :value="'#' + addLabelObj.color" @input="updateColor">
<div :style="{ backgroundColor: '#' + addLabelObj.color }" class="color0 icon-colorpicker" />
</ColorPicker>
<input v-model="addLabelObj.title" type="text">
<input v-tooltip="{content: missingDataLabel, show: !addLabelObjValidated, trigger: 'manual' }"
:disabled="!addLabelObjValidated"
type="submit"
value=""
class="icon-confirm">
<Actions>
<ActionButton icon="icon-close" @click="addLabel=false">
{{ t('deck', 'Cancel') }}
</ActionButton>
</Actions>
</form>
</template>
</li>
<button v-if="canManage && !isArchived" @click="clickShowAddLabel()">
<span class="icon-add" />{{ t('deck', 'Add a new tag') }}

View File

@@ -71,7 +71,7 @@
</a>
</div>
<Actions v-if="selectable">
<ActionButton icon="icon-confirm" @click="$emit('select-attachment', attachment)">
<ActionButton icon="icon-confirm" @click="$emit('selectAttachment', attachment)">
{{ t('deck', 'Add this attachment') }}
</ActionButton>
</Actions>
@@ -89,10 +89,10 @@
{{ t('deck', 'Remove attachment') }}
</ActionButton>
<ActionButton v-if="!attachment.extendedData.fileid && attachment.deletedAt === 0" icon="icon-delete" @click="$emit('delete-attachment', attachment)">
<ActionButton v-if="!attachment.extendedData.fileid && attachment.deletedAt === 0" icon="icon-delete" @click="$emit('deleteAttachment', attachment)">
{{ t('deck', 'Delete Attachment') }}
</ActionButton>
<ActionButton v-else-if="!attachment.extendedData.fileid" icon="icon-history" @click="$emit('restore-attachment', attachment)">
<ActionButton v-else-if="!attachment.extendedData.fileid" icon="icon-history" @click="$emit('restoreAttachment', attachment)">
{{ t('deck', 'Restore Attachment') }}
</ActionButton>
</Actions>
@@ -222,13 +222,13 @@ export default {
},
shareFromFiles() {
picker.pick()
.then(async (path) => {
.then(async(path) => {
console.debug(`path ${path} selected for sharing`)
if (!path.startsWith('/')) {
throw new Error(t('files', 'Invalid path selected'))
}
axios.post(generateOcsUrl('apps/files_sharing/api/v1/shares'), {
axios.post(generateOcsUrl('apps/files_sharing/api/v1', 2) + 'shares', {
path,
shareType: 12,
shareWith: '' + this.cardId,
@@ -245,7 +245,7 @@ export default {
},
showViewer(attachment) {
if (attachment.extendedData.fileid && window.OCA.Viewer.availableHandlers.map(handler => handler.mimes).flat().includes(attachment.extendedData.mimetype)) {
window.OCA.Viewer.open({ path: attachment.extendedData.path })
window.OCA.Viewer.open(attachment.extendedData.path)
return
}

View File

@@ -205,8 +205,6 @@ export default {
max-width: calc(100% - #{$modal-padding*2});
padding: 0 14px;
max-height: 100%;
user-select: text;
-webkit-user-select: text;
&::v-deep {
.app-sidebar-header {
position: sticky;

View File

@@ -24,8 +24,8 @@
<AttachmentList
:card-id="card.id"
:removable="true"
@delete-attachment="deleteAttachment"
@restore-attachment="restoreAttachment" />
@deleteAttachment="deleteAttachment"
@restoreAttachment="restoreAttachment" />
</template>
<script>

View File

@@ -101,7 +101,6 @@
:lang="lang"
:formatter="format"
:disabled="saving || !canEdit"
:shortcuts="shortcuts"
confirm />
<Actions v-if="canEdit">
<ActionButton v-if="copiedCard.duedate" icon="icon-delete" @click="removeDue()">
@@ -177,48 +176,6 @@ export default {
stringify: this.stringify,
parse: this.parse,
},
shortcuts: [
{
text: t('deck', 'Today'),
onClick() {
const date = new Date()
date.setDate(date.getDate())
date.setHours(23)
date.setMinutes(59)
return date
},
},
{
text: t('deck', 'Tomorrow'),
onClick() {
const date = new Date()
date.setDate(date.getDate() + 1)
date.setHours(23)
date.setMinutes(59)
return date
},
},
{
text: t('deck', 'Next week'),
onClick() {
const date = new Date()
date.setDate(date.getDate() + 7)
date.setHours(23)
date.setMinutes(59)
return date
},
},
{
text: t('deck', 'Next month'),
onClick() {
const date = new Date()
date.setDate(date.getDate() + 30)
date.setHours(23)
date.setMinutes(59)
return date
},
},
],
}
},
computed: {
@@ -359,14 +316,6 @@ export default {
</script>
<style lang="scss" scoped>
.section-wrapper::v-deep .mx-datepicker-main.mx-datepicker-popup {
left: 0 !important;
}
.section-wrapper::v-deep .mx-datepicker-main.mx-datepicker-popup.mx-datepicker-sidebar {
padding: 0 !important;
}
.section-wrapper {
display: flex;
max-width: 100%;

View File

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

View File

@@ -19,45 +19,47 @@
</div>
</div>
<li v-else class="comment">
<div class="comment--header">
<Avatar :user="comment.actorId" />
<span class="has-tooltip username">
{{ comment.actorDisplayName }}
</span>
<Actions v-show="!edit" :force-menu="true">
<ActionButton icon="icon-reply" :close-after-click="true" @click="replyTo()">
{{ t('deck', 'Reply') }}
</ActionButton>
<ActionButton v-if="canEdit"
icon="icon-rename"
:close-after-click="true"
@click="showUpdateForm()">
{{ t('deck', 'Update') }}
</ActionButton>
<ActionButton v-if="canEdit"
icon="icon-delete"
:close-after-click="true"
@click="deleteComment()">
{{ t('deck', 'Delete') }}
</ActionButton>
</Actions>
<Actions v-if="edit">
<ActionButton icon="icon-close" @click="hideUpdateForm" />
</Actions>
<div class="spacer" />
<div class="timestamp">
{{ relativeDate(comment.creationDateTime) }}
<template>
<div class="comment--header">
<Avatar :user="comment.actorId" />
<span class="has-tooltip username">
{{ comment.actorDisplayName }}
</span>
<Actions v-show="!edit" :force-menu="true">
<ActionButton icon="icon-reply" :close-after-click="true" @click="replyTo()">
{{ t('deck', 'Reply') }}
</ActionButton>
<ActionButton v-if="canEdit"
icon="icon-rename"
:close-after-click="true"
@click="showUpdateForm()">
{{ t('deck', 'Update') }}
</ActionButton>
<ActionButton v-if="canEdit"
icon="icon-delete"
:close-after-click="true"
@click="deleteComment()">
{{ t('deck', 'Delete') }}
</ActionButton>
</Actions>
<Actions v-if="edit">
<ActionButton icon="icon-close" @click="hideUpdateForm" />
</Actions>
<div class="spacer" />
<div class="timestamp">
{{ relativeDate(comment.creationDateTime) }}
</div>
</div>
</div>
<CommentItem v-if="comment.replyTo" :reply="true" :comment="comment.replyTo" />
<div v-show="!edit" ref="richTextElement">
<RichText
class="comment--content"
:text="richText(comment)"
:arguments="richArgs(comment)"
:autolink="true" />
</div>
<CommentForm v-if="edit" v-model="commentMsg" @submit="updateComment" />
<CommentItem v-if="comment.replyTo" :reply="true" :comment="comment.replyTo" />
<div v-show="!edit" ref="richTextElement">
<RichText
class="comment--content"
:text="richText(comment)"
:arguments="richArgs(comment)"
:autolink="true" />
</div>
<CommentForm v-if="edit" v-model="commentMsg" @submit="updateComment" />
</template>
</li>
</template>

View File

@@ -57,7 +57,7 @@
ref="markdownEditor"
v-model="description"
:configs="mdeConfig"
@update:modelValue="updateDescription"
@input="updateDescription"
@blur="saveDescription" />
<Modal v-if="modalShow" :title="t('deck', 'Choose attachment')" @close="modalShow=false">
@@ -66,7 +66,7 @@
<AttachmentList
:card-id="card.id"
:selectable="true"
@select-attachment="addAttachment" />
@selectAttachment="addAttachment" />
</div>
</Modal>
</div>
@@ -75,7 +75,6 @@
<script>
import MarkdownIt from 'markdown-it'
import MarkdownItTaskLists from 'markdown-it-task-lists'
import MarkdownItLinkAttributes from 'markdown-it-link-attributes'
import AttachmentList from './AttachmentList'
import { Actions, ActionButton, Modal } from '@nextcloud/vue'
import { formatFileSize } from '@nextcloud/files'
@@ -87,13 +86,6 @@ const markdownIt = new MarkdownIt({
})
markdownIt.use(MarkdownItTaskLists, { enabled: true, label: true, labelAfter: true })
markdownIt.use(MarkdownItLinkAttributes, {
attrs: {
target: '_blank',
rel: 'noreferrer noopener',
},
})
export default {
name: 'Description',
components: {
@@ -231,7 +223,7 @@ export default {
updateDescription() {
this.descriptionLastEdit = Date.now()
clearTimeout(this.descriptionSaveTimeout)
this.descriptionSaveTimeout = setTimeout(async () => {
this.descriptionSaveTimeout = setTimeout(async() => {
await this.saveDescription()
}, 2500)
},
@@ -323,12 +315,6 @@ h5 {
border-left: 1px solid var(--color-main-text);
}
.CodeMirror-selected,
.CodeMirror-line::selection, .CodeMirror-line>span::selection, .CodeMirror-line>span>span::selection {
background: var(--color-primary-element) !important;
color: var(--color-primary-text) !important;
}
.editor-preview,
.editor-statusbar {
display: none;

View File

@@ -35,14 +35,14 @@
<Avatar v-if="user.type === 1"
:user="user.participant.uid"
:display-name="user.participant.displayname"
:tooltip-message="user.participant.displayname + ' ' + t('deck', '(Group)')"
:tooltip-message="user.participant.displayname + ' ' + t('deck', '(group)')"
:is-no-user="true"
:disable-="true"
:size="32" />
<Avatar v-if="user.type === 7"
:user="user.participant.uid"
:display-name="user.participant.displayname"
:tooltip-message="user.participant.displayname + ' ' + t('deck', '(Circle)')"
:tooltip-message="user.participant.displayname + ' ' + t('deck', '(circle)')"
:is-no-user="true"
:disable-="true"
:size="32" />
@@ -53,19 +53,6 @@
<PopoverMenu :menu="popover" />
<slot />
</div>
<div class="avatar-print-list">
<div v-for="user in avatarUsers" :key="user.id" class="avatar-print-list-item">
<Avatar
class="avatar-print-list-avatar"
:user="user.participant.uid"
:display-name="user.participant.displayname"
:disable-menu="true"
:is-no-user="user.type !== 0"
:size="24" />
{{ user.participant.displayname }}
</div>
</div>
</div>
</template>
@@ -85,7 +72,7 @@ export default {
props: {
users: {
type: Array,
default: () => ([]),
default: () => { return {} },
},
},
data() {
@@ -129,15 +116,6 @@ export default {
}),
]
},
avatarUsers() {
if (!this.users) {
return []
}
return this.users.filter((user) => {
return [0, 1, 7].includes(user.type)
})
},
},
methods: {
togglePopover() {
@@ -154,7 +132,7 @@ export default {
margin-top: 5px;
position: relative;
flex-grow: 1;
::v-deep .popovermenu {
/deep/ .popovermenu {
margin-right: -4px;
img {
padding: 0;
@@ -174,7 +152,7 @@ export default {
padding-right: $avatar-offset;
flex-direction: row-reverse;
.avatardiv,
::v-deep .avatardiv {
/deep/ .avatardiv {
width: 36px;
height: 36px;
box-sizing: content-box !important;
@@ -189,7 +167,7 @@ export default {
cursor: pointer;
}
}
&:hover div:nth-child(n+2) ::v-deep .avatardiv {
&:hover div:nth-child(n+2) /deep/ .avatardiv {
margin-right: 1px;
}
}
@@ -198,26 +176,4 @@ export default {
display: block;
margin: 40px -6px;
}
.avatar-print-list {
display: none;
}
@media print {
.avatar-list {
display: none;
}
.avatar-print-list-item {
align-items: center;
display: flex;
gap: 10px;
margin-bottom: 10px;
}
.avatar-print-list {
display: block;
padding-top: 5px;
}
}
</style>

View File

@@ -41,7 +41,7 @@
<AvatarList :users="card.assignedUsers" />
<CardMenu class="card-menu" :card="card" />
<CardMenu :card="card" />
</div>
</template>
<script>
@@ -68,7 +68,7 @@ export default {
if (this.card.commentsUnread > 0) {
return t('deck', '{count} comments, {unread} unread', {
count: this.card.commentsCount,
unread: this.card.commentsUnread,
unread: this.card.commentsUnread
})
}
return null
@@ -150,15 +150,4 @@ export default {
.fade-enter, .fade-leave-to {
opacity: 0;
}
@media print {
.badges {
align-items: flex-start;
max-height: none !important;
}
.card-menu {
display: none;
}
}
</style>

View File

@@ -66,11 +66,7 @@
:placeholder="t('deck', 'Select a list')"
:options="stacksFromBoard"
:max-height="100"
label="title">
<span slot="noOptions">
{{ t('deck', 'List is empty') }}
</span>
</Multiselect>
label="title" />
<button :disabled="!isBoardAndStackChoosen" class="primary" @click="moveCard">
{{ t('deck', 'Move card') }}
@@ -135,7 +131,7 @@ export default {
},
activeBoards() {
return this.$store.getters.boards.filter((item) => item.deletedAt === 0 && item.archived === false)
},
}
},
methods: {
openCard() {

View File

@@ -23,7 +23,7 @@
<template>
<div v-if="card" class="duedate">
<transition name="zoom">
<div v-if="card.duedate" :class="dueIcon" :title="absoluteDate">
<div v-if="card.duedate" :class="dueIcon">
<span>{{ relativeDate }}</span>
</div>
</transition>
@@ -62,14 +62,14 @@ export default {
}
return moment(this.card.duedate).fromNow()
},
absoluteDate() {
return moment(this.card.duedate).format('L')
dueDateTooltip() {
return moment(this.card.duedate).format('LLLL')
},
},
}
</script>
<style lang="scss" scoped>
<style lang="scss" coped>
.icon.due {
background-position: 4px center;
border-radius: 3px;
@@ -105,7 +105,6 @@ export default {
padding: 3px 4px;
}
&::before,
span {
margin-left: 20px;
white-space: nowrap;
@@ -113,18 +112,4 @@ export default {
overflow: hidden;
}
}
@media print {
.icon.due {
background-color: transparent !important;
span {
display: none;
}
&::before {
content: attr(title);
}
}
}
</style>

View File

@@ -163,7 +163,7 @@ export default {
if (this.isAdmin) {
this.groupLimit = this.$store.getters.config('groupLimit')
this.groupLimitDisabled = false
axios.get(generateOcsUrl('cloud/groups')).then((response) => {
axios.get(generateOcsUrl('cloud', 2) + 'groups').then((response) => {
this.groups = response.data.ocs.data.groups.reduce((obj, item) => {
obj.push({
id: item,

View File

@@ -39,9 +39,6 @@
<script>
import { ColorPicker, ActionButton, Actions, AppNavigationItem } from '@nextcloud/vue'
/**
*
*/
function randomColor() {
let randomHexColor = ((1 << 24) * Math.random() | 0).toString(16)
while (randomHexColor.length < 6) {

View File

@@ -125,10 +125,7 @@
<div :style="{ backgroundColor: getColor }" class="color0 icon-colorpicker app-navigation-entry-bullet" />
</ColorPicker>
<form @submit.prevent.stop="applyEdit">
<input v-model="editTitle"
v-focus
type="text"
required>
<input v-model="editTitle" type="text" required>
<input type="submit" value="" class="icon-confirm">
<Actions><ActionButton icon="icon-close" @click.stop.prevent="cancelEdit" /></Actions>
</form>
@@ -152,9 +149,6 @@ export default {
directives: {
ClickOutside,
},
inject: [
'boardApi',
],
props: {
board: {
type: Object,
@@ -309,6 +303,9 @@ export default {
this.updateDueSetting = null
},
},
inject: [
'boardApi',
],
}
</script>

View File

@@ -78,7 +78,7 @@ export default {
},
computed: {
boardsSorted() {
return [...this.boards].sort((a, b) => a.title.localeCompare(b.title))
return [...this.boards].sort((a, b) => (a.title < b.title) ? -1 : 1)
},
collapsible() {
return this.boards.length > 0

View File

@@ -63,15 +63,10 @@ import { Actions, ActionButton } from '@nextcloud/vue'
const createCancelToken = () => axios.CancelToken.source()
/**
* @param root0
* @param root0.query
* @param root0.cursor
*/
function search({ query, cursor }) {
const cancelToken = createCancelToken()
const request = async () => axios.get(generateOcsUrl('apps/deck/api/v1.0/search'), {
const request = async() => axios.get(generateOcsUrl('apps/deck/api/v1.0', 2) + '/search', {
cancelToken: cancelToken.token,
params: {
term: query,

View File

@@ -21,11 +21,6 @@ ul {
ul {
list-style-type: disc;
.task-list-item {
margin-left: -20px;
list-style-type: none;
}
}
h1 {
@@ -72,7 +67,7 @@ img {
}
input[type=checkbox] {
margin: 0px 3px 0px 0px;
margin: 0px 10px 0px 0px;
line-height: 10px;
font-size: 10px;
display: inline-block;

View File

@@ -25,8 +25,9 @@ const buildSelector = (selector, propsData = {}) => {
return new Promise((resolve, reject) => {
const container = document.createElement('div')
document.getElementById('body-user').append(container)
const ComponentVM = new Vue({
render: (h) => h(selector, propsData),
const View = Vue.extend(selector)
const ComponentVM = new View({
propsData,
}).$mount(container)
ComponentVM.$root.$on('close', () => {
ComponentVM.$el.remove()

View File

@@ -22,6 +22,8 @@
import Vue from 'vue'
import BoardSelector from './BoardSelector'
import CardSelector from './CardSelector'
import './../css/collections.css'
import FileSharingPicker from './views/FileSharingPicker'
import { buildSelector } from './helpers/selector'
@@ -43,19 +45,13 @@ window.addEventListener('DOMContentLoaded', () => {
}
window.OCP.Collaboration.registerType('deck', {
action: () => {
const BoardSelector = () => import('./BoardSelector')
return buildSelector(BoardSelector)
},
action: () => buildSelector(BoardSelector),
typeString: t('deck', 'Link to a board'),
typeIconClass: 'icon-deck',
})
window.OCP.Collaboration.registerType('deck-card', {
action: () => {
const CardSelector = () => import('./CardSelector')
return buildSelector(CardSelector)
},
action: () => buildSelector(CardSelector),
typeString: t('deck', 'Link to a card'),
typeIconClass: 'icon-deck',
})

View File

@@ -44,39 +44,15 @@ window.addEventListener('DOMContentLoaded', () => {
window.OCA.Talk.registerMessageAction({
label: t('deck', 'Create a card'),
icon: 'icon-deck',
async callback({ message: { message, messageParameters, actorDisplayName }, metadata: { name: conversationName, token: conversationToken } }) {
const parsedMessage = message.replace(/{[a-z0-9-_]+}/gi, function(parameter) {
const parameterName = parameter.substr(1, parameter.length - 2)
if (messageParameters[parameterName]) {
if (messageParameters[parameterName].type === 'file' && messageParameters[parameterName].path) {
return messageParameters[parameterName].path
}
if (messageParameters[parameterName].type === 'user' || messageParameters[parameterName].type === 'call') {
return '@' + messageParameters[parameterName].name
}
if (messageParameters[parameterName].name) {
return messageParameters[parameterName].name
}
}
// Do not replace so insert with curly braces again
return parameter
})
const shortenedMessageCandidate = parsedMessage.replace(/^(.{255}[^\s]*).*/, '$1')
const shortenedMessage = shortenedMessageCandidate === '' ? parsedMessage.substr(0, 255) : shortenedMessageCandidate
async callback({ message: { message, actorDisplayName }, metadata: { name: conversationName, token: conversationToken } }) {
const shortenedMessageCandidate = message.replace(/^(.{255}[^\s]*).*/, '$1')
const shortenedMessage = shortenedMessageCandidate === '' ? message.substr(0, 255) : shortenedMessageCandidate
try {
await buildSelector(CardCreateDialog, {
props: {
title: shortenedMessage,
description: parsedMessage + '\n\n' + '['
+ t('deck', 'Message from {author} in {conversationName}', {
author: actorDisplayName,
conversationName,
})
+ '](' + window.location.protocol + '//' + window.location.host + generateUrl('/call/' + conversationToken) + ')',
},
title: shortenedMessage,
description: message + '\n\n' + '['
+ t('deck', 'Message from {author} in {conversationName}', { author: actorDisplayName, conversationName })
+ '](' + generateUrl('/call/' + conversationToken) + ')',
})
} catch (e) {
console.debug('Card creation dialog was canceled')

View File

@@ -120,7 +120,7 @@ if (!window.OCA.Deck) {
}
/**
* @typedef {object} CardRichObject
* @typedef {Object} CardRichObject
* @property {string} id
* @property {string} name
* @property {string} boardname
@@ -135,14 +135,13 @@ if (!window.OCA.Deck) {
/**
* Frontend message API for adding actions to talk messages.
*
* @param {*} Object the wrapping object.
* @param {string} label the action label.
* @param {String} label the action label.
* @param {registerActionCallback} callback the callback function. This function will receive
* the card as a parameter and be triggered by a click on the
* action. The card parameter will be of the format of a rich object string
* type "deck-card"
* @param {string} icon the action label. E.g. "icon-reply"
* @param {String} icon the action label. E.g. "icon-reply"
*/
window.OCA.Deck.registerCardAction = ({ label, callback, icon }) => {
const cardAction = {

View File

@@ -47,7 +47,7 @@ export default {
bodyFormData.append('cardId', this.cardId)
bodyFormData.append('type', type)
bodyFormData.append('file', file)
await queue.add(async () => {
await queue.add(async() => {
try {
await this.$store.dispatch('createAttachment', {
cardId: this.cardId,

View File

@@ -84,7 +84,7 @@ export default {
},
colorIsValid(hex) {
const re = /[A-Fa-f0-9]{6}/
const re = new RegExp('[A-Fa-f0-9]{6}')
if (re.test(hex)) {
return true
}

View File

@@ -23,8 +23,8 @@
/**
* Board model
*
* @typedef {object} Board
* @property {string} title
* @typedef {Object} Board
* @property {String} title
* @property {boolean} archived
* @property {number} shared 1 (shared) or 0 (not shared)
*/
@@ -32,8 +32,8 @@
/**
* Stack model
*
* @typedef {object} Stack
* @property {string} title
* @typedef {Object} Stack
* @property {String} title
* @property {number} boardId
* @property {number} order
*/
@@ -41,15 +41,15 @@
/**
* Card model
*
* @typedef {object} Card
* @property {string} title
* @typedef {Object} Card
* @property {String} title
* @property {boolean} archived
* @property {number} order
*/
/**
* Label model
*
* @typedef {object} Label
* @property {string} title
* @property {string} color
* @typedef {Object} Label
* @property {String} title
* @property {String} color
*/

View File

@@ -38,7 +38,7 @@ export class BoardApi {
* Updates a board.
*
* @param {Board} board the board object to update
* @return {Promise}
* @returns {Promise}
*/
updateBoard(board) {
return axios.put(this.url(`/boards/${board.id}`), board)
@@ -57,13 +57,13 @@ export class BoardApi {
/**
* Creates a new board.
*
* @typedef {object} BoardCreateObject
* @typedef {Object} BoardCreateObject
* @property {string} title
* @property {string} color
*
* @param {BoardCreateObject} boardData The board data to send.
* color the hexadecimal color value formated /[0-9A-F]{6}/i
* @return {Promise}
* @returns {Promise}
*/
createBoard(boardData) {
return axios.post(this.url('/boards'), boardData)

View File

@@ -31,7 +31,7 @@ export class CommentApi {
}
async loadComments({ cardId, limit, offset }) {
const api = await axios.get(generateOcsUrl(`apps/deck/api/v1.0/cards/${cardId}/comments`), {
const api = await axios.get(generateOcsUrl('apps/deck/api/v1.0/cards', 2) + `${cardId}/comments`, {
params: { limit, offset },
headers: { 'OCS-APIRequest': 'true' },
})
@@ -39,7 +39,7 @@ export class CommentApi {
}
async createComment({ cardId, comment, replyTo }) {
const api = await axios.post(generateOcsUrl(`apps/deck/api/v1.0/cards/${cardId}/comments`), {
const api = await axios.post(generateOcsUrl('apps/deck/api/v1.0/cards', 2) + `${cardId}/comments`, {
message: `${comment}`,
parentId: replyTo ? replyTo.id : null,
})
@@ -47,14 +47,14 @@ export class CommentApi {
}
async updateComment({ cardId, id, comment }) {
const api = await axios.put(generateOcsUrl(`apps/deck/api/v1.0/cards/${cardId}/comments/${id}`), {
const api = await axios.put(generateOcsUrl('apps/deck/api/v1.0/cards', 2) + `${cardId}/comments/${id}`, {
message: `${comment}`,
})
return api.data.ocs.data
}
async deleteComment({ cardId, id }) {
const api = await axios.delete(generateOcsUrl(`apps/deck/api/v1.0/cards/${cardId}/comments/${id}`))
const api = await axios.delete(generateOcsUrl('apps/deck/api/v1.0/cards', 2) + `${cardId}/comments/${id}`)
return api.data.ocs.data
}

View File

@@ -26,7 +26,7 @@ import { generateOcsUrl } from '@nextcloud/router'
export class OverviewApi {
url(url) {
return generateOcsUrl(`apps/deck/api/v1.0/${url}`)
return generateOcsUrl('apps/deck/api/v1.0') + url
}
get(filter) {

View File

@@ -22,7 +22,7 @@
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
const shareUrl = generateOcsUrl('apps/files_sharing/api/v1/shares')
const shareUrl = generateOcsUrl('apps/files_sharing/api/v1', 2) + 'shares'
const createShare = async function({ path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label }) {
try {

Some files were not shown because too many files have changed in this diff Show More