Compare commits

..
Author SHA1 Message Date
Luka Trovic 386131774d fix: update comment input ui
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-25 14:20:09 +01:00
Luka Trovic 97b7c1a2f2 Merge branch 'master' of next.github.com:nextcloud/deck into feature/update-card-modal-ui 2022-03-23 13:22:06 +01:00
Luka Trovic 10a1c68917 feat: show title and tab buttons at the top while scrolling
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-23 13:18:40 +01:00
ValdnetandGitHub b10dbf8097 Merge pull request #3666 from nextcloud/Valdnet-patch-1
l10n: Delete space
2022-03-23 09:10:33 +01:00
ValdnetandGitHub 7a5eca2dee l10n: Delete space
Signed-off-by: Valdnet <47037905+Valdnet@users.noreply.github.com>
2022-03-23 08:49:58 +01:00
Julius HärtlandGitHub b9e428807d Merge pull request #3560 from Ben-Ro/master
Sort boards non case sensitive
2022-03-22 09:42:51 +01:00
Julius HärtlandGitHub 48cd27594f Merge pull request #3650 from nextcloud/bugfix/3649
Handle description shortening more gracefully
2022-03-22 09:02:40 +01:00
benandJulius Härtl 62d68edc17 fixes nextcloud/deck#3410
Signed-off-by: ben <git@rott.io>
2022-03-22 08:51:53 +01:00
Julius Härtl 48db989781 Handle description shortening more gracefully
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:48:32 +01:00
Julius HärtlandGitHub 4027f407f7 Merge pull request #2496 from nextcloud/transfer-ownership
Transfer ownership
2022-03-22 08:43:17 +01:00
Julius Härtl c6aef45d8c Adjust documentaion wording
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:23:30 +01:00
Julius Härtl 23f0b16a5a Handle board exceptions more gracefully
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:23:30 +01:00
Julius Härtl 3f29cd97db Cover case where the owner is preserved
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:23:30 +01:00
Luka TrovicandJulius Härtl 9f1dbd137c fix: feedback
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-22 08:23:29 +01:00
Luka TrovicandJulius Härtl bf9a51d167 feat: add api endpoint and UI to transfer a board to a different user
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-22 08:23:29 +01:00
Julius Härtl 3a4ec07103 fix: test cases using generator
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:23:29 +01:00
Julius Härtl 4f13977851 Reuse single board transfer for all user boards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:23:29 +01:00
Julius Härtl c2144373d9 fix: Properly handle limited scope for remapping users
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:23:29 +01:00
Julius Härtl a032287cb5 cleanup test cases
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:23:29 +01:00
Julius Härtl a45e46f80a Allow transfer of single boards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:23:29 +01:00
Julius Härtl b774090032 fix: Psalm
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:23:28 +01:00
Julius Härtl 72134e6e95 fix: unit tests
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:23:28 +01:00
Luka TrovicandJulius Härtl e4551bde15 feat: add integration test for transferring board ownership with data
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-22 08:23:28 +01:00
Luka TrovicandJulius Härtl 4615926e3b fix: integration tests
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-22 08:23:28 +01:00
Luka TrovicandJulius Härtl afbbdf0c1b fix: unit test & psalm static code analysis issues
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-22 08:23:28 +01:00
MaxandJulius Härtl b6340e54c3 fix: queries with the new base mapper in BoardMapper
Signed-off-by: Max <max@nextcloud.com>
2022-03-22 08:23:28 +01:00
MaxandJulius Härtl 4d3dabb94e fix: Assignment is the new AssignedUsers
Signed-off-by: Max <max@nextcloud.com>
2022-03-22 08:23:27 +01:00
Julius Härtl e8ada52c37 Make queries work with the new base mapper
Signed-off-by: Julius Härtl <jus@bitgrid.net>

fix: conflicts
2022-03-22 08:23:27 +01:00
Julius Härtl fa7fcef470 Just cleanup old ACL rules, there are none for the board owner so nothing to cleanup or persist there
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:23:27 +01:00
Julius Härtl 3e7d0d3d72 Use proper description of what gets transferred
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:23:27 +01:00
Julius Härtl ba7cadf9d5 Fix card mapper query for transfer
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-22 08:23:27 +01:00
Sergey ShliakhovandJulius Härtl 6106066460 Fix coding styles
Signed-off-by: Julius Härtl <jus@bitgrid.net>
Signed-off-by: Max <max@nextcloud.com>
2022-03-22 08:23:27 +01:00
Sergey ShliakhovandJulius Härtl 7df4b7c4bf Transfer deck ownership even if target user already participant of a board
https://github.com/nextcloud/deck/pull/1955#issuecomment-640392715
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>
2022-03-22 08:23:27 +01:00
Sergey ShliakhovandJulius Härtl 8b45495214 Check type before transfer card participants ownership
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>

temp
2022-03-22 08:23:26 +01:00
Sergey ShliakhovandJulius Härtl e3750a709d Fix wrong class name
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>
2022-03-22 08:23:26 +01:00
Sergey ShliakhovandJulius Härtl b45c454ce2 Fix code style
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>
2022-03-22 08:23:26 +01:00
Sergey ShliakhovandJulius Härtl 3d269e28f4 Add tests
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>
2022-03-22 08:23:26 +01:00
Sergey ShliakhovandJulius Härtl 19a2aeb5e5 Update docs
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>

fix: conflicts
2022-03-22 08:23:26 +01:00
Sergey ShliakhovandJulius Härtl 5d0b8d878b Add deck:transfer-ownership command
Signed-off-by: Sergey Shliakhov <husband.sergey@gmail.com>
2022-03-22 08:23:26 +01:00
Julius HärtlandGitHub 3aad01497a Merge pull request #3653 from nextcloud/feature/show-error-on-boad-fetchdata
🚸 Shows error on board fetchData
2022-03-21 14:56:43 +01:00
Vinicius ReisandJulius Härtl 2e94b8e048 🚸 show error on board fetchData
Signed-off-by: Vinicius Reis <vinicius.reis@nextcloud.com>
2022-03-21 13:47:59 +01:00
Luka Trovic 83b739d117 style: update modal UI styles
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-21 09:12:43 +01:00
Luka Trovic eef33ac220 Merge branch 'master' of next.github.com:nextcloud/deck into feature/update-card-modal-ui 2022-03-21 09:08:40 +01:00
dependabot[bot]andGitHub 6cd8173e98 Merge pull request #3657 from nextcloud/dependabot/npm_and_yarn/master/babel/runtime-7.17.8 2022-03-19 09:34:59 +00:00
dependabot[bot]andGitHub 5333b96b37 Bump @babel/runtime from 7.17.2 to 7.17.8
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.17.2 to 7.17.8.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.17.8/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-19 09:25:11 +00:00
dependabot[bot]andGitHub 27b06c545d Merge pull request #3656 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/moment-1.2.0 2022-03-19 09:24:20 +00:00
dependabot[bot]andGitHub 64c3cf4a42 Bump @nextcloud/moment from 1.1.1 to 1.2.0
Bumps [@nextcloud/moment](https://github.com/nextcloud/nextcloud-moment) from 1.1.1 to 1.2.0.
- [Release notes](https://github.com/nextcloud/nextcloud-moment/releases)
- [Changelog](https://github.com/nextcloud/nextcloud-moment/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/nextcloud-moment/compare/v1.1.1...v1.2.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/moment"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-19 07:54:37 +00:00
dependabot[bot]andGitHub 046458081c Merge pull request #3658 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/vue-5.1.1 2022-03-19 07:53:42 +00:00
dependabot[bot]andGitHub 3cabfbca06 Bump @nextcloud/vue from 5.1.0 to 5.1.1
Bumps [@nextcloud/vue](https://github.com/nextcloud/nextcloud-vue) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/nextcloud/nextcloud-vue/releases)
- [Changelog](https://github.com/nextcloud/nextcloud-vue/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/nextcloud-vue/compare/v5.1.0...v5.1.1)

---
updated-dependencies:
- dependency-name: "@nextcloud/vue"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-19 05:44:55 +00:00
dependabot[bot]andGitHub 24cb0ea94b Merge pull request #3654 from nextcloud/dependabot/composer/phpunit/phpunit-9.5.19 2022-03-19 05:44:02 +00:00
dependabot[bot]andGitHub 4d4a91c70f Bump phpunit/phpunit from 9.5.18 to 9.5.19
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.5.18 to 9.5.19.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/master/ChangeLog-9.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.5.18...9.5.19)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-19 03:14:21 +00:00
dependabot[bot]andGitHub e1006d9df7 Merge pull request #3655 from nextcloud/dependabot/npm_and_yarn/master/relative-ci/agent-3.1.2 2022-03-19 03:13:30 +00:00
dependabot[bot]andGitHub d1d74bee1f Bump @relative-ci/agent from 3.1.1 to 3.1.2
Bumps [@relative-ci/agent](https://github.com/relative-ci/agent) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/relative-ci/agent/releases)
- [Commits](https://github.com/relative-ci/agent/compare/v3.1.1...v3.1.2)

---
updated-dependencies:
- dependency-name: "@relative-ci/agent"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-19 02:02:34 +00:00
Nextcloud bot 2aed4c1cc5 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-18 02:38:29 +00:00
Nextcloud bot 12adc6a691 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-17 02:41:16 +00:00
Julius HärtlandGitHub f37f9b57a1 Merge pull request #3635 from nextcloud/bugfix/fix-missing-file-sidebar
🐛 Fix missing files sidebar
2022-03-12 11:41:15 +01:00
Vinicius ReisandGitHub 4b39c57968 Merge branch 'master' into bugfix/fix-missing-file-sidebar 2022-03-11 23:43:59 -03:00
dependabot[bot]andGitHub 682e50d86d Merge pull request #3636 from nextcloud/dependabot/composer/phpunit/phpunit-9.5.18 2022-03-12 02:26:12 +00:00
dependabot[bot]andGitHub 35a8f97c3f Bump phpunit/phpunit from 9.5.16 to 9.5.18
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.5.16 to 9.5.18.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/master/ChangeLog-9.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.5.16...9.5.18)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-12 02:02:17 +00:00
Vinicius Reis ca3997f12e 🐛 Fix missing files sidebar
Signed-off-by: Vinicius Reis <vinicius.reis@nextcloud.com>
2022-03-11 11:52:06 -03:00
Luka TrovicandGitHub 9ef81e1dac Merge pull request #3611 from nextcloud/bug/generate-fixed-link-for-activity-emails
Generate fixed link for activity emails
2022-03-09 18:23:57 +01:00
Luka TrovicandJulius Härtl 165375fbab fix: generate fixed link for activity emails
Signed-off-by: Luka Trovic <luka@nextcloud.com>

fix: generate fixed link for activity emails

Signed-off-by: Luka Trovic <luka@nextcloud.com>

Fix tests

Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-03-09 16:57:31 +01:00
Julius HärtlandGitHub 4b622d85ee Merge pull request #3623 from nextcloud/feat/workflow-auto-update-command-rebase.yml
Updating command-rebase.yml workflow from template
2022-03-05 11:21:20 +01:00
Julius HärtlandGitHub 075f8418e7 Merge pull request #3621 from nextcloud/dependabot/github_actions/actions/checkout-3
Bump actions/checkout from 2.4.0 to 3
2022-03-05 11:21:00 +01:00
Nextcloud bot a363ebc986 Updating command-rebase.yml workflow from template
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-03-05 08:24:07 +00:00
dependabot[bot]andGitHub 5364990e9d Bump actions/checkout from 2.4.0 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.0 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.4.0...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-05 07:19:51 +00:00
dependabot[bot]andGitHub bd71a929b1 Merge pull request #3620 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/vue-5.1.0 2022-03-05 07:19:11 +00:00
dependabot[bot]andGitHub 7886b00332 Bump @nextcloud/vue from 5.0.0 to 5.1.0
Bumps [@nextcloud/vue](https://github.com/nextcloud/nextcloud-vue) from 5.0.0 to 5.1.0.
- [Release notes](https://github.com/nextcloud/nextcloud-vue/releases)
- [Changelog](https://github.com/nextcloud/nextcloud-vue/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/nextcloud-vue/compare/v5.0.0...v5.1.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/vue"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-05 06:03:54 +00:00
dependabot[bot]andGitHub 0ee3e32d97 Merge pull request #3622 from nextcloud/dependabot/github_actions/shivammathur/setup-php-2.17.1 2022-03-05 06:03:01 +00:00
dependabot[bot]andGitHub 6f954fa7a5 Bump shivammathur/setup-php from 2.17.0 to 2.17.1
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.17.0 to 2.17.1.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.17.0...2.17.1)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-05 03:15:48 +00:00
dependabot[bot]andGitHub a0a8c32ee4 Merge pull request #3619 from nextcloud/dependabot/composer/vimeo/psalm-4.22.0 2022-03-05 03:15:03 +00:00
dependabot[bot]andGitHub 000811ebfd Bump vimeo/psalm from 4.21.0 to 4.22.0
Bumps [vimeo/psalm](https://github.com/vimeo/psalm) from 4.21.0 to 4.22.0.
- [Release notes](https://github.com/vimeo/psalm/releases)
- [Commits](https://github.com/vimeo/psalm/compare/4.21.0...4.22.0)

---
updated-dependencies:
- dependency-name: vimeo/psalm
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-05 02:01:29 +00:00
Julius HärtlandGitHub 9629627d6d Create appstore-build-publish.yml 2022-03-04 13:55:28 +01:00
Julius HärtlandGitHub 9bbef1fe42 Merge pull request #3552 from nextcloud/fix/return-selector
return the selector for collections
2022-03-04 09:53:19 +01:00
Julius HärtlandGitHub 5bd608595a Merge pull request #3612 from nextcloud/bug/make-insert-attachment-button-clickable
Make insert attachment buttom easy to click
2022-03-02 17:23:34 +01:00
Julius HärtlandGitHub 0df048da5c Merge pull request #3613 from elitejake/patch-1
Remove extra bullet
2022-03-02 15:46:29 +01:00
elitejakeandGitHub 0b22224046 Remove extra bullet 2022-03-02 14:39:56 +00:00
Luka Trovic 6040172cd7 fix: make insert attachment buttom easy to click
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-01 15:19:36 +01:00
Julius HärtlandGitHub 26c3529a5c Merge pull request #3610 from nextcloud/dependabot/github_actions/actions/setup-node-3
Bump actions/setup-node from 2.5.1 to 3
2022-02-28 09:13:21 +01:00
dependabot[bot]andGitHub 9c6dd11aa2 Bump actions/setup-node from 2.5.1 to 3
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2.5.1 to 3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2.5.1...v3)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-26 07:05:53 +00:00
dependabot[bot]andGitHub 4d2e3a6500 Merge pull request #3609 from nextcloud/dependabot/composer/phpunit/phpunit-9.5.16 2022-02-26 07:05:12 +00:00
dependabot[bot]andGitHub f5d6ad73dd Bump phpunit/phpunit from 9.5.14 to 9.5.16
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.5.14 to 9.5.16.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/master/ChangeLog-9.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.5.14...9.5.16)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-26 05:18:46 +00:00
dependabot[bot]andGitHub ad3fc4b2dd Merge pull request #3607 from nextcloud/dependabot/composer/vimeo/psalm-4.21.0 2022-02-26 05:17:59 +00:00
dependabot[bot]andGitHub f1a3cd1867 Bump vimeo/psalm from 4.20.0 to 4.21.0
Bumps [vimeo/psalm](https://github.com/vimeo/psalm) from 4.20.0 to 4.21.0.
- [Release notes](https://github.com/vimeo/psalm/releases)
- [Commits](https://github.com/vimeo/psalm/compare/4.20.0...4.21.0)

---
updated-dependencies:
- dependency-name: vimeo/psalm
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-26 03:48:37 +00:00
dependabot[bot]andGitHub fdaae15328 Merge pull request #3608 from nextcloud/dependabot/composer/php-parallel-lint/php-parallel-lint-1.3.2 2022-02-26 03:47:55 +00:00
dependabot[bot]andGitHub e83a993ebc Bump php-parallel-lint/php-parallel-lint from 1.3.1 to 1.3.2
Bumps [php-parallel-lint/php-parallel-lint](https://github.com/php-parallel-lint/PHP-Parallel-Lint) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/php-parallel-lint/PHP-Parallel-Lint/releases)
- [Changelog](https://github.com/php-parallel-lint/PHP-Parallel-Lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.3.1...v1.3.2)

---
updated-dependencies:
- dependency-name: php-parallel-lint/php-parallel-lint
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-26 02:02:16 +00:00
dependabot[bot]andGitHub 4f23ebedb6 Merge pull request #3605 from nextcloud/dependabot/npm_and_yarn/striptags-3.2.0 2022-02-25 09:15:04 +00:00
dependabot[bot]andGitHub 6e8ca6c067 Bump striptags from 3.1.1 to 3.2.0
Bumps [striptags](https://github.com/ericnorris/striptags) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/ericnorris/striptags/releases)
- [Commits](https://github.com/ericnorris/striptags/compare/v3.1.1...v3.2.0)

---
updated-dependencies:
- dependency-name: striptags
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-25 09:02:52 +00:00
dependabot[bot]andGitHub 6d6a31037e Merge pull request #3604 from nextcloud/dependabot/npm_and_yarn/path-parse-1.0.7 2022-02-25 09:02:03 +00:00
dependabot[bot]andGitHub 7814d59e0c Bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-25 08:52:33 +00:00
Julius HärtlandGitHub cc2ff38320 Merge pull request #3603 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/webpack-vue-config-5.0.0
Bump @nextcloud/webpack-vue-config from 4.3.2 to 5.0.0
2022-02-25 09:51:50 +01:00
dependabot[bot]andGitHub a43de8ec29 Bump @nextcloud/webpack-vue-config from 4.3.2 to 5.0.0
Bumps [@nextcloud/webpack-vue-config](https://github.com/nextcloud/webpack-vue-config) from 4.3.2 to 5.0.0.
- [Release notes](https://github.com/nextcloud/webpack-vue-config/releases)
- [Changelog](https://github.com/nextcloud/webpack-vue-config/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/webpack-vue-config/compare/v4.3.2...v5.0.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/webpack-vue-config"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-25 08:35:52 +00:00
Julius HärtlandGitHub a1bd914b48 Merge pull request #3599 from nextcloud/enh/time-insensitive-cron
Move DeleteCron to be time insensitive
2022-02-24 16:16:52 +01:00
Julius Härtl e42ffa4ca2 Move DeleteCron to be time insensitive
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-02-24 14:08:46 +01:00
dependabot[bot]andGitHub 8c33760c79 Merge pull request #3594 from nextcloud/dependabot/npm_and_yarn/master/dompurify-2.3.6 2022-02-19 05:24:51 +00:00
dependabot[bot]andGitHub c5a9a0897e Bump dompurify from 2.3.5 to 2.3.6
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.3.5 to 2.3.6.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/2.3.5...2.3.6)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-19 05:07:11 +00:00
dependabot[bot]andGitHub 1e32a01be4 Merge pull request #3593 from nextcloud/dependabot/composer/phpunit/phpunit-9.5.14 2022-02-19 05:06:17 +00:00
dependabot[bot]andGitHub d44f489173 Bump phpunit/phpunit from 9.5.13 to 9.5.14
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.5.13 to 9.5.14.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/master/ChangeLog-9.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.5.13...9.5.14)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-19 03:26:27 +00:00
Nextcloud bot cfa67f1cbd [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-19 02:37:58 +00:00
Nextcloud bot 97aa71e1fb [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-17 02:38:48 +00:00
Luka Trovic 69896d5cca feat: improve tabs behavior & style
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-02-16 09:14:59 +01:00
Luka Trovic 021d55226b fix: feedback
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-02-14 10:38:55 +01:00
Julius HärtlandGitHub c3e5cf6851 Merge pull request #3539 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/stylelint-config-2.1.2
Bump @nextcloud/stylelint-config from 1.0.0-beta.0 to 2.1.2
2022-02-13 15:17:34 +01:00
Julius HärtlandGitHub 84d3d6b834 Merge pull request #3588 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/vue-5.0.0
Bump @nextcloud/vue from 4.3.0 to 5.0.0
2022-02-13 15:17:15 +01:00
dependabot[bot]andGitHub 778f0cda64 Bump @nextcloud/vue from 4.3.0 to 5.0.0
Bumps [@nextcloud/vue](https://github.com/nextcloud/nextcloud-vue) from 4.3.0 to 5.0.0.
- [Release notes](https://github.com/nextcloud/nextcloud-vue/releases)
- [Changelog](https://github.com/nextcloud/nextcloud-vue/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/nextcloud-vue/compare/v4.3.0...v5.0.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/vue"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-12 06:02:17 +00:00
dependabot[bot]andGitHub 504916608a Bump @nextcloud/stylelint-config from 1.0.0-beta.0 to 2.1.2
Bumps [@nextcloud/stylelint-config](https://github.com/nextcloud/stylelint-config) from 1.0.0-beta.0 to 2.1.2.
- [Release notes](https://github.com/nextcloud/stylelint-config/releases)
- [Changelog](https://github.com/nextcloud/stylelint-config/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/stylelint-config/commits/v2.1.2)

---
updated-dependencies:
- dependency-name: "@nextcloud/stylelint-config"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-12 06:02:15 +00:00
dependabot[bot]andGitHub 33b99290c2 Merge pull request #3590 from nextcloud/dependabot/github_actions/shivammathur/setup-php-2.17.0 2022-02-12 06:01:13 +00:00
dependabot[bot]andGitHub 132da8cf02 Bump shivammathur/setup-php from 2.16.0 to 2.17.0
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.16.0 to 2.17.0.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.16.0...2.17.0)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-12 04:54:31 +00:00
dependabot[bot]andGitHub 3744541cd6 Merge pull request #3591 from nextcloud/dependabot/npm_and_yarn/follow-redirects-1.14.8 2022-02-12 04:53:42 +00:00
dependabot[bot]andGitHub da98ab81d6 Bump follow-redirects from 1.14.7 to 1.14.8
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-12 03:39:53 +00:00
dependabot[bot]andGitHub 9a9b64f448 Merge pull request #3589 from nextcloud/dependabot/npm_and_yarn/master/jest-27.5.1 2022-02-12 03:39:06 +00:00
dependabot[bot]andGitHub ed7e2fe1b9 Bump jest from 27.4.7 to 27.5.1
Bumps [jest](https://github.com/facebook/jest) from 27.4.7 to 27.5.1.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/compare/v27.4.7...v27.5.1)

---
updated-dependencies:
- dependency-name: jest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-12 02:04:09 +00:00
Luka Trovic 6c59f52c31 style: fix some UI issues
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-02-10 12:36:47 +01:00
Luka Trovic 7d414891f9 feat: add attachments tab 2022-02-09 22:00:44 +01:00
dependabot[bot]andGitHub 634c9d7768 Merge pull request #3583 from nextcloud/dependabot/npm_and_yarn/master/babel/runtime-7.17.2 2022-02-09 17:42:50 +00:00
dependabot[bot]andGitHub 5988975324 Bump @babel/runtime from 7.16.7 to 7.17.2
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.16.7 to 7.17.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.17.2/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-09 17:28:27 +00:00
dependabot[bot]andGitHub ffd5bd784d Merge pull request #3567 from nextcloud/dependabot/npm_and_yarn/nanoid-3.2.0 2022-02-09 17:26:55 +00:00
dependabot[bot]andGitHub 44b23688f5 Bump nanoid from 3.1.30 to 3.2.0
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.30 to 3.2.0.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.30...3.2.0)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-09 17:10:36 +00:00
dependabot[bot]andGitHub ef8c542289 Merge pull request #3575 from nextcloud/dependabot/composer/vimeo/psalm-4.20.0 2022-02-09 17:09:34 +00:00
dependabot[bot]andGitHub c0d2154e46 Bump vimeo/psalm from 4.18.1 to 4.20.0
Bumps [vimeo/psalm](https://github.com/vimeo/psalm) from 4.18.1 to 4.20.0.
- [Release notes](https://github.com/vimeo/psalm/releases)
- [Commits](https://github.com/vimeo/psalm/compare/4.18.1...4.20.0)

---
updated-dependencies:
- dependency-name: vimeo/psalm
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-09 17:00:35 +00:00
max-nextcloudandGitHub c9d34ab6df Merge pull request #3553 from nextcloud/feat/workflow-auto-update-dependabot-approve-merge.yml
Updating dependabot-approve-merge.yml workflow from template
2022-02-09 17:59:53 +01:00
Nextcloud botandmax-nextcloud 1dc62207ba Updating dependabot-approve-merge.yml workflow from template
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-09 16:36:02 +01:00
dependabot[bot]andGitHub fe007ca1d3 Merge pull request #3576 from nextcloud/dependabot/npm_and_yarn/master/relative-ci/agent-3.1.1 2022-02-09 15:02:27 +00:00
dependabot[bot]andGitHub 887b1e46e7 Bump @relative-ci/agent from 3.1.0 to 3.1.1
Bumps [@relative-ci/agent](https://github.com/relative-ci/agent) from 3.1.0 to 3.1.1.
- [Release notes](https://github.com/relative-ci/agent/releases)
- [Commits](https://github.com/relative-ci/agent/compare/v3.1.0...v3.1.1)

---
updated-dependencies:
- dependency-name: "@relative-ci/agent"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-09 14:11:34 +00:00
Julius HärtlandGitHub f04fa03baa Merge pull request #3582 from nextcloud/update-master-php-enviroment
Update master php enviroment
2022-02-09 15:10:29 +01:00
Joas Schilling 106bc7f47f Update master php enviroment
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-02-09 11:37:29 +01:00
Luka Trovic 3cd94f330f feat: project and comments tabs
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-02-08 10:42:54 +01:00
Nextcloud bot 4b5b4badd4 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-08 02:38:03 +00:00
Luka Trovic 00a3c8e20f Merge branch 'master' of next.github.com:nextcloud/deck into feature/update-card-modal-ui 2022-02-06 10:56:31 -08:00
Nextcloud bot f353f1cdd4 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-06 02:36:57 +00:00
Nextcloud bot 1d7aa5d1f0 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-05 02:37:25 +00:00
Luka Trovic 309fbc735c Merge branch 'master' of next.github.com:nextcloud/deck into feature/update-card-modal-ui 2022-02-04 15:28:52 +01:00
Luka Trovic bcaa74b33f feat: add due date tab
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-02-04 15:24:03 +01:00
Nextcloud bot 69ff35653e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-02-01 02:37:23 +00:00
Luka Trovic 427f960c80 add tags tab
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-31 15:51:47 +01:00
Julius HärtlandGitHub 2c487a4375 Merge pull request #3566 from nextcloud/dependabot/npm_and_yarn/master/relative-ci/agent-3.1.0
Bump @relative-ci/agent from 3.0.0 to 3.1.0
2022-01-31 12:05:35 +01:00
Julius HärtlandGitHub 6cc2ef684f Merge pull request #3550 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/axios-1.9.0
Bump @nextcloud/axios from 1.8.0 to 1.9.0
2022-01-31 12:05:08 +01:00
dependabot[bot]andGitHub fb1177fa76 Bump @nextcloud/axios from 1.8.0 to 1.9.0
Bumps [@nextcloud/axios](https://github.com/nextcloud/nextcloud-axios) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/nextcloud/nextcloud-axios/releases)
- [Changelog](https://github.com/nextcloud/nextcloud-axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/nextcloud-axios/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/axios"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-31 09:26:22 +00:00
dependabot[bot]andGitHub 6347fee2a1 Bump @relative-ci/agent from 3.0.0 to 3.1.0
Bumps [@relative-ci/agent](https://github.com/relative-ci/agent) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/relative-ci/agent/releases)
- [Commits](https://github.com/relative-ci/agent/compare/v3.0.0...v3.1.0)

---
updated-dependencies:
- dependency-name: "@relative-ci/agent"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-31 09:26:20 +00:00
Julius HärtlandGitHub e12b6658ec Merge pull request #3561 from nextcloud/update-master-php-testing-versions
Update master php testing versions
2022-01-31 10:24:52 +01:00
Nextcloud bot e23027094f [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-31 02:37:23 +00:00
dependabot[bot]andGitHub 89f31635c5 Merge pull request #3565 from nextcloud/dependabot/npm_and_yarn/master/dompurify-2.3.5 2022-01-29 05:18:16 +00:00
dependabot[bot]andGitHub 2cfb107167 Bump dompurify from 2.3.4 to 2.3.5
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.3.4 to 2.3.5.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/2.3.4...2.3.5)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-29 04:31:09 +00:00
dependabot[bot]andGitHub 5d562db408 Merge pull request #3568 from nextcloud/dependabot/npm_and_yarn/node-fetch-2.6.7 2022-01-29 04:30:15 +00:00
dependabot[bot]andGitHub 9993a198b1 Bump node-fetch from 2.6.1 to 2.6.7
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-29 03:23:16 +00:00
dependabot[bot]andGitHub cf7c327816 Merge pull request #3563 from nextcloud/dependabot/composer/phpunit/phpunit-9.5.13 2022-01-29 03:22:33 +00:00
dependabot[bot]andGitHub f2c1b42811 Bump phpunit/phpunit from 9.5.12 to 9.5.13
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.5.12 to 9.5.13.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/master/ChangeLog-9.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.5.12...9.5.13)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-29 02:01:10 +00:00
Joas Schilling b40f8609be Update master php testing versions
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-01-28 09:24:29 +01:00
Luka Trovic 3ad4fbd96c feat: add members tab
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-27 13:05:43 +01:00
Luka Trovic 49dccb6199 feat: add a new CardModal component
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-25 15:54:36 +01:00
Nextcloud bot c73be045c3 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-24 02:37:08 +00:00
dartcafe a6516f07fe return the selector for collections
Signed-off-by: dartcafe <github@dartcafe.de>
2022-01-22 09:30:12 +01:00
dependabot[bot]andGitHub a7243f7573 Merge pull request #3551 from nextcloud/dependabot/composer/phpunit/phpunit-9.5.12 2022-01-22 06:33:09 +00:00
dependabot[bot]andGitHub ad7f4aa22b Bump phpunit/phpunit from 9.5.11 to 9.5.12
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.5.11 to 9.5.12.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/master/ChangeLog-9.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.5.11...9.5.12)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-22 03:29:38 +00:00
dependabot[bot]andGitHub bc263b6da5 Merge pull request #3548 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/webpack-vue-config-4.3.2 2022-01-22 03:28:52 +00:00
dependabot[bot]andGitHub a3953344b5 Bump @nextcloud/webpack-vue-config from 4.3.0 to 4.3.2
Bumps [@nextcloud/webpack-vue-config](https://github.com/nextcloud/webpack-vue-config) from 4.3.0 to 4.3.2.
- [Release notes](https://github.com/nextcloud/webpack-vue-config/releases)
- [Changelog](https://github.com/nextcloud/webpack-vue-config/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/webpack-vue-config/compare/v4.3.0...v4.3.2)

---
updated-dependencies:
- dependency-name: "@nextcloud/webpack-vue-config"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-22 02:18:24 +00:00
Luka TrovicandGitHub 99973b4501 Merge pull request #3530 from nextcloud/bug/improve-contrast-link-in-markdown-note
Make links in markdown note bolder
2022-01-21 14:07:10 +01:00
Luka Trovic 4e31332fe4 fix: make links in markdown note bolder
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-21 13:30:35 +01:00
Julien VeyssierandGitHub 51dffdacd6 Merge pull request #3532 from nextcloud/feature/timestamps-on-created-and-modified-at-values
Timestamps on created and modified at values
2022-01-20 10:00:10 +01:00
Luka Trovic 054c5aaf8c feat: timestamps on created and modified at values
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-20 09:22:37 +01:00
Julien VeyssierandGitHub 4f49254cfe Merge pull request #3531 from nextcloud/bug/missing-horizontal-scrollbar-in-large-table-inside-description
Add horizontal scrollbar into the large table inside description
2022-01-19 12:13:38 +01:00
Luka TrovicandJulien Veyssier (Rebase PR Action) a11bf5f4ba fix: add horizontal scrollbar into the large table inside description
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-19 10:16:43 +00:00
Nextcloud bot 9d3de1e576 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-19 02:38:10 +00:00
Julien VeyssierandGitHub b00c8a1196 Merge pull request #3541 from nextcloud/fix/check-stack-permission
Fix confusion between stackId and boardId in StackService
2022-01-18 14:28:26 +01:00
Julien Veyssier ee3eb99cc7 fix confusion between stackId and boardId in StackService::update()
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
2022-01-18 11:24:31 +01:00
Nextcloud bot b73462555b [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-18 02:56:01 +00:00
Julius HärtlandGitHub 24c1687857 Merge pull request #3499 from nextcloud/feature/show-board-name-in-browser-title
Show board name in browser title
2022-01-14 23:15:59 +01:00
Julius HärtlandGitHub 123a5b8b13 Merge pull request #3529 from nextcloud/bugfix/noid/fix-talk-integration
Fix talk integration
2022-01-14 22:11:04 +01:00
Julius HärtlandGitHub a048d40cd7 Merge pull request #3528 from nextcloud/enh/calendar-global-setting
Add app config to toggle the default calendar setting as an admin
2022-01-14 21:48:55 +01:00
Luka Trovic b26f61b9a3 show board name in browser title
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-14 17:11:31 +01:00
Nextcloud bot bf7203210c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-13 02:37:06 +00:00
Joas Schilling 53d70321da Fix talk integration
title and description where not populated and also opposed to documentation the link is not absolute

Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-01-12 16:20:31 +01:00
Julius Härtl c070b18b1b Add app config to toggle the default calendar setting as an admin
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-01-12 13:43:50 +01:00
Nextcloud bot 3446c4aa4f [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-12 02:37:22 +00:00
Julius HärtlandGitHub 19dad95c55 Merge pull request #3522 from nextcloud/docs/api-changelog
Extend API changelog
2022-01-11 19:06:50 +01:00
Julius HärtlandGitHub fc58528817 Merge pull request #3500 from nextcloud/bugfix/3312 2022-01-11 09:26:37 +01:00
Julius Härtl ced39c9501 Extend API changelog
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-01-11 09:13:05 +01:00
Julius HärtlandGitHub ae27e431b2 Merge pull request #3502 from nextcloud/bug/move-card-shows-deleted-boards 2022-01-11 09:00:02 +01:00
Nextcloud bot d820d57661 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-11 02:37:37 +00:00
dependabot[bot]andGitHub adb90f2a51 Merge pull request #3518 from nextcloud/dependabot/npm_and_yarn/master/markdown-it-12.3.2 2022-01-10 12:45:10 +00:00
dependabot[bot]andGitHub c1e6f22fa1 Bump markdown-it from 12.3.0 to 12.3.2
Bumps [markdown-it](https://github.com/markdown-it/markdown-it) from 12.3.0 to 12.3.2.
- [Release notes](https://github.com/markdown-it/markdown-it/releases)
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/markdown-it/markdown-it/compare/12.3.0...12.3.2)

---
updated-dependencies:
- dependency-name: markdown-it
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-10 11:54:44 +00:00
Julius HärtlandGitHub c39d07db1a Merge pull request #3519 from nextcloud/dependabot/composer/vimeo/psalm-4.18.1 2022-01-10 12:53:52 +01:00
Julius HärtlandGitHub 0ab5707c4f Merge pull request #3512 from simonspa/fix/parameterOrder 2022-01-10 12:01:49 +01:00
dependabot[bot]andGitHub a47110d6f7 Bump vimeo/psalm from 4.16.1 to 4.18.1
Bumps [vimeo/psalm](https://github.com/vimeo/psalm) from 4.16.1 to 4.18.1.
- [Release notes](https://github.com/vimeo/psalm/releases)
- [Commits](https://github.com/vimeo/psalm/compare/4.16.1...4.18.1)

---
updated-dependencies:
- dependency-name: vimeo/psalm
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-10 11:01:42 +00:00
Julius HärtlandGitHub 2d2671fd77 Merge pull request #3516 from nextcloud/dependabot/npm_and_yarn/master/markdown-it-12.3.1 2022-01-10 12:01:11 +01:00
Julius HärtlandGitHub 55cf2a6214 Merge pull request #3517 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/webpack-vue-config-4.3.0 2022-01-10 12:01:04 +01:00
Julius HärtlandGitHub e488d42935 Merge pull request #3515 from nextcloud/dependabot/npm_and_yarn/master/jest-27.4.7 2022-01-10 12:00:50 +01:00
Nextcloud bot 6d8a5bc956 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-10 02:37:40 +00:00
dependabot[bot]andGitHub 81c908a59d Bump @nextcloud/webpack-vue-config from 4.2.0 to 4.3.0
Bumps [@nextcloud/webpack-vue-config](https://github.com/nextcloud/webpack-vue-config) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/nextcloud/webpack-vue-config/releases)
- [Changelog](https://github.com/nextcloud/webpack-vue-config/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/webpack-vue-config/compare/v4.2.0...v4.3.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/webpack-vue-config"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-08 04:07:21 +00:00
Nextcloud bot 84910d3d3e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-08 02:36:39 +00:00
dependabot[bot]andGitHub 3e6ade718f Bump markdown-it from 12.3.0 to 12.3.1
Bumps [markdown-it](https://github.com/markdown-it/markdown-it) from 12.3.0 to 12.3.1.
- [Release notes](https://github.com/markdown-it/markdown-it/releases)
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/markdown-it/markdown-it/compare/12.3.0...12.3.1)

---
updated-dependencies:
- dependency-name: markdown-it
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-08 02:04:19 +00:00
dependabot[bot]andGitHub b5ed3b122a Bump jest from 27.4.5 to 27.4.7
Bumps [jest](https://github.com/facebook/jest) from 27.4.5 to 27.4.7.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/compare/v27.4.5...v27.4.7)

---
updated-dependencies:
- dependency-name: jest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-08 02:02:31 +00:00
Simon Spannagel 3dfc33378d CardApiController: Fix order of optional parameters
Signed-off-by: Simon Spannagel <simonspa@kth.se>
2022-01-07 11:31:32 +01:00
Luka Trovic e4dbd9e385 exclude deleted boards in the selection for target
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-06 09:26:15 +01:00
Nextcloud bot 61910290b9 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-06 02:38:49 +00:00
Nextcloud bot bce79c596b [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-05 02:38:14 +00:00
Nextcloud bot 6be3c3fe67 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-04 02:38:08 +00:00
Julius Härtl eaf6defe59 Move any circles API usage to internal service
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-01-03 10:23:47 +01:00
Julius Härtl bfc8222e6f Avoid blocking calendar access if something goes wrong while fetching deck entries
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-01-03 10:23:47 +01:00
Julius HärtlandGitHub 66fa241382 Merge pull request #3504 from nextcloud/dependabot/npm_and_yarn/master/babel/runtime-7.16.7 2022-01-03 08:32:27 +01:00
Julius HärtlandGitHub 7b237d8cd8 Merge pull request #3505 from nextcloud/Valdnet-patch-1 2022-01-03 08:32:11 +01:00
Nextcloud bot 5fc1aba9cd [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-03 02:38:04 +00:00
Nextcloud bot 3900a15b4b [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-02 02:37:44 +00:00
ValdnetandGitHub cf8023855b l10n: Add dot.
Signed-off-by: Valdnet <47037905+Valdnet@users.noreply.github.com>
2022-01-01 21:47:52 +01:00
dependabot[bot]andGitHub ff41bbab7b Bump @babel/runtime from 7.16.5 to 7.16.7
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.16.5 to 7.16.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.16.7/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-01 05:00:23 +00:00
dependabot[bot]andGitHub bec07726a7 Merge pull request #3503 from nextcloud/dependabot/github_actions/actions/setup-node-2.5.1 2022-01-01 04:23:38 +00:00
dependabot[bot]andGitHub c24e72f161 Bump actions/setup-node from 2.5.0 to 2.5.1
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2.5.0 to 2.5.1.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2.5.0...v2.5.1)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-01 03:26:30 +00:00
Nextcloud bot 6296ebb87c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2022-01-01 02:37:46 +00:00
Nextcloud bot a96bb277a4 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-31 02:38:03 +00:00
Julius HärtlandGitHub b58913e730 Merge pull request #3182 from vitormattos/import-from-trello 2021-12-30 11:39:35 +01:00
Vitor MattosandJulius Härtl ccd5bce7ea Replace glob by EventDispatcher
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:50 +01:00
Vitor MattosandJulius Härtl f2b6934ac3 Move all classes to a sub-namespace
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:50 +01:00
Vitor MattosandJulius Härtl 24c8b2f4aa Make error more specific
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:50 +01:00
a3959e3cfc Update lib/Service/BoardImportCommandService.php
Co-authored-by: Julien Veyssier <eneiluj@posteo.net>

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:50 +01:00
Vitor MattosandJulius Härtl fda8a03c43 Remove unused codes
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:50 +01:00
Vitor MattosandJulius Härtl 5b30577df0 Update documentation, import created
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:50 +01:00
Vitor MattosandJulius Härtl 4561887348 Add long comments with attachment
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor MattosandJulius Härtl e87c063076 Documentation, improvements on validation, refactor
Validate get boad
change pattern of api params
Import only one board by api
Populate data from api
Update class diagram
Update documentation
Add return when success
Sort comments
Fix order of cards
Instructions of attachments

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor MattosandJulius Härtl 202ea30090 Start implementing Trello API service
Implement name of system to import
Implement need validate data
Fix allowed system list
Start implementing Trello API service

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor MattosandJulius Härtl c7a37ea425 Increase documentation
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor MattosandJulius Härtl 19c609540b Default valute to board property fix command flow and refactor
Fixes on getBoard tests
Refactor
Reduce psalm info
Refactor to implement pattern
Change order of methods to put all abstract first and all public first

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor MattosandJulius Härtl 6714c89220 Remove interact from command and implement bootstrap method
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor MattosandJulius Härtl e01e4cf1a7 Create route to get json schema to validate config
Fix visibility
Make compatible with php 7.2
Remove returing instance
Increase coverage
Reduce psalm info
Throw exception if system not defined
Increment coverage

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor MattosandJulius Härtl 4138953208 Changes to make possible implement api endpoint
Update documentation
Start implementing getSystems route
Code to route getSystems
Controller to board import
Change return
Increase coverage

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor MattosandJulius Härtl 39a927de18 Clean attachment table
Clean code
Clean attachment table

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:49 +01:00
Vitor MattosandJulius Härtl c5d10dafb8 Import participants
Big refactor to create route
Import participants

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:48 +01:00
Vitor MattosandJulius Härtl fd92fc3c4d Refactor and improvements on command
Check available helpers
Default permission: view only
Moved validate setting from helper to command
Turn more easy create a importer
Docblock and improvements on interface
lcfirst on system property
Helper moved to ImporHelper folder
Moved fixtures to ImportHelper
Rename settings to config
Big refactor to move import methods to service

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:48 +01:00
Vitor MattosandJulius Härtl eb8bf3f22b Translations and tests
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:48 +01:00
Vitor MattosandJulius Härtl e28a47e9e0 Update UserExport.php
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:48 +01:00
Vitor MattosandJulius Härtl 48df98ce67 Add new command
Clean code
Add new command
Import last modified and deleted date
Replace arrow functions by lambda functions
Add properties to class
Add dependency to composer.json
Signed-off-by: Vitor Mattos <vitor@php.rio>
Turn private methods
Add output messages and associate users to cards

Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:46 +01:00
Vitor MattosandJulius Härtl 89028c74cb Command to import from Trello
Signed-off-by: Vitor Mattos <vitor@php.rio>
2021-12-30 11:19:34 +01:00
Julius HärtlandGitHub 4e5537f204 Merge pull request #3501 from nextcloud/bug/missing-translation-not-found-in-transifex 2021-12-30 09:00:20 +01:00
Luka Trovic f74bca8c43 add a missing translation - not found in transifex
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2021-12-30 08:23:14 +01:00
Julius HärtlandGitHub 30e4b43e46 Merge pull request #3424 from nextcloud/dependabot/composer/nextcloud/coding-standard-1.0.0 2021-12-29 16:24:45 +01:00
Julius Härtl 596834853b Fix codestyle issues
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-12-29 11:43:57 +01:00
dependabot[bot]andJulius Härtl eff3c94c6a Bump nextcloud/coding-standard from 0.5.0 to 1.0.0
Bumps [nextcloud/coding-standard](https://github.com/nextcloud/coding-standard) from 0.5.0 to 1.0.0.
- [Release notes](https://github.com/nextcloud/coding-standard/releases)
- [Changelog](https://github.com/nextcloud/coding-standard/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/coding-standard/compare/v0.5.0...v1.0.0)

---
updated-dependencies:
- dependency-name: nextcloud/coding-standard
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-29 11:43:56 +01:00
Julius HärtlandGitHub 8aa3edee58 Merge pull request #3429 from Themanwhosmellslikesugar/rework/store-card-details-in-modal 2021-12-29 10:47:49 +01:00
Julius HärtlandGitHub d3404c7489 Merge pull request #3495 from nextcloud/dependabot/npm_and_yarn/master/markdown-it-link-attributes-4.0.0
Bump markdown-it-link-attributes from 3.0.0 to 4.0.0
2021-12-28 22:12:21 +01:00
dependabot[bot]andGitHub 84bcd2e502 Bump markdown-it-link-attributes from 3.0.0 to 4.0.0
Bumps [markdown-it-link-attributes](https://github.com/crookedneighbor/markdown-it-link-attributes) from 3.0.0 to 4.0.0.
- [Release notes](https://github.com/crookedneighbor/markdown-it-link-attributes/releases)
- [Changelog](https://github.com/crookedneighbor/markdown-it-link-attributes/blob/main/CHANGELOG.md)
- [Commits](https://github.com/crookedneighbor/markdown-it-link-attributes/compare/v3.0.0...v4.0.0)

---
updated-dependencies:
- dependency-name: markdown-it-link-attributes
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-28 18:07:35 +00:00
Julius HärtlandGitHub f645937b10 Merge pull request #3498 from nextcloud/bugfix/noid/composer
Update composer config
2021-12-28 19:06:32 +01:00
Julius Härtl d9af04121d Update composer config
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-12-28 14:45:37 +01:00
Dmitriy IvankoandJulius Härtl 51bcbdb87d Store cardDetailsInModal in config instead of state
Signed-off-by: Dmitriy Ivanko <tmwsls12@gmail.com>
2021-12-28 14:39:25 +01:00
Julius HärtlandGitHub 25dee609b5 Merge pull request #3479 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/vue-4.3.0 2021-12-27 10:22:02 +01:00
Julius HärtlandGitHub 14ab4597c5 Merge pull request #3481 from nextcloud/dependabot/npm_and_yarn/master/markdown-it-12.3.0 2021-12-27 10:21:55 +01:00
Julius HärtlandGitHub d3146b4019 Merge pull request #3491 from nextcloud/dependabot/composer/vimeo/psalm-4.15.0 2021-12-27 10:21:48 +01:00
dependabot[bot]andGitHub 4b02be2028 Bump @nextcloud/vue from 4.2.0 to 4.3.0
Bumps [@nextcloud/vue](https://github.com/nextcloud/nextcloud-vue) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/nextcloud/nextcloud-vue/releases)
- [Changelog](https://github.com/nextcloud/nextcloud-vue/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/nextcloud-vue/compare/v4.2.0...v4.3.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/vue"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-25 03:26:42 +00:00
dependabot[bot]andGitHub 476d607148 Bump markdown-it from 12.2.0 to 12.3.0
Bumps [markdown-it](https://github.com/markdown-it/markdown-it) from 12.2.0 to 12.3.0.
- [Release notes](https://github.com/markdown-it/markdown-it/releases)
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/markdown-it/markdown-it/compare/12.2.0...12.3.0)

---
updated-dependencies:
- dependency-name: markdown-it
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-25 03:26:40 +00:00
dependabot[bot]andGitHub 99f17823ec Bump vimeo/psalm from 4.11.2 to 4.15.0
Bumps [vimeo/psalm](https://github.com/vimeo/psalm) from 4.11.2 to 4.15.0.
- [Release notes](https://github.com/vimeo/psalm/releases)
- [Commits](https://github.com/vimeo/psalm/compare/4.11.2...v4.15.0)

---
updated-dependencies:
- dependency-name: vimeo/psalm
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-25 03:26:39 +00:00
dependabot[bot]andGitHub ad537162c8 Merge pull request #3496 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/eslint-config-6.1.2 2021-12-25 03:25:45 +00:00
dependabot[bot]andGitHub 3a243b1fc7 Bump @nextcloud/eslint-config from 6.1.1 to 6.1.2
Bumps [@nextcloud/eslint-config](https://github.com/nextcloud/eslint-config) from 6.1.1 to 6.1.2.
- [Release notes](https://github.com/nextcloud/eslint-config/releases)
- [Changelog](https://github.com/nextcloud/eslint-config/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/eslint-config/compare/v6.1.1...v6.1.2)

---
updated-dependencies:
- dependency-name: "@nextcloud/eslint-config"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-25 02:03:08 +00:00
Nextcloud bot bfcbe0306e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-24 02:39:51 +00:00
Nextcloud bot d060a842b4 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-22 02:38:51 +00:00
Nextcloud bot 90a3339e18 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-20 02:40:43 +00:00
dependabot[bot]andGitHub 90287606c1 Merge pull request #3492 from nextcloud/dependabot/npm_and_yarn/master/babel/runtime-7.16.5 2021-12-18 03:41:10 +00:00
dependabot[bot]andGitHub f65b3801cc Bump @babel/runtime from 7.16.3 to 7.16.5
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.16.3 to 7.16.5.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.16.5/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-18 03:14:37 +00:00
Nextcloud bot b4f35bf2fd [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-18 02:37:53 +00:00
Nextcloud bot a857c63b35 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-17 02:39:59 +00:00
dependabot[bot]andGitHub 5802a31e93 Merge pull request #3488 from nextcloud/dependabot/npm_and_yarn/master/jest-27.4.5 2021-12-16 11:03:32 +00:00
dependabot[bot]andGitHub d459995df3 Bump jest from 27.4.4 to 27.4.5
Bumps [jest](https://github.com/facebook/jest) from 27.4.4 to 27.4.5.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/compare/v27.4.4...v27.4.5)

---
updated-dependencies:
- dependency-name: jest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-16 09:31:32 +00:00
Julius HärtlandGitHub 5be01e60fb Merge pull request #3480 from nextcloud/dependabot/npm_and_yarn/master/jest-27.4.4 2021-12-16 10:30:34 +01:00
Julius HärtlandGitHub 8443549d74 Merge pull request #3470 from nextcloud/dependabot/github_actions/actions/setup-node-2.5.0 2021-12-16 10:30:15 +01:00
Julius HärtlandGitHub 23e532a9c2 Merge pull request #3469 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/webpack-vue-config-4.2.0 2021-12-16 10:29:55 +01:00
Julius HärtlandGitHub 2a41d98c6f Merge pull request #3471 from nextcloud/dependabot/npm_and_yarn/master/jest-27.4.3 2021-12-16 10:29:44 +01:00
Julius HärtlandGitHub 4c4b8f3bed Merge pull request #3487 from nextcloud/Valdnet-patch-1 2021-12-16 08:55:16 +01:00
ValdnetandGitHub 1806f0817b l10n: Change to a capital letter
Signed-off-by: Valdnet <47037905+Valdnet@users.noreply.github.com>
2021-12-16 08:42:34 +01:00
Nextcloud bot e855ef3414 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-16 02:38:14 +00:00
Nextcloud bot 82232e8890 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-12 02:38:54 +00:00
dependabot[bot]andGitHub 99c880df18 Bump jest from 27.3.1 to 27.4.3
Bumps [jest](https://github.com/facebook/jest) from 27.3.1 to 27.4.3.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/compare/v27.3.1...v27.4.3)

---
updated-dependencies:
- dependency-name: jest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 05:21:52 +00:00
dependabot[bot]andGitHub 144ca0d39d Bump @nextcloud/webpack-vue-config from 4.1.4 to 4.2.0
Bumps [@nextcloud/webpack-vue-config](https://github.com/nextcloud/webpack-vue-config) from 4.1.4 to 4.2.0.
- [Release notes](https://github.com/nextcloud/webpack-vue-config/releases)
- [Changelog](https://github.com/nextcloud/webpack-vue-config/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/webpack-vue-config/compare/v4.1.4...v4.2.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/webpack-vue-config"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 05:21:17 +00:00
dependabot[bot]andGitHub 71fbdfeba5 Bump jest from 27.3.1 to 27.4.4
Bumps [jest](https://github.com/facebook/jest) from 27.3.1 to 27.4.4.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/compare/v27.3.1...v27.4.4)

---
updated-dependencies:
- dependency-name: jest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 05:21:08 +00:00
dependabot[bot]andGitHub 101995598b Bump actions/setup-node from 2.4.1 to 2.5.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2.4.1 to 2.5.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2.4.1...v2.5.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 05:20:48 +00:00
dependabot[bot]andGitHub f5c35729ca Merge pull request #3482 from nextcloud/dependabot/npm_and_yarn/master/dompurify-2.3.4 2021-12-11 05:20:04 +00:00
dependabot[bot]andGitHub fc1983869b Bump dompurify from 2.3.3 to 2.3.4
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.3.3 to 2.3.4.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/2.3.3...2.3.4)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 04:55:03 +00:00
Nextcloud bot 081b5119f5 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-09 02:40:03 +00:00
Nextcloud bot a22e5f7719 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-05 02:43:12 +00:00
dependabot[bot]andGitHub 8ef118ad0f Merge pull request #3468 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/eslint-config-6.1.1 2021-12-04 03:31:28 +00:00
dependabot[bot]andGitHub 86945d5030 Bump @nextcloud/eslint-config from 6.1.0 to 6.1.1
Bumps [@nextcloud/eslint-config](https://github.com/nextcloud/eslint-config) from 6.1.0 to 6.1.1.
- [Release notes](https://github.com/nextcloud/eslint-config/releases)
- [Changelog](https://github.com/nextcloud/eslint-config/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/eslint-config/compare/v6.1.0...v6.1.1)

---
updated-dependencies:
- dependency-name: "@nextcloud/eslint-config"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-04 02:02:29 +00:00
Nextcloud bot c63423c25a [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-03 02:40:11 +00:00
Nextcloud bot 8db48106b9 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-01 17:22:45 +00:00
Julius HärtlandGitHub 6d201a1f13 Merge pull request #3458 from nextcloud/bugfix/3451 2021-11-30 13:23:40 +01:00
Julius HärtlandGitHub 5425536fc0 Merge pull request #3459 from nextcloud/bugfix/3457 2021-11-30 13:20:46 +01:00
Julius Härtl 92acaa0011 Properly handle setters now that there is a default value of null with unset acl/labels
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-30 10:57:24 +01:00
Julius Härtl a5b76991b8 Fix cursor generation if no results are found
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-29 20:44:42 +01:00
Julius HärtlandGitHub 33f5af41c8 Merge pull request #3454 from nextcloud/dependabot/composer/symfony/event-dispatcher-4.4.34 2021-11-29 20:38:44 +01:00
Julius HärtlandGitHub f5223d90a0 Merge pull request #3456 from nextcloud/dependabot/github_actions/shivammathur/setup-php-2.16.0 2021-11-29 20:38:29 +01:00
Julius HärtlandGitHub 4a51335a28 Merge pull request #3455 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/axios-1.8.0 2021-11-29 20:38:18 +01:00
Nextcloud bot e5ffe95c17 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-27 02:37:08 +00:00
dependabot[bot]andGitHub 3096b701b6 Bump shivammathur/setup-php from 2.15.0 to 2.16.0
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.15.0 to 2.16.0.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.15.0...2.16.0)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-27 02:04:50 +00:00
dependabot[bot]andGitHub caf2e688f7 Bump @nextcloud/axios from 1.7.0 to 1.8.0
Bumps [@nextcloud/axios](https://github.com/nextcloud/nextcloud-axios) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/nextcloud/nextcloud-axios/releases)
- [Changelog](https://github.com/nextcloud/nextcloud-axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/nextcloud-axios/compare/v1.7.0...v1.8.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/axios"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-27 02:02:57 +00:00
dependabot[bot]andGitHub 3269845cfd Bump symfony/event-dispatcher from 4.4.30 to 4.4.34
Bumps [symfony/event-dispatcher](https://github.com/symfony/event-dispatcher) from 4.4.30 to 4.4.34.
- [Release notes](https://github.com/symfony/event-dispatcher/releases)
- [Changelog](https://github.com/symfony/event-dispatcher/blob/5.3/CHANGELOG.md)
- [Commits](https://github.com/symfony/event-dispatcher/compare/v4.4.30...v4.4.34)

---
updated-dependencies:
- dependency-name: symfony/event-dispatcher
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-27 02:01:03 +00:00
Nextcloud bot 5349fcc707 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-26 02:57:38 +00:00
Nextcloud bot 0f095e9b69 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-25 02:36:02 +00:00
Julius HärtlandGitHub 3affa7b5ec Merge pull request #3444 from nextcloud/enh/cache-frequent-queries 2021-11-24 15:06:37 +01:00
Julius Härtl 4ec57d337b Keep API results the same as before
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-24 10:12:28 +01:00
Julius HärtlandGitHub 6fd83258a0 Merge pull request #3365 from nextcloud/enh/use-qbmapper-for-boards 2021-11-24 09:40:50 +01:00
Julius Härtl 901b8f2506 Avoid fetching board details multiple times
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-24 09:40:30 +01:00
Julius Härtl 80388d1a88 Cache card to board id relation
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-24 09:40:30 +01:00
Nextcloud bot f90c9602b8 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-24 02:36:58 +00:00
Julius Härtl f861f9e5fc Update psalm baseline
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-23 23:44:50 +01:00
Julien VeyssierandJulius Härtl bbfb155802 switch to QBMapper in BoardMapper
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
2021-11-23 22:57:50 +01:00
Julius HärtlandGitHub 10ab8c8688 Merge pull request #3428 from nextcloud/bugfix/download-attachment 2021-11-22 18:39:58 +01:00
Julius HärtlandGitHub 24a6d088ca Merge pull request #3436 from nextcloud/dependabot/npm_and_yarn/master/vue/test-utils-1.3.0 2021-11-22 16:39:02 +01:00
Julius Härtl 00d386dcaf Allow to download an attachment without navigating to the files app
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-22 16:38:28 +01:00
Nextcloud bot b71f91c439 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-20 02:53:14 +00:00
dependabot[bot]andGitHub 7aa35bb728 Bump @vue/test-utils from 1.2.2 to 1.3.0
Bumps [@vue/test-utils](https://github.com/vuejs/vue-test-utils/tree/HEAD/packages/test-utils) from 1.2.2 to 1.3.0.
- [Release notes](https://github.com/vuejs/vue-test-utils/releases)
- [Changelog](https://github.com/vuejs/vue-test-utils/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-test-utils/commits/v1.3.0/packages/test-utils)

---
updated-dependencies:
- dependency-name: "@vue/test-utils"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-20 02:04:23 +00:00
Julius HärtlandGitHub 800412237d Merge pull request #3432 from nextcloud/bugfix/noid/description-save 2021-11-19 10:14:25 +01:00
Julius Härtl ca411c6168 Fix event name for updating the description
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-11-19 08:32:40 +01:00
Nextcloud bot d414ffe937 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-19 02:53:33 +00:00
Julius HärtlandGitHub ad483f3613 Merge pull request #3425 from nextcloud/dependabot/npm_and_yarn/master/babel/runtime-7.16.3
Bump @babel/runtime from 7.16.0 to 7.16.3
2021-11-15 07:21:51 +01:00
Julius HärtlandGitHub 8311a13275 Merge pull request #3427 from nextcloud/dependabot/npm_and_yarn/master/nextcloud/webpack-vue-config-4.1.4
Bump @nextcloud/webpack-vue-config from 4.1.2 to 4.1.4
2021-11-15 07:21:38 +01:00
Nextcloud bot e2a7063772 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-15 02:35:06 +00:00
Nextcloud bot 1dbf36ae07 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-14 02:36:19 +00:00
Nextcloud bot 29278a51e5 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-13 02:36:41 +00:00
dependabot[bot]andGitHub 31e48ce404 Bump @nextcloud/webpack-vue-config from 4.1.2 to 4.1.4
Bumps [@nextcloud/webpack-vue-config](https://github.com/nextcloud/webpack-vue-config) from 4.1.2 to 4.1.4.
- [Release notes](https://github.com/nextcloud/webpack-vue-config/releases)
- [Changelog](https://github.com/nextcloud/webpack-vue-config/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/webpack-vue-config/compare/v4.1.2...v4.1.4)

---
updated-dependencies:
- dependency-name: "@nextcloud/webpack-vue-config"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-13 02:04:54 +00:00
dependabot[bot]andGitHub 404a7eb412 Bump @babel/runtime from 7.16.0 to 7.16.3
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.16.0 to 7.16.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.16.3/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-13 02:02:03 +00:00
Julius HärtlandGitHub a85a6db368 Merge pull request #3420 from nextcloud/feat/package-node-npm-engines-update 2021-11-11 16:12:42 +01:00
Nextcloud bot 9464337036 Update npm and node engines versions
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-11 13:36:52 +00:00
Vincent PetryandGitHub 87676b49dd Merge pull request #3419 from nextcloud/update-master-version
Update version on master
2021-11-11 11:53:23 +01:00
Joas Schilling 6248089d8b Update version on master
Signed-off-by: Joas Schilling <coding@schilljs.com>
2021-11-11 10:51:16 +01:00
105 changed files with 11245 additions and 5186 deletions
+3 -3
View File
@@ -12,15 +12,15 @@ jobs:
node-version: [14.x]
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v3
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@2.17.1
with:
php-version: '7.4'
tools: composer
+1 -1
View File
@@ -66,7 +66,7 @@ jobs:
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
- name: Set up php ${{ env.PHP_VERSION }}
uses: shivammathur/setup-php@v2
uses: shivammathur/setup-php@2.17.1
with:
php-version: ${{ env.PHP_VERSION }}
coverage: none
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
reaction-type: "+1"
- name: Checkout the latest code
uses: actions/checkout@v2.4.0
uses: actions/checkout@v3
with:
fetch-depth: 0
token: ${{ secrets.COMMAND_BOT_PAT }}
@@ -25,5 +25,5 @@ jobs:
# Nextcloud bot approve and merge request
- uses: ahmadnassri/action-dependabot-auto-merge@v2
with:
target: patch
target: minor
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}
+4 -4
View File
@@ -19,7 +19,7 @@ jobs:
matrix:
php-versions: ['7.4']
databases: ['sqlite', 'mysql', 'pgsql']
server-versions: ['stable23']
server-versions: ['master']
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@v3
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@v3
with:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@2.17.1
with:
php-version: ${{ matrix.php-versions }}
tools: phpunit
+9 -9
View File
@@ -13,13 +13,13 @@ jobs:
strategy:
matrix:
php-versions: ['7.2', '7.3', '7.4']
php-versions: ['7.4', '8.0']
name: php${{ matrix.php-versions }} lint
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
- name: Set up php${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@2.17.1
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@v3
- name: Set up php
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@2.17.1
with:
php-version: 7.4
coverage: none
@@ -50,9 +50,9 @@ jobs:
node-version: [14.x]
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
- name: Use node ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7
@@ -71,10 +71,10 @@ jobs:
name: stylelint node${{ matrix.node-version }}
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
- name: Set up node ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
+3 -3
View File
@@ -17,15 +17,15 @@ jobs:
node-version: [14.x]
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v3
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@2.17.1
with:
php-version: '7.4'
tools: composer
+2 -2
View File
@@ -12,9 +12,9 @@ jobs:
node-version: [14.x]
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7
+5 -5
View File
@@ -18,9 +18,9 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['7.3', '7.4']
php-versions: ['7.4', '8.0']
databases: ['sqlite', 'mysql', 'pgsql']
server-versions: ['stable23']
server-versions: ['master']
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@v3
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@v3
with:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@2.17.1
with:
php-version: ${{ matrix.php-versions }}
tools: phpunit
+3 -3
View File
@@ -12,13 +12,13 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
ocp-version: [ 'dev-stable23' ]
ocp-version: [ 'dev-master' ]
name: Nextcloud ${{ matrix.ocp-version }}
steps:
- name: Checkout
uses: actions/checkout@v2.4.0
uses: actions/checkout@v3
- name: Set up php
uses: shivammathur/setup-php@2.15.0
uses: shivammathur/setup-php@2.17.1
with:
php-version: 7.4
tools: composer:v1
+1 -22
View File
@@ -1,26 +1,10 @@
# Changelog
All notable changes to this project will be documented in this file.
## 1.6.1
### 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)
## 1.6.0
## 1.6.0-beta1
### 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
@@ -29,11 +13,6 @@ All notable changes to this project will be documented in this file.
### 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
+1 -2
View File
@@ -50,8 +50,7 @@ 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.xml --coverage-clover build/php-unit.coverage.xml
phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
phpunit -c tests/phpunit.integration.xml --testsuite=integration-database --coverage-clover build/php-integration.coverage.xml
endif
test-integration:
+1 -1
View File
@@ -26,7 +26,7 @@ Deck is a kanban style organization tool aimed at personal planning and project
- [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.
+7 -6
View File
@@ -7,16 +7,16 @@
- 📥 Add your tasks to cards and put them in order
- 📄 Write down additional notes in markdown
- 📄 Write down additional notes in Markdown
- 🔖 Assign labels for even better organization
- 👥 Share with your team, friends or family
- 📎 Attach files and embed them in your markdown description
- 📎 Attach files and embed them in your Markdown description
- 💬 Discuss with your team using comments
- ⚡ Keep track of changes in the activity stream
- 🚀 Get your project organized
</description>
<version>1.6.1</version>
<version>1.7.0-alpha1</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Deck</namespace>
@@ -31,11 +31,10 @@
<screenshot>https://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-1.png</screenshot>
<screenshot>https://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-2.png</screenshot>
<dependencies>
<php min-version="7.3"/>
<database min-version="9.4">pgsql</database>
<database>sqlite</database>
<database min-version="5.5">mysql</database>
<nextcloud min-version="23" max-version="23"/>
<database min-version="8.0">mysql</database>
<nextcloud min-version="24" max-version="24"/>
</dependencies>
<background-jobs>
<job>OCA\Deck\Cron\DeleteCron</job>
@@ -44,6 +43,8 @@
</background-jobs>
<commands>
<command>OCA\Deck\Command\UserExport</command>
<command>OCA\Deck\Command\BoardImport</command>
<command>OCA\Deck\Command\TransferOwnership</command>
</commands>
<activity>
<settings>
+5
View File
@@ -39,6 +39,7 @@ 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'],
@@ -91,6 +92,10 @@ return [
['name' => 'board_api#deleteAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
['name' => 'board_api#updateAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl/{aclId}', 'verb' => 'PUT'],
['name' => 'board_import_api#getAllowedSystems', 'url' => '/api/v{apiVersion}/boards/import/getSystems','verb' => 'GET'],
['name' => 'board_import_api#getConfigSchema', 'url' => '/api/v{apiVersion}/boards/import/config/schema/{name}','verb' => 'GET'],
['name' => 'board_import_api#import', 'url' => '/api/v{apiVersion}/boards/import','verb' => 'POST'],
['name' => 'stack_api#index', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks', 'verb' => 'GET'],
['name' => 'stack_api#getArchived', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/archived', 'verb' => 'GET'],
+11 -4
View File
@@ -9,20 +9,27 @@
}
],
"require": {
"cogpowered/finediff": "0.3.*"
"cogpowered/finediff": "0.3.*",
"justinrainbow/json-schema": "^5.2"
},
"require-dev": {
"roave/security-advisories": "dev-master",
"christophwurst/nextcloud": "^21@dev",
"christophwurst/nextcloud": "dev-master",
"phpunit/phpunit": "^9",
"nextcloud/coding-standard": "^0.5.0",
"nextcloud/coding-standard": "^1.0.0",
"symfony/event-dispatcher": "^4.0",
"vimeo/psalm": "^4.3",
"php-parallel-lint/php-parallel-lint": "^1.2"
},
"config": {
"optimize-autoloader": true,
"classmap-authoritative": true
"classmap-authoritative": true,
"allow-plugins": {
"composer/package-versions-deprecated": true
},
"platform": {
"php": "7.4"
}
},
"scripts": {
"lint": "find . -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",
Generated
+903 -611
View File
File diff suppressed because it is too large Load Diff
+66 -2
View File
@@ -96,10 +96,27 @@ If available the ETag will also be part of JSON response objects as shown below
# Changelog
## 1.0.0 (unreleased)
## API version 1.0
- Deck >=1.0.0: The maximum length of the card title has been extended from 100 to 255 characters
- Deck >=1.0.0: The API will now return a 400 Bad request response if the length limitation of a board, stack or card title is exceeded
## API version 1.1
This API version has become available with **Deck 1.3.0**.
- The maximum length of the card title has been extended from 100 to 255 characters
- The API will now return a 400 Bad request response if the length limitation of a board, stack or card title is exceeded
- The attachments API endpoints will return other attachment types than deck_file
- Prior to Deck version v1.3.0 (API v1.0), attachments were stored within deck. For this type of attachments `deck_file` was used as the default type of attachments
- Starting with Deck version 1.3.0 (API v1.1) files are stored within the users regular Nextcloud files and the type `file` has been introduced for that
## API version 1.2 (unreleased)
- Endpoints for the new import functionality have been added:
- [GET /boards/import/getSystems - Import a board](#get-boardsimportgetsystems-import-a-board)
- [GET /boards/import/config/system/{schema} - Import a board](#get-boardsimportconfigsystemschema-import-a-board)
- [POST /boards/import - Import a board](#post-boardsimport-import-a-board)
# Endpoints
@@ -927,7 +944,8 @@ The request can fail with a bad request response for the following reasons:
| type | String | The type of the attachement |
| file | Binary | File data to add as an attachment |
For now only `deck_file` is supported as an attachment type.
- Prior to Deck version v1.3.0 (API v1.0), attachments were stored within deck. For this type of attachments `deck_file` was used as the default type of attachments
- Starting with Deck version 1.3.0 (API v1.1) files are stored within the users regular Nextcloud files and the type `file` has been introduced for that
#### Response
@@ -988,6 +1006,49 @@ For now only `deck_file` is supported as an attachment type.
##### 200 Success
### GET /boards/import/getSystems - Import a board
#### Request parameters
| Parameter | Type | Description |
| ------------ | ------- | --------------------------------------------- |
| system | Integer | The system name. Example: trello |
#### Response
Make a request to see the json schema of system
```json
{
}
```
### GET /boards/import/config/system/{schema} - Import a board
#### Request parameters
#### Response
```json
[
"trello"
]
```
### POST /boards/import - Import a board
#### Request parameters
| Parameter | Type | Description |
| ------------ | ------- | --------------------------------------------- |
| system | string | The allowed name of system to import from |
| config | Object | The config object (JSON) |
| data | Object | The data object to import (JSON) |
#### Response
##### 200 Success
# OCS API
The following endpoints are available through the Nextcloud OCS endpoint, which is available at `/ocs/v2.php/apps/deck/api/v1.0/`.
@@ -1004,6 +1065,7 @@ Deck stores user and app configuration values globally and per board. The GET en
| Config key | Description |
| --- | --- |
| calendar | Determines if the calendar/tasks integration through the CalDAV backend is enabled for the user (boolean) |
| cardDetailsInModal | Determines if the bigger view is used (boolean) |
| groupLimit | Determines if creating new boards is limited to certain groups of the instance. The resulting output is an array of group objects with the id and the displayname (Admin only)|
```
@@ -1016,6 +1078,7 @@ Deck stores user and app configuration values globally and per board. The GET en
},
"data": {
"calendar": true,
"cardDetailsInModal": true,
"groupLimit": [
{
"id": "admin",
@@ -1045,6 +1108,7 @@ Deck stores user and app configuration values globally and per board. The GET en
| --- | ----- |
| notify-due | `off`, `assigned` or `all` |
| calendar | Boolean |
| cardDetailsInModal | Boolean |
#### Example request
+90 -3
View File
@@ -14,6 +14,9 @@ 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. [Import boards](#6-import-boards)
7. [Search](#7-search)
8. [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.
@@ -69,14 +72,80 @@ The **sharing tab** allows you to add users or even groups to your boards.
**Deleted objects** allows you to return previously deleted stacks or cards.
The **Timeline** allows you to see everything that happened in your boards. Everything!
## Search
### 6. Import boards
Importing can be done using the API or the `occ` `deck:import` command.
Comments with more than 1000 characters are placed as attached files to the card.
It is possible to import from the following sources:
#### Trello JSON
Steps:
* Create the data file
* Access Trello
* go to the board you want to export
* Follow the steps in [Trello documentation](https://help.trello.com/article/747-exporting-data-from-trello-1) and export as JSON
* Create the configuration file
* Execute the import informing the import file path, data file and source as `Trello JSON`
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/fixtures/config-trelloJson-schema.json) for import `Trello JSON`
Example configuration file:
```json
{
"owner": "admin",
"color": "0800fd",
"uidRelation": {
"johndoe": "johndoe"
}
}
```
**Limitations**:
Importing from a JSON file imports up to 1000 actions. To find out how many actions the board to be imported has, identify how many actions the JSON has.
#### Trello API
Import using API is recommended for boards with more than 1000 actions.
Trello makes it possible to attach links to a card. Deck does not have this feature. Attachments and attachment links are added in a markdown table at the end of the description for every imported card that has attachments in Trello.
* Get the API Key and API Token [here](https://developer.atlassian.com/cloud/trello/guides/rest-api/api-introduction/#authentication-and-authorization)
* Get the ID of the board you want to import by making a request to:
https://api.trello.com/1/members/me/boards?key={yourKey}&token={yourToken}&fields=id,name
This ID you will use in the configuration file in the `board` property
* Create the configuration file
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/fixtures/config-trelloApi-schema.json) for import `Trello JSON`
Example configuration file:
```json
{
"owner": "admin",
"color": "0800fd",
"api": {
"key": "0cc175b9c0f1b6a831c399e269772661",
"token": "92eb5ffee6ae2fec3ad71c777531578f4a8a08f09d37b73795649038408b5f33"
},
"board": "8277e0910d750195b4487976",
"uidRelation": {
"johndoe": "johndoe"
}
}
```
### 7. Search
Deck provides a global search either through the unified search in the Nextcloud header or with the inline search next to the board controls.
This search allows advanced filtering of cards across all board of the logged in user.
For example the search `project tag:ToDo assigned:alice assigned:bob` will return all cards where the card title or description contains project **and** the tag ToDo is set **and** the user alice is assigned **and** the user bob is assigned.
### Supported search filters
#### Supported search filters
| Filter | Operators | Query |
| ----------- | ----------------- | ------------------------------------------------------------ |
@@ -90,4 +159,22 @@ 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 wuotes can be used to pass a query with spaces, e.g. `"Exact match with spaces"` or `title:"My card"`.
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
```
+32
View File
@@ -0,0 +1,32 @@
## Implement import
* Create a new importer class extending `ABoardImportService`
* Create a listener for event `BoardImportGetAllowedEvent` to enable your importer.
> You can read more about listeners on [Nextcloud](https://docs.nextcloud.com/server/latest/developer_manual/basics/events.html?highlight=event#writing-a-listener) doc.
Example:
```php
class YourCustomImporterListener {
public function handle(Event $event): void {
if (!($event instanceof BoardImportGetAllowedEvent)) {
return;
}
$event->getService()->addAllowedImportSystem([
'name' => YourCustomImporterService::$name,
'class' => YourCustomImporterService::class,
'internalName' => 'YourCustomImporter'
]);
}
}
```
* Register your listener on your `Application` class like this:
```php
$dispatcher = $this->getContainer()->query(IEventDispatcher::class);
$dispatcher->registerEventListener(
BoardImportGetAllowedEvent::class,
YourCustomImporterListener::class
);
```
* Use the `lib/Service/Importer/Systems/TrelloJsonService.php` class as inspiration
+7
View File
@@ -0,0 +1,7 @@
## Import class diagram
Importing boards to the Deck implements the class diagram below.
> **NOTE**: When making any changes to the structure of the classes or implementing import from other sources, edit the `BoardImport.yuml` file
![Screenshot](resources/BoardImport.svg)
+214
View File
@@ -0,0 +1,214 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
-->
<!-- Title: G Pages: 1 -->
<svg width="417pt" height="830pt"
viewBox="0.00 0.00 417.01 830.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 826)">
<title>G</title>
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-826 413.012,-826 413.012,4 -4,4"/>
<!-- A0 -->
<g id="node1" class="node">
<title>A0</title>
<polygon fill="#fff8dc" stroke="#000000" points="165.909,-822 70.091,-822 70.091,-766 171.909,-766 171.909,-816 165.909,-822"/>
<polyline fill="none" stroke="#000000" points="165.909,-822 165.909,-816 "/>
<polyline fill="none" stroke="#000000" points="171.909,-816 165.909,-816 "/>
<text text-anchor="middle" x="121" y="-809" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Classes used on</text>
<text text-anchor="middle" x="121" y="-797" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">board import.</text>
<text text-anchor="middle" x="121" y="-785" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Methods just to</text>
<text text-anchor="middle" x="121" y="-773" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">illustrate.</text>
</g>
<!-- A1 -->
<g id="node2" class="node">
<title>A1</title>
<polygon fill="none" stroke="#000000" points="108.7773,-680 23.2227,-680 23.2227,-644 108.7773,-644 108.7773,-680"/>
<text text-anchor="middle" x="66" y="-659" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ApiController</text>
</g>
<!-- A2 -->
<g id="node3" class="node">
<title>A2</title>
<polygon fill="none" stroke="#000000" points="0,-514 0,-546 132,-546 132,-514 0,-514"/>
<text text-anchor="start" x="9.607" y="-527" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImportApiController</text>
<polygon fill="none" stroke="#000000" points="0,-458 0,-514 132,-514 132,-458 0,-458"/>
<text text-anchor="start" x="45.8645" y="-495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+import()</text>
<text text-anchor="start" x="16.1335" y="-483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+getAllowedSystems()</text>
<text text-anchor="start" x="20.0185" y="-471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+getConfigSchema()</text>
</g>
<!-- A1&#45;&gt;A2 -->
<g id="edge1" class="edge">
<title>A1&#45;&gt;A2</title>
<path fill="none" stroke="#000000" d="M66,-633.6693C66,-609.4424 66,-574.1663 66,-546.2238"/>
<polygon fill="#000000" stroke="#000000" points="66,-643.957 61.5001,-633.9569 66,-638.957 66.0001,-633.957 66.0001,-633.957 66.0001,-633.957 66,-638.957 70.5001,-633.957 66,-643.957 66,-643.957"/>
</g>
<!-- A3 -->
<g id="node4" class="node">
<title>A3</title>
<polygon fill="none" stroke="#000000" points="92,-364 92,-396 200,-396 200,-364 92,-364"/>
<text text-anchor="start" x="101.828" y="-377" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImportService</text>
<polygon fill="none" stroke="#000000" points="92,-284 92,-364 200,-364 200,-284 92,-284"/>
<text text-anchor="start" x="125.8645" y="-345" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+import()</text>
<text text-anchor="start" x="118.9105" y="-333" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+bootstrap()</text>
<text text-anchor="start" x="105.857" y="-321" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+validateSystem()</text>
<text text-anchor="start" x="108.218" y="-309" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateConfig()</text>
<text text-anchor="start" x="112.107" y="-297" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateData()</text>
</g>
<!-- A2&#45;&gt;A3 -->
<g id="edge2" class="edge">
<title>A2&#45;&gt;A3</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M87.8604,-457.7328C95.8577,-441.5382 105.0823,-422.8583 113.7939,-405.2174"/>
<polygon fill="#000000" stroke="#000000" points="118.2935,-396.1057 117.9004,-407.0646 116.0795,-400.5889 113.8656,-405.072 113.8656,-405.072 113.8656,-405.072 116.0795,-400.5889 109.8308,-403.0795 118.2935,-396.1057 118.2935,-396.1057"/>
<text text-anchor="middle" x="88.3076" y="-434.7378" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
</g>
<!-- A7 -->
<g id="node8" class="node">
<title>A7</title>
<polygon fill="none" stroke="#000000" points="37,-196 37,-228 129,-228 129,-196 37,-196"/>
<text text-anchor="start" x="46.612" y="-209" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">TrelloApiService</text>
<polygon fill="none" stroke="#000000" points="37,-164 37,-196 129,-196 129,-164 37,-164"/>
<text text-anchor="start" x="53.9655" y="-177" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+name:string</text>
</g>
<!-- A3&#45;&gt;A7 -->
<g id="edge6" class="edge">
<title>A3&#45;&gt;A7</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M114.8609,-283.9135C107.8316,-268.5143 100.7854,-252.0928 95.0404,-237.6613"/>
<polygon fill="#000000" stroke="#000000" points="91.2872,-228.0253 99.1098,-235.7102 93.1019,-232.6844 94.9167,-237.3434 94.9167,-237.3434 94.9167,-237.3434 93.1019,-232.6844 90.7235,-238.9767 91.2872,-228.0253 91.2872,-228.0253"/>
<text text-anchor="middle" x="99.6759" y="-267.8975" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
</g>
<!-- A9 -->
<g id="node10" class="node">
<title>A9</title>
<polygon fill="none" stroke="#000000" points="148,-202 148,-234 273,-234 273,-202 148,-202"/>
<text text-anchor="start" x="170.7765" y="-215" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">TrelloJsonService</text>
<polygon fill="none" stroke="#000000" points="148,-158 148,-202 273,-202 273,-158 148,-158"/>
<text text-anchor="start" x="181.4655" y="-183" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+name:string</text>
<text text-anchor="start" x="157.981" y="-171" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#needValidateData:true</text>
</g>
<!-- A3&#45;&gt;A9 -->
<g id="edge9" class="edge">
<title>A3&#45;&gt;A9</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M164.3261,-283.9135C170.0039,-270.5688 176.3462,-256.4563 182.4816,-243.5365"/>
<polygon fill="#000000" stroke="#000000" points="186.9002,-234.3677 186.6126,-245.3298 184.7295,-238.872 182.5588,-243.3762 182.5588,-243.3762 182.5588,-243.3762 184.7295,-238.872 178.505,-241.4226 186.9002,-234.3677 186.9002,-234.3677"/>
<text text-anchor="middle" x="163.6874" y="-260.9237" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
</g>
<!-- A10 -->
<g id="node11" class="node">
<title>A10</title>
<polygon fill="#fff8dc" stroke="#000000" points="317.7872,-362 218.2128,-362 218.2128,-318 323.7872,-318 323.7872,-356 317.7872,-362"/>
<polyline fill="none" stroke="#000000" points="317.7872,-362 317.7872,-356 "/>
<polyline fill="none" stroke="#000000" points="323.7872,-356 317.7872,-356 "/>
<text text-anchor="middle" x="271" y="-349" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">validateSystem is</text>
<text text-anchor="middle" x="271" y="-337" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">public because is</text>
<text text-anchor="middle" x="271" y="-325" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">used on Api.</text>
</g>
<!-- A3&#45;&gt;A10 -->
<g id="edge11" class="edge">
<title>A3&#45;&gt;A10</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M200.1992,-340C206.1915,-340 212.1837,-340 218.176,-340"/>
</g>
<!-- A4 -->
<g id="node5" class="node">
<title>A4</title>
<polygon fill="none" stroke="#000000" points="264.1131,-812 189.8869,-812 189.8869,-776 264.1131,-776 264.1131,-812"/>
<text text-anchor="middle" x="227" y="-791" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Command</text>
</g>
<!-- A5 -->
<g id="node6" class="node">
<title>A5</title>
<polygon fill="none" stroke="#000000" points="148,-684 148,-716 307,-716 307,-684 148,-684"/>
<text text-anchor="start" x="199.9955" y="-697" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImport</text>
<polygon fill="none" stroke="#000000" points="148,-652 148,-684 307,-684 307,-652 148,-652"/>
<text text-anchor="start" x="157.907" y="-665" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+boardImportCommandService</text>
<polygon fill="none" stroke="#000000" points="148,-608 148,-652 307,-652 307,-608 148,-608"/>
<text text-anchor="start" x="200.8305" y="-633" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#configure()</text>
<text text-anchor="start" x="177.76" y="-621" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#execute(input,output)</text>
</g>
<!-- A4&#45;&gt;A5 -->
<g id="edge3" class="edge">
<title>A4&#45;&gt;A5</title>
<path fill="none" stroke="#000000" d="M227,-765.6356C227,-751.1554 227,-733.0451 227,-716.0324"/>
<polygon fill="#000000" stroke="#000000" points="227,-775.9227 222.5001,-765.9227 227,-770.9227 227.0001,-765.9227 227.0001,-765.9227 227.0001,-765.9227 227,-770.9227 231.5001,-765.9228 227,-775.9227 227,-775.9227"/>
</g>
<!-- A6 -->
<g id="node7" class="node">
<title>A6</title>
<polygon fill="none" stroke="#000000" points="150,-526 150,-558 304,-558 304,-526 150,-526"/>
<text text-anchor="start" x="159.7715" y="-539" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImportCommandService</text>
<polygon fill="none" stroke="#000000" points="150,-446 150,-526 304,-526 304,-446 150,-446"/>
<text text-anchor="start" x="199.9105" y="-507" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+bootstrap()</text>
<text text-anchor="start" x="206.8645" y="-495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+import()</text>
<text text-anchor="start" x="186.857" y="-483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+validateSystem()</text>
<text text-anchor="start" x="189.218" y="-471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateConfig()</text>
<text text-anchor="start" x="193.107" y="-459" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateData()</text>
</g>
<!-- A5&#45;&gt;A6 -->
<g id="edge4" class="edge">
<title>A5&#45;&gt;A6</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M227,-607.8313C227,-595.0442 227,-581.2707 227,-568.0248"/>
<polygon fill="#000000" stroke="#000000" points="227,-558.0234 231.5001,-568.0234 227,-563.0234 227.0001,-568.0234 227.0001,-568.0234 227.0001,-568.0234 227,-563.0234 222.5001,-568.0235 227,-558.0234 227,-558.0234"/>
<text text-anchor="middle" x="218.5476" y="-586.7051" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
</g>
<!-- A6&#45;&gt;A3 -->
<g id="edge5" class="edge">
<title>A6&#45;&gt;A3</title>
<path fill="none" stroke="#000000" d="M198.8975,-445.7949C192.3634,-432.7268 185.3528,-418.7057 178.6417,-405.2834"/>
<polygon fill="#000000" stroke="#000000" points="174.0529,-396.1057 182.55,-403.0375 176.289,-400.5779 178.5251,-405.05 178.5251,-405.05 178.5251,-405.05 176.289,-400.5779 174.5001,-407.0625 174.0529,-396.1057 174.0529,-396.1057"/>
</g>
<!-- A7&#45;&gt;A3 -->
<g id="edge7" class="edge">
<title>A7&#45;&gt;A3</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M102.735,-228.0253C109.5347,-241.763 117.1224,-258.3431 124.0627,-274.4849"/>
<polygon fill="#000000" stroke="#000000" points="128.0634,-283.9135 120.0148,-276.4657 126.1104,-279.3107 124.1573,-274.7079 124.1573,-274.7079 124.1573,-274.7079 126.1104,-279.3107 128.2998,-272.9502 128.0634,-283.9135 128.0634,-283.9135"/>
<text text-anchor="middle" x="118.307" y="-237.5757" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
</g>
<!-- A8 -->
<g id="node9" class="node">
<title>A8</title>
<polygon fill="none" stroke="#000000" points="80,-64 80,-108 213,-108 213,-64 80,-64"/>
<text text-anchor="start" x="117.04" y="-89" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;abstract&gt;&gt;</text>
<text text-anchor="start" x="98.9935" y="-77" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ABoardImportService</text>
<polygon fill="none" stroke="#000000" points="80,-32 80,-64 213,-64 213,-32 80,-32"/>
<text text-anchor="start" x="92.036" y="-45" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#needValidateData:false</text>
<polygon fill="none" stroke="#000000" points="80,0 80,-32 213,-32 213,0 80,0"/>
<text text-anchor="start" x="89.677" y="-13" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+needValidateData():bool</text>
</g>
<!-- A7&#45;&gt;A8 -->
<g id="edge8" class="edge">
<title>A7&#45;&gt;A8</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M97.2957,-163.778C103.3956,-150.029 110.7371,-133.4813 117.8485,-117.4527"/>
<polygon fill="none" stroke="#000000" points="121.1416,-118.6605 121.9978,-108.1003 114.743,-115.8216 121.1416,-118.6605"/>
<text text-anchor="middle" x="96.9205" y="-140.7815" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">implements</text>
</g>
<!-- A9&#45;&gt;A3 -->
<g id="edge10" class="edge">
<title>A9&#45;&gt;A3</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M198.9952,-234.3677C194.0646,-246.7117 188.0483,-260.7568 181.8434,-274.4849"/>
<polygon fill="#000000" stroke="#000000" points="177.5286,-283.9135 177.598,-272.9478 179.6093,-279.367 181.6899,-274.8204 181.6899,-274.8204 181.6899,-274.8204 179.6093,-279.367 185.7818,-276.693 177.5286,-283.9135 177.5286,-283.9135"/>
<text text-anchor="middle" x="200.0654" y="-251.3391" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
</g>
<!-- A9&#45;&gt;A8 -->
<g id="edge13" class="edge">
<title>A9&#45;&gt;A8</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M192.8492,-157.9466C187.2535,-145.5313 180.8796,-131.389 174.6742,-117.6209"/>
<polygon fill="none" stroke="#000000" points="177.7167,-115.8534 170.4168,-108.1747 171.3349,-118.7297 177.7167,-115.8534"/>
<text text-anchor="middle" x="177.6953" y="-141.8944" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">implements</text>
</g>
<!-- A11 -->
<g id="node12" class="node">
<title>A11</title>
<polygon fill="#fff8dc" stroke="#000000" points="403.024,-224 290.976,-224 290.976,-168 409.024,-168 409.024,-218 403.024,-224"/>
<polyline fill="none" stroke="#000000" points="403.024,-224 403.024,-218 "/>
<polyline fill="none" stroke="#000000" points="409.024,-218 403.024,-218 "/>
<text text-anchor="middle" x="350" y="-211" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">To create an import</text>
<text text-anchor="middle" x="350" y="-199" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">to another system,</text>
<text text-anchor="middle" x="350" y="-187" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">create another class</text>
<text text-anchor="middle" x="350" y="-175" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">similar to this.</text>
</g>
<!-- A9&#45;&gt;A11 -->
<g id="edge12" class="edge">
<title>A9&#45;&gt;A11</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M272.6172,-196C278.6627,-196 284.7083,-196 290.7538,-196"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

+24
View File
@@ -0,0 +1,24 @@
// Created using [yUML](https://github.com/jaime-olivares/vscode-yuml)
// {type:class}
// {direction:topDown}
// {generate:true}
[note: Classes used on board import. Methods just to illustrate. {bg:cornsilk}]
[ApiController]<-[BoardImportApiController|+import();+getAllowedSystems();+getConfigSchema()]
[BoardImportApiController]uses-.->[BoardImportService|+import();+bootstrap();+validateSystem();#validateConfig();#validateData();]
[Command]<-[BoardImport|+boardImportCommandService|#configure();#execute(input,output)]
[BoardImport]uses-.->[BoardImportCommandService|+bootstrap();+import();+validateSystem();#validateConfig();#validateData()]
[BoardImportCommandService]->[BoardImportService]
[BoardImportService]uses-.->[TrelloApiService|+name:string]
[TrelloApiService]uses-.->[BoardImportService]
[TrelloApiService]implements-.-^[<<abstract>> ABoardImportService|#needValidateData:false|+needValidateData():bool]
[BoardImportService]uses-.->[TrelloJsonService|+name:string;#needValidateData:true]
[TrelloJsonService]uses-.->[BoardImportService]
[BoardImportService]-[note: validateSystem is public because is used on Api. {bg:cornsilk}]
[TrelloJsonService]-[note: To create an import to another system, create another class similar to this. {bg:cornsilk}]
[TrelloJsonService]implements-.-^[<<abstract>> ABoardImportService]
+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"><g><g><polygon points="426.667,213.333 288.36,213.333 333.706,0 148.817,0 85.333,298.667 227.556,298.667 227.556,512 " /></g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g></svg>

After

Width:  |  Height:  |  Size: 594 B

+1
View File
@@ -0,0 +1 @@
<svg fill="#26e07f" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px" height="24px"><path fill-rule="evenodd" d="M 11 2 L 11 11 L 2 11 L 2 13 L 11 13 L 11 22 L 13 22 L 13 13 L 22 13 L 22 11 L 13 11 L 13 2 Z"/></svg>

After

Width:  |  Height:  |  Size: 235 B

+111 -86
View File
@@ -2,135 +2,145 @@ OC.L10N.register(
"deck",
{
"You have created a new board {board}" : "Δημιουργήσατε νέο πίνακα {board}",
"{user} has created a new board {board}" : "Ο {user} δημιούργησε νέο πίνακα {board}",
"{user} has created a new board {board}" : "Ο {user} δημιούργησε νέο πίνακα {board}",
"You have deleted the board {board}" : "Έχετε διαγράψει τον πίνακα {board}",
"{user} has deleted the board {board}" : "Ο {user} διέγραψε τον πίνακα {board}",
"{user} has deleted the board {board}" : "Ο {user} διέγραψε τον πίνακα {board}",
"You have restored the board {board}" : "Εχετε επαναφέρει τον πίνακα {board}",
"{user} has restored the board {board}" : "Ο {user} επανέφερε τον πίνακα {board}",
"{user} has restored the board {board}" : "Ο {user} επανέφερε τον πίνακα {board}",
"You have shared the board {board} with {acl}" : "Εχετε διαμοιράσει τον πίνακα {board} με {acl}",
"{user} has shared the board {board} with {acl}" : "Ο {user} διαμοίρασε τον πίνακα {board} με {acl}",
"{user} has shared the board {board} with {acl}" : "Ο {user} διαμοίρασε τον πίνακα {board} με {acl}",
"You have removed {acl} from the board {board}" : "Αφαιρέθηκε η {acl} από τον πίνακα {board}",
"{user} has removed {acl} from the board {board}" : "Ο {user} αφαίρεσε την {acl} από τον πίνακα {board}",
"{user} has removed {acl} from the board {board}" : "Ο {user} αφαίρεσε την {acl} από τον πίνακα {board}",
"You have renamed the board {before} to {board}" : "Μετονομάσατε τον πίνακα {before} σε {board}",
"{user} has renamed the board {before} to {board}" : "Ο {user} μετονόμασε τον πίνακα {before} σε {board}",
"{user} has renamed the board {before} to {board}" : "Ο {user} μετονόμασε τον πίνακα {before} σε {board}",
"You have archived the board {board}" : "Αρχειοθετήσατε τον πίνακα {board}",
"{user} has archived the board {before}" : "Ο {user} αρχειοθέτησε τον πίνακα {before}",
"You have unarchived the board {board}" : "Επαναφέρατε τον πίνακα {board} από αρχείο",
"{user} has unarchived the board {before}" : "Ο {user} επανέφερε τον πίνακα {before} από αρχείο",
"{user} has archived the board {before}" : "Ο {user} αρχειοθέτησε τον πίνακα {before}",
"You have unarchived the board {board}" : "Επαναφέρατε τον πίνακα {board} από το αρχείο",
"{user} has unarchived the board {before}" : "Ο {user} επανέφερε τον πίνακα {before} από αρχείο",
"You have created a new list {stack} on board {board}" : "Έχετε δημιουργήσει μια νέα λίστα {stack} στον πίνακα {board}",
"{user} has created a new list {stack} on board {board}" : "Ο {user} δημιούργησε μια νέα λίστα {stack} στον πίνακα {board}",
"{user} has created a new list {stack} on board {board}" : "Ο {user} δημιούργησε μια νέα λίστα {stack} στον πίνακα {board}",
"You have renamed list {before} to {stack} on board {board}" : "Μετονομάσατε την λίστα {before} σε {stack} στον πίνακα {board}",
"{user} has renamed list {before} to {stack} on board {board}" : "Ο {user} μετονόμασε την λίστα {before} σε {stack} στον πίνακα {board}",
"{user} has renamed list {before} to {stack} on board {board}" : "Ο {user} μετονόμασε την λίστα {before} σε {stack} στον πίνακα {board}",
"You have deleted list {stack} on board {board}" : "Διαγράψατε την λίστα {stack} στον πίνακα {board}",
"{user} has deleted list {stack} on board {board}" : "Ο {user} διέγραψε την λίστα {stack} στον πίνακα {board}",
"{user} has deleted list {stack} on board {board}" : "Ο {user} διέγραψε την λίστα {stack} στον πίνακα {board}",
"You have created card {card} in list {stack} on board {board}" : "Δημιουργήσατε την καρτέλα {card} στην λίστα {stack} του πίνακα {board}",
"{user} has created card {card} in list {stack} on board {board}" : "Ο {user} δημιούργησε την καρτέλα {card} στην λίστα {stack} του πίνακα {board}",
"{user} has created card {card} in list {stack} on board {board}" : "Ο {user} δημιούργησε την καρτέλα {card} στην λίστα {stack} του πίνακα {board}",
"You have deleted card {card} in list {stack} on board {board}" : "Διαγράψατε την καρτέλα {card} στην λίστα {stack} του πίνακα {board}",
"{user} has deleted card {card} in list {stack} on board {board}" : "Ο {user} διέγραψε την καρτέλα {card} στην λίστα {stack} του πίνακα {board}",
"{user} has deleted card {card} in list {stack} on board {board}" : "Ο {user} διέγραψε την καρτέλα {card} στην λίστα {stack} του πίνακα {board}",
"You have renamed the card {before} to {card}" : "Μετονομάσατε την καρτέλα {before} σε {card}",
"{user} has renamed the card {before} to {card}" : "Ο {user} μετονόμασε την καρτέλα {before} σε {card}",
"{user} has renamed the card {before} to {card}" : "Ο {user} μετονόμασε την καρτέλα {before} σε {card}",
"You have added a description to card {card} in list {stack} on board {board}" : "Προσθέσατε μια περιγραφή στην καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has added a description to card {card} in list {stack} on board {board}" : "Ο {user} πρόσθεσε μια περιγραφή στην καρτέλα {card} της λίστας {stack} του πίνακα {board} ",
"{user} has added a description to card {card} in list {stack} on board {board}" : "Ο {user} πρόσθεσε μια περιγραφή στην καρτέλα {card} της λίστας {stack} του πίνακα {board} ",
"You have updated the description of card {card} in list {stack} on board {board}" : "Ενημερώσατε την περιγραφή στην καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has updated the description of the card {card} in list {stack} on board {board}" : "Ο {user} ενημέρωσε την περιγραφή της καρτέλας {card} στη λίστα {stack} του πίνακα {board}",
"{user} has updated the description of the card {card} in list {stack} on board {board}" : "Ο {user} ενημέρωσε την περιγραφή της καρτέλας {card} στη λίστα {stack} του πίνακα {board}",
"You have archived card {card} in list {stack} on board {board}" : "Αρχειοθετήσατε την κάρτα {card} στην λίστα {stack} του πίνακα {board} ",
"{user} has archived card {card} in list {stack} on board {board}" : "Ο {user} αρχειοθέτησε την κάρτα {card} στην λίστα {stack} του πίνακα {board} ",
"{user} has archived card {card} in list {stack} on board {board}" : "Ο {user} αρχειοθέτησε την κάρτα {card} στην λίστα {stack} του πίνακα {board} ",
"You have unarchived card {card} in list {stack} on board {board}" : "Επαναφέρατε από το αρχείο την καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has unarchived card {card} in list {stack} on board {board}" : "Ο {user} επανέφερε από το αρχείο την κάρτα {card} της λίστας {stack} του πίνακα {board}",
"{user} has unarchived card {card} in list {stack} on board {board}" : "Ο {user} επανέφερε από το αρχείο την κάρτα {card} της λίστας {stack} του πίνακα {board}",
"You have removed the due date of card {card}" : "Καταργήσατε την ημερομηνία λήξης της καρτέλας {card}",
"{user} has removed the due date of card {card}" : "Ο {user} κατήργησε την ημερομηνία λήξης της καρτέλας {card}",
"{user} has removed the due date of card {card}" : "Ο {user} κατάργησε την ημερομηνία λήξης της καρτέλας {card}",
"You have set the due date of card {card} to {after}" : "Ορίσατε την ημερομηνία λήξης της καρτέλας {card} σε {after}",
"{user} has set the due date of card {card} to {after}" : "Ο {user} όρισε την ημερομηνία λήξης της καρτέλας {card} σε {after} ",
"{user} has set the due date of card {card} to {after}" : "Ο {user} όρισε την ημερομηνία λήξης της καρτέλας {card} σε {after} ",
"You have updated the due date of card {card} to {after}" : "Ενημερώσατε την ημερομηνία λήξης της καρτέλας {card} σε {after}",
"{user} has updated the due date of card {card} to {after}" : "Ο {user} ενημέρωσε την ημερομηνία λήξης της καρτέλας {card} σε {after}",
"You have added the tag {label} to card {card} in list {stack} on board {board}" : "Προσθέσατε ετικέτα στην καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has added the tag {label} to card {card} in list {stack} on board {board}" : "Ο {user} πρόσθεσε ετικέτα στην καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"You have removed the tag {label} from card {card} in list {stack} on board {board}" : "Αφαιρέσατε την ετικέτα από την καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has removed the tag {label} from card {card} in list {stack} on board {board}" : "Ο {user} αφαίρεσε την ετικέτα της καρτέλα {card} της λίστας {stack} του πίνακα {board} ",
"{user} has updated the due date of card {card} to {after}" : "Ο {user} ενημέρωσε την ημερομηνία λήξης της καρτέλας {card} σε {after}",
"You have added the tag {label} to card {card} in list {stack} on board {board}" : "Προσθέσατε ετικέτα {label} στην καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has added the tag {label} to card {card} in list {stack} on board {board}" : "Ο {user} πρόσθεσε ετικέτα {label} στην καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"You have removed the tag {label} from card {card} in list {stack} on board {board}" : "Αφαιρέσατε την ετικέτα {label} από την καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has removed the tag {label} from card {card} in list {stack} on board {board}" : "Ο {user} αφαίρεσε την ετικέτα {label} της καρτέλας {card} της λίστας {stack} του πίνακα {board} ",
"You have assigned {assigneduser} to card {card} on board {board}" : "Έχετε ορίσει τον {assigneduser} στην καρτέλα {card} στον πίνακα {board}",
"{user} has assigned {assigneduser} to card {card} on board {board}" : "Ο {user} έχει ορισθεί {assigneduser} στην καρτέλα {card} του πίνακα {board}",
"You have unassigned {assigneduser} from card {card} on board {board}" : "Έχετε αφαιρεθεί {assigneduser} από την καρτέλα {card} του πίνακα {board}",
"{user} has unassigned {assigneduser} from card {card} on board {board}" : "Ο {user} έχει αφαιρεθεί {assigneduser} από την καρτέλα {card} του πίνακα {board}",
"{user} has assigned {assigneduser} to card {card} on board {board}" : "Ο {user} έχει ορισθεί {assigneduser} στην καρτέλα {card} του πίνακα {board}",
"You have unassigned {assigneduser} from card {card} on board {board}" : "Έχετε αφαιρέσει {assigneduser} από την καρτέλα {card} του πίνακα {board}",
"{user} has unassigned {assigneduser} from card {card} on board {board}" : "Ο {user} έχει αφαιρεθεί {assigneduser} από την καρτέλα {card} του πίνακα {board}",
"You have moved the card {card} from list {stackBefore} to {stack}" : "Μετακινήσατε την καρτέλα {card} από την λίστα {stackBefore} στη {stack}",
"{user} has moved the card {card} from list {stackBefore} to {stack}" : "Ο {user} μετακίνησε την καρτέλα {card} από την λίστα {stackBefore} στην {stack}",
"{user} has moved the card {card} from list {stackBefore} to {stack}" : "Ο {user} μετακίνησε την καρτέλα {card} από την λίστα {stackBefore} στην {stack}",
"You have added the attachment {attachment} to card {card}" : "Προσθέσατε το συνημμένο {attachment} στην καρτέλα {card}",
"{user} has added the attachment {attachment} to card {card}" : "Ο {user} πρόσθεσε το συνημμένο {attachment} στην καρτέλα {card}",
"{user} has added the attachment {attachment} to card {card}" : "Ο {user} πρόσθεσε το συνημμένο {attachment} στην καρτέλα {card}",
"You have updated the attachment {attachment} on card {card}" : "Ενημερώσατε το συνημμένο {attachment} της καρτέλας {card}",
"{user} has updated the attachment {attachment} on card {card}" : "Ο {user} ενημέρωσε το συνημμένο {attachment} της καρτέλας {card}",
"{user} has updated the attachment {attachment} on card {card}" : "Ο {user} ενημέρωσε το συνημμένο {attachment} της καρτέλας {card}",
"You have deleted the attachment {attachment} from card {card}" : "Διαγράψατε το συνημμένο {attachment} της καρτέλας {card}",
"{user} has deleted the attachment {attachment} from card {card}" : "Ο {user} διέγραψε το συνημμένο {attachment} της καρτέλας {card}",
"{user} has deleted the attachment {attachment} from card {card}" : "Ο {user} διέγραψε το συνημμένο {attachment} της καρτέλας {card}",
"You have restored the attachment {attachment} to card {card}" : "Επαναφέρατε το συνημμένο {attachment} στην καρτέλα {card}",
"{user} has restored the attachment {attachment} to card {card}" : "Ο {user} επανέφερε το συνημμένο {attachment} στην καρτέλα {card}",
"{user} has restored the attachment {attachment} to card {card}" : "Ο {user} επανέφερε το συνημμένο {attachment} στην καρτέλα {card}",
"You have commented on card {card}" : "Σχολιάσατε την καρτέλα {card}",
"{user} has commented on card {card}" : "Ο {user} σχολίασε την καρτέλα {card}",
"{user} has commented on card {card}" : "Ο {user} σχολίασε την καρτέλα {card}",
"A <strong>card description</strong> inside the Deck app has been changed" : "Η <strong>περιγραφή καρτέλας </strong>στην εφαρμογή Deck άλλαξε",
"Deck" : "Deck",
"Changes in the <strong>Deck app</strong>" : "Αλλαγές στην <strong>εφαρμογή Deck</strong>",
"A <strong>comment</strong> was created on a card" : "Ένα <strong>σχόλιο</strong> δημιουργήθηκε σε μια καρτέλα",
"Upcoming cards" : "Επερχόμενες κάρτες",
"Upcoming cards" : "Επερχόμενες καρτέλες",
"Personal" : "Προσωπικά",
"The card \"%s\" on \"%s\" has been assigned to you by %s." : "Η καρτέλα \"%s\" του \"%s\" ανατέθηκε σε εσάς από τον %s.",
"The card \"%s\" on \"%s\" has reached its due date." : "Η κάρτα \"1%s\" στο \"1%s\" έχει λήξει.",
"%s has mentioned you in a comment on \"%s\"." : "%s σας ανέφερε σε σχόλιο στο \"%s\".",
"The board \"%s\" has been shared with you by %s." : "Ο πίνακας \"%s\" είναι σε κοινή χρήση μαζί σας %s.",
"{user} has assigned the card {deck-card} on {deck-board} to you." : "Ο/Η {user} έχει αναθέσει την καρτέλα {deck-card} του πίνακα {deck-board} σε εσάς.",
"The card \"%s\" on \"%s\" has reached its due date." : "Η καρτέλα \"%s\" στο \"%s\" έχει λήξει.",
"The card {deck-card} on {deck-board} has reached its due date." : "Η καρτέλα {deck-card} στο {deck-board} έχει λήξει.",
"%s has mentioned you in a comment on \"%s\"." : "Ο/η%s σας ανέφερε σε σχόλιο στο \"%s\".",
"{user} has mentioned you in a comment on {deck-card}." : "Ο/Η {user} σας ανέφερε σε ένα σχόλιο στο {deck-card}.",
"The board \"%s\" has been shared with you by %s." : "Ο πίνακας \"%s\" είναι σε κοινή χρήση μαζί σας από %s.",
"{user} has shared {deck-board} with you." : "Ο/Η διαμοιράστηκε μαζί σας το {deck-board}",
"Card comments" : "Σχόλια καρτέλας",
"%s on %s" : "%s στο %s",
"No data was provided to create an attachment." : "Δεν δόθηκαν στοιχεία για δημιουργία συνημμένου.",
"Finished" : "Ολοκληρώθηκε",
"To review" : "Προς επισκόπηση",
"Action needed" : "Απαιτείται ενέργεια",
"Later" : "Αργότερα",
"copy" : "Αντιγραφή",
"To do" : "Να κάνω",
"To do" : "Προς Ενέργεια",
"Doing" : "Σε εξέλιξη",
"Done" : "Ολοκληρώθηκε",
"Example Task 3" : "Παράδειγμα Εργασίας 3",
"Example Task 2" : "Παράδειγμα Εργασίας 2",
"Example Task 1" : "Παράδειγμα Εργασίας 1",
"The file was uploaded" : "Το αρχείο μεταφορτώθηκε",
"The uploaded file exceeds the upload_max_filesize directive in php.ini" : "Το μεταφορτωμένο αρχείο υπερβαίνει την οδηγία upload_max_filesize στο php.ini",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Το μεταφορτωμένο αρχείο υπερβαίνει την οδηγία MAX_FILE_SIZE που καθορίστηκε στην φόρμα HTML.",
"The uploaded file exceeds the upload_max_filesize directive in php.ini" : "Το αρχείο που εστάλη υπερβαίνει την οδηγία μέγιστου επιτρεπτού μεγέθους \"upload_max_filesize\" του php.ini",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Το ανεβασμένο αρχείο υπερβαίνει το MAX_FILE_SIZE που ορίζεται στην HTML φόρμα",
"The file was only partially uploaded" : "Το αρχείο μεταφορτώθηκε εν μέρει",
"No file was uploaded" : "Δεν μεταφορτώθηκε κάποιο αρχείο",
"Missing a temporary folder" : "Λείπει κάποιος προσωρινός φάκελος",
"Could not write file to disk" : "Αδυναμία εγγραφής αρχείου στον δίσκο",
"A PHP extension stopped the file upload" : "Ένα πρόσθετο PHP διέκοψε την μεταφόρτωση του αρχείου",
"No file uploaded or file size exceeds maximum of %s" : "Δεν μεταφορτώθηκε αρχείο ή το μέγεθος αρχείου υπερβαίνει το μέγιστο %s",
"Card not found" : "Η κάρτα δεν βρέθηκε",
"Path is already shared with this card" : "Η διαδρομή κοινοποιείται ήδη σε αυτήν την κάρτα",
"This comment has more than %s characters.\nAdded as an attachment to the card with name %s.\nAccessible on URL: %s." : "Αυτό το σχόλιο έχει περισσότερους από %s χαρακτήρες.\nΠροστέθηκε ως συνημμένο στην καρτέλα με όνομα %s .\nΠροσβάσιμο στη διεύθυνση URL: %s.",
"Card not found" : "Η καρτέλα δεν βρέθηκε",
"Path is already shared with this card" : "Η διαδρομή κοινοποιείται ήδη σε αυτήν την καρτέλα",
"Invalid date, date format must be YYYY-MM-DD" : "Μη έγκυρη ημερομηνία, η μορφή ημερομηνίας πρέπει να είναι ΕΕΕΕ-ΜΜ-ΗΗ",
"Personal planning and team project organization" : "Προσωπικός σχεδιασμός και ομαδική οργάνωση",
"Personal planning and team project organization" : "Προσωπικός σχεδιασμός και οργάνωση ομαδικών έργων",
"Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.\n\n\n- 📥 Add your tasks to cards and put them in order\n- 📄 Write down additional notes in Markdown\n- 🔖 Assign labels for even better organization\n- 👥 Share with your team, friends or family\n- 📎 Attach files and embed them in your Markdown description\n- 💬 Discuss with your team using comments\n- ⚡ Keep track of changes in the activity stream\n- 🚀 Get your project organized" : "Το Deck είναι ένα εργαλείο οργάνωσης τύπου kanban με στόχο τον προσωπικό προγραμματισμό και την οργάνωση έργων για ομάδες που έχουν ενσωματωθεί στο Nextcloud.\n\n\n- 📥 Προσθέστε τις εργασίες σας στις καρτέλες και βάλτε τες στη σειρά\n- 📄 Γράψτε τις πρόσθετες σημειώσεις\n- 🔖 Αντιστοιχίστε τις ετικέτες για ακόμη καλύτερη οργάνωση\n- 👥 Μοιραστείτε με την ομάδα, φίλους ή την οικογένειά σας\n- 📎 Επισυνάψτε αρχεία και ενσωματώστε τα στην περιγραφή\n- 💬 Συζητήστε με την ομάδα σας χρησιμοποιώντας σχόλια\n- ⚡ Παρακολουθήστε τις αλλαγές στη ροή δραστηριοτήτων\n- 🚀 Έχετε τα όλα οργανωμένα",
"Card details" : "Λεπτομέρειες καρτέλας",
"Add board" : "Προσθήκη πίνακα",
"Select the board to link to a project" : "Επιλέξτε πίνακα και συνδέστε τον σε έργο",
"Select the board to link to a project" : "Επιλέξτε πίνακα και συνδέστε τον σε ένα έργο",
"Search by board title" : "Αναζήτηση με το όνομα πίνακα",
"Select board" : "Επιλογή πίνακα",
"Create a new card" : "Δημιουργία νέας κάρτας",
"Create a new card" : "Δημιουργία νέας καρτέλας",
"Select a board" : "Επιλογή ενός πίνακα",
"Select a list" : "Επιλέξτε μια λίστα",
"Card title" : "Τίτλος κάρτας",
"Card title" : "Τίτλος καρτέλας",
"Cancel" : "Aκύρωση",
"Open card" : "Άνοιγμα κάρτας",
"Creating the new card" : "Γίνεται δημιουργία της νέας καρτέλας...",
"Card \"{card}\" was added to \"{board}\"" : "Η καρτέλα \"{card}\" προστέθηκε στο \"{board}\"",
"Open card" : "Άνοιγμα καρτέλας",
"Close" : "Κλείσιμο",
"Create card" : "Δημιουργία κάρτας",
"Select a card" : "Επίλογή μιας καρτέλας",
"Create card" : "Δημιουργία καρτέλας",
"Select a card" : "Επιλογή μιας καρτέλας",
"Select the card to link to a project" : "Επιλογή καρτέλας για σύνδεση στο έργο",
"Link to card" : "Σύνδεσμος σε καρτέλα",
"File already exists" : "Το αρχείο υπάρχει ήδη",
"A file with the name {filename} already exists." : "Το αρχείο με όνομα {filename} υπάρχει ήδη.",
"Do you want to overwrite it?" : "Επιθυμείτε να γίνει αντικατάσταση του?",
"Overwrite file" : "Αντικατάσταση αρχείου",
"Keep existing file" : "Διατήρηση υπάρχων αρχείου",
"Keep existing file" : "Διατήρηση υπάρχοντος αρχείου",
"This board is read only" : "Ο πίνακας είναι μόνο για ανάγνωση",
"Drop your files to upload" : "Αποθέστε τα αρχεία σας για ανέβασμα",
"Add card" : "Προσθήκη κάρτας",
"Archived cards" : "Αρχειοθετημένες κάρτες",
"Add card" : "Προσθήκη καρτέλας",
"Archived cards" : "Αρχειοθετημένες καρτέλες",
"Add list" : "Προσθήκη λίστας",
"List name" : "Λίστα ονομάτων",
"List name" : "Όνομα λίστας",
"Apply filter" : "Εφαρμογή φίλτρου",
"Filter by tag" : "Φίλτρο ανά ετικέτα",
"Filter by assigned user" : "Φίλτρο ανά χρήστη",
"Unassigned" : "Χωρίς ανάθεση",
"Filter by due date" : "Φίλτρο ανά ημερομηνία λήξης",
"Overdue" : "Εκπρόθεσμος",
"Overdue" : "Εκπρόθεσμες",
"Next 24 hours" : "Επόμενες 24 ώρες",
"Next 7 days" : "Επόμενες 7 ημέρες",
"Next 30 days" : "Επόμενες 30 ημέρες",
@@ -147,7 +157,7 @@ OC.L10N.register(
"Sharing" : "Διαμοιρασμός",
"Tags" : "Ετικέτες",
"Deleted items" : "Διαγραμμένα αντικείμενα",
"Timeline" : "Χρονοδιάγραμμα",
"Timeline" : "Χρονολόγιο",
"Deleted lists" : "Διαγραμμένες λίστες",
"Undo" : "Αναίρεση",
"Deleted cards" : "Διαγραμμένες καρτέλες",
@@ -167,7 +177,7 @@ OC.L10N.register(
"Delete list" : "Διαγραφή λίστας",
"Archive all cards in this list" : "Αρχειοθέτηση όλων των καρτελών σε αυτή τη λίστα.",
"Add a new card" : "Προσθήκη νέας καρτέλας",
"Card name" : "Όνομα κάρτας",
"Card name" : "Όνομα καρτέλας",
"List deleted" : "Η λίστα διαγράφηκε",
"Edit" : "Επεξεργασία",
"Add a new tag" : "Προσθήκη νέας ετικέτας",
@@ -175,14 +185,17 @@ OC.L10N.register(
"Board name" : "Όνομα πίνακα",
"Members" : "Μέλη",
"Upload new files" : "Ανεβάστε νέα αρχεία",
"Share from Files" : "Κοινή χρήση από αρχεία",
"Share from Files" : "Κοινή χρήση από Αρχεία",
"Pending share" : "Κοινή χρήση σε εκκρεμότητα",
"Add this attachment" : "Προσθήκη αυτού του συνημμένου",
"Show in Files" : "Εμφάνιση σε αρχεία",
"Delete Attachment" : "Διαγραφή Συνημμένου",
"Restore Attachment" : "Επαναφορά Συνημμένου",
"Show in Files" : "Εμφάνιση σε Αρχεία",
"Download" : "Λήψη",
"Remove attachment" : "Αφαίρεση συνημμένου",
"Delete Attachment" : "Διαγραφή συνημμένου",
"Restore Attachment" : "Επαναφορά συνημμένου",
"File to share" : "Αρχείο για κοινή χρήση",
"Invalid path selected" : "Επιλέχθηκε μη έγκυρη διαδρομή",
"Open in sidebar view" : "Άνοιγμα σε προβολή πλευρικής γραμμής",
"Open in sidebar view" : "Άνοιγμα σε προβολή πλευρικής στήλης",
"Open in bigger view" : "Άνοιγμα σε μεγαλύτερη προβολή",
"Attachments" : "Συνημμένα",
"Comments" : "Σχόλια",
@@ -190,20 +203,24 @@ OC.L10N.register(
"Created" : "Δημιουργήθηκε",
"The title cannot be empty." : "Ο τίτλος δεν μπορεί να είναι κενός.",
"No comments yet. Begin the discussion!" : "Χωρίς σχόλια ακόμη. Ξεκινήστε την συζήτηση!",
"Failed to load comments" : "Αποτυχία φόρτωσης σχολίων",
"Assign a tag to this card…" : "Ορίστε μια ετικέτα σε αυτήν την καρτέλα...",
"Assign to users" : "Αναθέστε στους χρήστες",
"Assign to users" : "Ανάθεση σε χρήστες",
"Assign to users/groups/circles" : "Ανάθεση σε χρήστες/ομάδες/κύκλους",
"Assign a user to this card…" : "Αναθέστε χρήστη στην καρτέλα...",
"Assign a user to this card…" : "Ανάθεση χρήστη στην καρτέλα...",
"Due date" : "Ημερομηνία λήξης",
"Set a due date" : "Καθορίστε ημερομηνίας λήξης",
"Remove due date" : "Αφαίρεση ημερομηνίας λήξης",
"Select Date" : "Επέλεξε Ημέρα",
"Select Date" : "Επιλέξτε ημερομηνία",
"Today" : "Σήμερα",
"Tomorrow" : "Αύριο",
"Next week" : "Επόμενη εβδομάδα",
"Next month" : "Επόμενος μήνας",
"Save" : "Αποθήκευση",
"The comment cannot be empty." : "Το σχόλιο δεν μπορεί να είναι κενό.",
"The comment cannot be longer than 1000 characters." : "Το σχόλιο δεν μπορεί να έχι περισσότερους από 1000 χαρακτήρες.",
"In reply to" : "Ως απάντηση σε",
"In reply to" : "Σε απάντηση σε",
"Cancel reply" : "Ακύρωση απάντησης",
"Reply" : "Απάντηση",
"Update" : "Ενημέρωση",
"Description" : "Περιγραφή",
@@ -216,21 +233,23 @@ OC.L10N.register(
"Write a description …" : "Γράψτε μια περιγραφή…",
"Choose attachment" : "Επιλογή συνημμένου",
"(group)" : "(ομάδα)",
"{count} comments, {unread} unread" : "{count} σχόλια, {unread} μη αναγνωσμένα",
"Assign to me" : "Ανάθεση σε εμένα",
"Unassign myself" : "Αποδέσμευσή μου",
"Move card" : "Μετακίνηση κάρτας",
"Unarchive card" : "Αναίρεση αρχειοθέτησης κάρτας",
"Archive card" : "Αρχειοθέτηση κάρτας",
"Delete card" : "Διαγραφή κάρτας",
"Move card" : "Μετακίνηση καρτέλας",
"Unarchive card" : "Αναίρεση αρχειοθέτησης καρτέλας",
"Archive card" : "Αρχειοθέτηση καρτέλας",
"Delete card" : "Διαγραφή καρτέλας",
"Move card to another board" : "Μετακίνηση καρτέλας σε άλλο πίνακα",
"Card deleted" : "Η κάρτα διαγράφηκε",
"List is empty" : "Η λίστα είναι άδεια.",
"Card deleted" : "Η καρτέλα διαγράφηκε",
"seconds ago" : " δευτερόλεπτα πριν ",
"All boards" : "Όλοι οι πίνακες",
"Archived boards" : "Αρχειοθέτηση πινάκων ",
"Shared with you" : "Διαμοιρασμένα μαζί σας",
"Use bigger card view" : "Χρησιμοποιήστε μεγαλύτερη προβολή κάρτας",
"Use bigger card view" : "Χρησιμοποιήστε μεγαλύτερη προβολή καρτέλας",
"Show boards in calendar/tasks" : "Εμφάνιση πινάκων στο ημερολόγιο / εργασίες",
"Limit deck usage of groups" : "Περιορίστε τη χρήση της εφαρμογής σε ομάδες",
"Limit deck usage of groups" : "Περιορίστε τη χρήση της εφαρμογής deck σε ομάδες",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Ο περιορισμός του Deck θα εμποδίσει τους χρήστες που δεν είναι μέρος αυτών των ομάδων να δημιουργούν δικούς τους πίνακες. Οι χρήστες θα εξακολουθήσουν να εργάζονται σε πίνακες που έχουν διαμοιραστεί μαζί τους.",
"Board details" : "Λεπτομέριες πίνακα",
"Edit board" : "Επεξεργασία πίνακα",
@@ -240,31 +259,37 @@ OC.L10N.register(
"Turn on due date reminders" : "Ενεργοποιήστε τις υπενθυμίσεις ημερομηνίας προθεσμίας",
"Turn off due date reminders" : "Απενεργοποιήστε τις υπενθυμίσεις ημερομηνίας προθεσμίας",
"Due date reminders" : "Υπενθυμίσεις ημερομηνίας προθεσμίας",
"All cards" : "Όλες οι κάρτες",
"Assigned cards" : "Ανατεθείς κάρτες",
"All cards" : "Όλες οι καρτέλες",
"Assigned cards" : "Ανατεθειμένες καρτέλες",
"No notifications" : "Δεν υπάρχουν ειδοποιήσεις",
"Delete board" : "Διαγραφή πίνακα",
"Board {0} deleted" : "Διαγράφηκε {0} πίνακας",
"Only assigned cards" : "Μόνο κάρτες που έχουν ανατεθεί",
"Board {0} deleted" : "Διαγράφηκε {0} πίνακας ",
"Only assigned cards" : "Μόνο καρτέλες που έχουν ανατεθεί",
"No reminder" : "Δεν υπάρχει υπενθύμιση",
"An error occurred" : "Παρουσιάστηκε σφάλμα",
"Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον πίνακα {title}; Θα διαγραφούν όλα τα δεδομένα.",
"Delete the board?" : "Διαγραφή πίνακα;",
"Delete the board?" : "Διαγραφή του πίνακα;",
"Loading filtered view" : "Φόρτωση εμφάνισης με βάση το φίλτρο",
"This week" : "Αυτή την εβδομάδα",
"No due" : "Χωρίς λήξη",
"No upcoming cards" : "Δεν υπάρχουν επερχόμενες κάρτες",
"upcoming cards" : "Επερχόμενες κάρτες",
"Search for {searchQuery} in all boards" : "Αναζήτηση για {searchQuery} σε όλους τους πίνακες",
"No results found" : "Δεν βρέθηκαν αποτελέσματα",
"No upcoming cards" : "Δεν υπάρχουν επερχόμενες καρτέλες",
"upcoming cards" : "επερχόμενες καρτέλες",
"Link to a board" : "Σύνδεσμος στον πίνακα",
"Link to a card" : "Σύνδεσμος σε καρτέλα",
"Create a card" : "Δημιουργία κάρτας",
"Create a card" : "Δημιουργία καρτέλας",
"Message from {author} in {conversationName}" : "Μήνυμα από {author} σε {conversationName}",
"Something went wrong" : "Κάτι πήγε στραβά",
"Failed to upload {name}" : "Αποτυχία μεταφόρτωσης {όνομα}",
"Failed to upload {name}" : "Αποτυχία μεταφόρτωσης {name}",
"Maximum file size of {size} exceeded" : "Υπέρβαση επιτρεπόμενου μεγέθους αρχείου {size}",
"Error creating the share" : "Σφάλμα κατά τη δημιουργία της κοινοποίησης",
"Share with a Deck card" : "Μοιραστείτε με μια κάρτα Deck",
"Share {file} with a Deck card" : "Μοιραστείτε {αρχείο} με μια κάρτα Deck",
"Share with a Deck card" : "Μοιραστείτε με μια καρτέλα Deck",
"Share {file} with a Deck card" : "Μοιραστείτε το {file} με μια καρτέλα Deck",
"Share" : "Μοιραστείτε",
"Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.\n\n\n- 📥 Add your tasks to cards and put them in order\n- 📄 Write down additional notes in markdown\n- 🔖 Assign labels for even better organization\n- 👥 Share with your team, friends or family\n- 📎 Attach files and embed them in your markdown description\n- 💬 Discuss with your team using comments\n- ⚡ Keep track of changes in the activity stream\n- 🚀 Get your project organized" : "Το Deck είναι ένα εργαλείο οργάνωσης τύπου kanban με στόχο τον προσωπικό προγραμματισμό και την ομαδική οργάνωση για ομάδες που έχουν ενσωματωθεί στο Nextcloud.\n\n\n- 📥 Προσθέστε τις εργασίες σας στις καρτέλες και βάλτε τες στη σειρά\n- 📄 Γράψτε τις πρόσθετες σημειώσεις\n- 🔖 Αντιστοιχίστε τις ετικέτες για ακόμη καλύτερη οργάνωση\n- 👥 Μοιραστείτε με την ομάδα, φίλους ή την οικογένειά σας\n- 📎 Συνδέστε αρχεία και ενσωματώστε τα στην περιγραφή\n- 💬 Συζητήστε με την ομάδα σας χρησιμοποιώντας σχόλια\n- ⚡ Παρακολουθήστε τις αλλαγές στη ροή δραστηριοτήτων\n- 🚀 Έχετε τα όλα οργανωμένα"
"Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.\n\n\n- 📥 Add your tasks to cards and put them in order\n- 📄 Write down additional notes in markdown\n- 🔖 Assign labels for even better organization\n- 👥 Share with your team, friends or family\n- 📎 Attach files and embed them in your markdown description\n- 💬 Discuss with your team using comments\n- ⚡ Keep track of changes in the activity stream\n- 🚀 Get your project organized" : "Το Deck είναι ένα εργαλείο οργάνωσης τύπου kanban με στόχο τον προσωπικό προγραμματισμό και την οργάνωση έργων για ομάδες που έχουν ενσωματωθεί στο Nextcloud.\n\n\n- 📥 Προσθέστε τις εργασίες σας στις καρτέλες και βάλτε τες στη σειρά\n- 📄 Γράψτε τις πρόσθετες σημειώσεις\n- 🔖 Αντιστοιχίστε τις ετικέτες για ακόμη καλύτερη οργάνωση\n- 👥 Μοιραστείτε με την ομάδα, φίλους ή την οικογένειά σας\n- 📎 Συνδέστε αρχεία και ενσωματώστε τα στην περιγραφή\n- 💬 Συζητήστε με την ομάδα σας χρησιμοποιώντας σχόλια\n- ⚡ Παρακολουθήστε τις αλλαγές στη ροή δραστηριοτήτων\n- 🚀 Έχετε τα όλα οργανωμένα",
"Creating the new card…" : "Δημιουργία νέας καρτέλας...",
"\"{card}\" was added to \"{board}\"" : "\"{card}\" προστέθηκε στο \"{board}\"",
"(circle)" : "(κύκλος)"
},
"nplurals=2; plural=(n != 1);");
+111 -86
View File
@@ -1,134 +1,144 @@
{ "translations": {
"You have created a new board {board}" : "Δημιουργήσατε νέο πίνακα {board}",
"{user} has created a new board {board}" : "Ο {user} δημιούργησε νέο πίνακα {board}",
"{user} has created a new board {board}" : "Ο {user} δημιούργησε νέο πίνακα {board}",
"You have deleted the board {board}" : "Έχετε διαγράψει τον πίνακα {board}",
"{user} has deleted the board {board}" : "Ο {user} διέγραψε τον πίνακα {board}",
"{user} has deleted the board {board}" : "Ο {user} διέγραψε τον πίνακα {board}",
"You have restored the board {board}" : "Εχετε επαναφέρει τον πίνακα {board}",
"{user} has restored the board {board}" : "Ο {user} επανέφερε τον πίνακα {board}",
"{user} has restored the board {board}" : "Ο {user} επανέφερε τον πίνακα {board}",
"You have shared the board {board} with {acl}" : "Εχετε διαμοιράσει τον πίνακα {board} με {acl}",
"{user} has shared the board {board} with {acl}" : "Ο {user} διαμοίρασε τον πίνακα {board} με {acl}",
"{user} has shared the board {board} with {acl}" : "Ο {user} διαμοίρασε τον πίνακα {board} με {acl}",
"You have removed {acl} from the board {board}" : "Αφαιρέθηκε η {acl} από τον πίνακα {board}",
"{user} has removed {acl} from the board {board}" : "Ο {user} αφαίρεσε την {acl} από τον πίνακα {board}",
"{user} has removed {acl} from the board {board}" : "Ο {user} αφαίρεσε την {acl} από τον πίνακα {board}",
"You have renamed the board {before} to {board}" : "Μετονομάσατε τον πίνακα {before} σε {board}",
"{user} has renamed the board {before} to {board}" : "Ο {user} μετονόμασε τον πίνακα {before} σε {board}",
"{user} has renamed the board {before} to {board}" : "Ο {user} μετονόμασε τον πίνακα {before} σε {board}",
"You have archived the board {board}" : "Αρχειοθετήσατε τον πίνακα {board}",
"{user} has archived the board {before}" : "Ο {user} αρχειοθέτησε τον πίνακα {before}",
"You have unarchived the board {board}" : "Επαναφέρατε τον πίνακα {board} από αρχείο",
"{user} has unarchived the board {before}" : "Ο {user} επανέφερε τον πίνακα {before} από αρχείο",
"{user} has archived the board {before}" : "Ο {user} αρχειοθέτησε τον πίνακα {before}",
"You have unarchived the board {board}" : "Επαναφέρατε τον πίνακα {board} από το αρχείο",
"{user} has unarchived the board {before}" : "Ο {user} επανέφερε τον πίνακα {before} από αρχείο",
"You have created a new list {stack} on board {board}" : "Έχετε δημιουργήσει μια νέα λίστα {stack} στον πίνακα {board}",
"{user} has created a new list {stack} on board {board}" : "Ο {user} δημιούργησε μια νέα λίστα {stack} στον πίνακα {board}",
"{user} has created a new list {stack} on board {board}" : "Ο {user} δημιούργησε μια νέα λίστα {stack} στον πίνακα {board}",
"You have renamed list {before} to {stack} on board {board}" : "Μετονομάσατε την λίστα {before} σε {stack} στον πίνακα {board}",
"{user} has renamed list {before} to {stack} on board {board}" : "Ο {user} μετονόμασε την λίστα {before} σε {stack} στον πίνακα {board}",
"{user} has renamed list {before} to {stack} on board {board}" : "Ο {user} μετονόμασε την λίστα {before} σε {stack} στον πίνακα {board}",
"You have deleted list {stack} on board {board}" : "Διαγράψατε την λίστα {stack} στον πίνακα {board}",
"{user} has deleted list {stack} on board {board}" : "Ο {user} διέγραψε την λίστα {stack} στον πίνακα {board}",
"{user} has deleted list {stack} on board {board}" : "Ο {user} διέγραψε την λίστα {stack} στον πίνακα {board}",
"You have created card {card} in list {stack} on board {board}" : "Δημιουργήσατε την καρτέλα {card} στην λίστα {stack} του πίνακα {board}",
"{user} has created card {card} in list {stack} on board {board}" : "Ο {user} δημιούργησε την καρτέλα {card} στην λίστα {stack} του πίνακα {board}",
"{user} has created card {card} in list {stack} on board {board}" : "Ο {user} δημιούργησε την καρτέλα {card} στην λίστα {stack} του πίνακα {board}",
"You have deleted card {card} in list {stack} on board {board}" : "Διαγράψατε την καρτέλα {card} στην λίστα {stack} του πίνακα {board}",
"{user} has deleted card {card} in list {stack} on board {board}" : "Ο {user} διέγραψε την καρτέλα {card} στην λίστα {stack} του πίνακα {board}",
"{user} has deleted card {card} in list {stack} on board {board}" : "Ο {user} διέγραψε την καρτέλα {card} στην λίστα {stack} του πίνακα {board}",
"You have renamed the card {before} to {card}" : "Μετονομάσατε την καρτέλα {before} σε {card}",
"{user} has renamed the card {before} to {card}" : "Ο {user} μετονόμασε την καρτέλα {before} σε {card}",
"{user} has renamed the card {before} to {card}" : "Ο {user} μετονόμασε την καρτέλα {before} σε {card}",
"You have added a description to card {card} in list {stack} on board {board}" : "Προσθέσατε μια περιγραφή στην καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has added a description to card {card} in list {stack} on board {board}" : "Ο {user} πρόσθεσε μια περιγραφή στην καρτέλα {card} της λίστας {stack} του πίνακα {board} ",
"{user} has added a description to card {card} in list {stack} on board {board}" : "Ο {user} πρόσθεσε μια περιγραφή στην καρτέλα {card} της λίστας {stack} του πίνακα {board} ",
"You have updated the description of card {card} in list {stack} on board {board}" : "Ενημερώσατε την περιγραφή στην καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has updated the description of the card {card} in list {stack} on board {board}" : "Ο {user} ενημέρωσε την περιγραφή της καρτέλας {card} στη λίστα {stack} του πίνακα {board}",
"{user} has updated the description of the card {card} in list {stack} on board {board}" : "Ο {user} ενημέρωσε την περιγραφή της καρτέλας {card} στη λίστα {stack} του πίνακα {board}",
"You have archived card {card} in list {stack} on board {board}" : "Αρχειοθετήσατε την κάρτα {card} στην λίστα {stack} του πίνακα {board} ",
"{user} has archived card {card} in list {stack} on board {board}" : "Ο {user} αρχειοθέτησε την κάρτα {card} στην λίστα {stack} του πίνακα {board} ",
"{user} has archived card {card} in list {stack} on board {board}" : "Ο {user} αρχειοθέτησε την κάρτα {card} στην λίστα {stack} του πίνακα {board} ",
"You have unarchived card {card} in list {stack} on board {board}" : "Επαναφέρατε από το αρχείο την καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has unarchived card {card} in list {stack} on board {board}" : "Ο {user} επανέφερε από το αρχείο την κάρτα {card} της λίστας {stack} του πίνακα {board}",
"{user} has unarchived card {card} in list {stack} on board {board}" : "Ο {user} επανέφερε από το αρχείο την κάρτα {card} της λίστας {stack} του πίνακα {board}",
"You have removed the due date of card {card}" : "Καταργήσατε την ημερομηνία λήξης της καρτέλας {card}",
"{user} has removed the due date of card {card}" : "Ο {user} κατήργησε την ημερομηνία λήξης της καρτέλας {card}",
"{user} has removed the due date of card {card}" : "Ο {user} κατάργησε την ημερομηνία λήξης της καρτέλας {card}",
"You have set the due date of card {card} to {after}" : "Ορίσατε την ημερομηνία λήξης της καρτέλας {card} σε {after}",
"{user} has set the due date of card {card} to {after}" : "Ο {user} όρισε την ημερομηνία λήξης της καρτέλας {card} σε {after} ",
"{user} has set the due date of card {card} to {after}" : "Ο {user} όρισε την ημερομηνία λήξης της καρτέλας {card} σε {after} ",
"You have updated the due date of card {card} to {after}" : "Ενημερώσατε την ημερομηνία λήξης της καρτέλας {card} σε {after}",
"{user} has updated the due date of card {card} to {after}" : "Ο {user} ενημέρωσε την ημερομηνία λήξης της καρτέλας {card} σε {after}",
"You have added the tag {label} to card {card} in list {stack} on board {board}" : "Προσθέσατε ετικέτα στην καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has added the tag {label} to card {card} in list {stack} on board {board}" : "Ο {user} πρόσθεσε ετικέτα στην καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"You have removed the tag {label} from card {card} in list {stack} on board {board}" : "Αφαιρέσατε την ετικέτα από την καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has removed the tag {label} from card {card} in list {stack} on board {board}" : "Ο {user} αφαίρεσε την ετικέτα της καρτέλα {card} της λίστας {stack} του πίνακα {board} ",
"{user} has updated the due date of card {card} to {after}" : "Ο {user} ενημέρωσε την ημερομηνία λήξης της καρτέλας {card} σε {after}",
"You have added the tag {label} to card {card} in list {stack} on board {board}" : "Προσθέσατε ετικέτα {label} στην καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has added the tag {label} to card {card} in list {stack} on board {board}" : "Ο {user} πρόσθεσε ετικέτα {label} στην καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"You have removed the tag {label} from card {card} in list {stack} on board {board}" : "Αφαιρέσατε την ετικέτα {label} από την καρτέλα {card} της λίστας {stack} του πίνακα {board}",
"{user} has removed the tag {label} from card {card} in list {stack} on board {board}" : "Ο {user} αφαίρεσε την ετικέτα {label} της καρτέλας {card} της λίστας {stack} του πίνακα {board} ",
"You have assigned {assigneduser} to card {card} on board {board}" : "Έχετε ορίσει τον {assigneduser} στην καρτέλα {card} στον πίνακα {board}",
"{user} has assigned {assigneduser} to card {card} on board {board}" : "Ο {user} έχει ορισθεί {assigneduser} στην καρτέλα {card} του πίνακα {board}",
"You have unassigned {assigneduser} from card {card} on board {board}" : "Έχετε αφαιρεθεί {assigneduser} από την καρτέλα {card} του πίνακα {board}",
"{user} has unassigned {assigneduser} from card {card} on board {board}" : "Ο {user} έχει αφαιρεθεί {assigneduser} από την καρτέλα {card} του πίνακα {board}",
"{user} has assigned {assigneduser} to card {card} on board {board}" : "Ο {user} έχει ορισθεί {assigneduser} στην καρτέλα {card} του πίνακα {board}",
"You have unassigned {assigneduser} from card {card} on board {board}" : "Έχετε αφαιρέσει {assigneduser} από την καρτέλα {card} του πίνακα {board}",
"{user} has unassigned {assigneduser} from card {card} on board {board}" : "Ο {user} έχει αφαιρεθεί {assigneduser} από την καρτέλα {card} του πίνακα {board}",
"You have moved the card {card} from list {stackBefore} to {stack}" : "Μετακινήσατε την καρτέλα {card} από την λίστα {stackBefore} στη {stack}",
"{user} has moved the card {card} from list {stackBefore} to {stack}" : "Ο {user} μετακίνησε την καρτέλα {card} από την λίστα {stackBefore} στην {stack}",
"{user} has moved the card {card} from list {stackBefore} to {stack}" : "Ο {user} μετακίνησε την καρτέλα {card} από την λίστα {stackBefore} στην {stack}",
"You have added the attachment {attachment} to card {card}" : "Προσθέσατε το συνημμένο {attachment} στην καρτέλα {card}",
"{user} has added the attachment {attachment} to card {card}" : "Ο {user} πρόσθεσε το συνημμένο {attachment} στην καρτέλα {card}",
"{user} has added the attachment {attachment} to card {card}" : "Ο {user} πρόσθεσε το συνημμένο {attachment} στην καρτέλα {card}",
"You have updated the attachment {attachment} on card {card}" : "Ενημερώσατε το συνημμένο {attachment} της καρτέλας {card}",
"{user} has updated the attachment {attachment} on card {card}" : "Ο {user} ενημέρωσε το συνημμένο {attachment} της καρτέλας {card}",
"{user} has updated the attachment {attachment} on card {card}" : "Ο {user} ενημέρωσε το συνημμένο {attachment} της καρτέλας {card}",
"You have deleted the attachment {attachment} from card {card}" : "Διαγράψατε το συνημμένο {attachment} της καρτέλας {card}",
"{user} has deleted the attachment {attachment} from card {card}" : "Ο {user} διέγραψε το συνημμένο {attachment} της καρτέλας {card}",
"{user} has deleted the attachment {attachment} from card {card}" : "Ο {user} διέγραψε το συνημμένο {attachment} της καρτέλας {card}",
"You have restored the attachment {attachment} to card {card}" : "Επαναφέρατε το συνημμένο {attachment} στην καρτέλα {card}",
"{user} has restored the attachment {attachment} to card {card}" : "Ο {user} επανέφερε το συνημμένο {attachment} στην καρτέλα {card}",
"{user} has restored the attachment {attachment} to card {card}" : "Ο {user} επανέφερε το συνημμένο {attachment} στην καρτέλα {card}",
"You have commented on card {card}" : "Σχολιάσατε την καρτέλα {card}",
"{user} has commented on card {card}" : "Ο {user} σχολίασε την καρτέλα {card}",
"{user} has commented on card {card}" : "Ο {user} σχολίασε την καρτέλα {card}",
"A <strong>card description</strong> inside the Deck app has been changed" : "Η <strong>περιγραφή καρτέλας </strong>στην εφαρμογή Deck άλλαξε",
"Deck" : "Deck",
"Changes in the <strong>Deck app</strong>" : "Αλλαγές στην <strong>εφαρμογή Deck</strong>",
"A <strong>comment</strong> was created on a card" : "Ένα <strong>σχόλιο</strong> δημιουργήθηκε σε μια καρτέλα",
"Upcoming cards" : "Επερχόμενες κάρτες",
"Upcoming cards" : "Επερχόμενες καρτέλες",
"Personal" : "Προσωπικά",
"The card \"%s\" on \"%s\" has been assigned to you by %s." : "Η καρτέλα \"%s\" του \"%s\" ανατέθηκε σε εσάς από τον %s.",
"The card \"%s\" on \"%s\" has reached its due date." : "Η κάρτα \"1%s\" στο \"1%s\" έχει λήξει.",
"%s has mentioned you in a comment on \"%s\"." : "%s σας ανέφερε σε σχόλιο στο \"%s\".",
"The board \"%s\" has been shared with you by %s." : "Ο πίνακας \"%s\" είναι σε κοινή χρήση μαζί σας %s.",
"{user} has assigned the card {deck-card} on {deck-board} to you." : "Ο/Η {user} έχει αναθέσει την καρτέλα {deck-card} του πίνακα {deck-board} σε εσάς.",
"The card \"%s\" on \"%s\" has reached its due date." : "Η καρτέλα \"%s\" στο \"%s\" έχει λήξει.",
"The card {deck-card} on {deck-board} has reached its due date." : "Η καρτέλα {deck-card} στο {deck-board} έχει λήξει.",
"%s has mentioned you in a comment on \"%s\"." : "Ο/η%s σας ανέφερε σε σχόλιο στο \"%s\".",
"{user} has mentioned you in a comment on {deck-card}." : "Ο/Η {user} σας ανέφερε σε ένα σχόλιο στο {deck-card}.",
"The board \"%s\" has been shared with you by %s." : "Ο πίνακας \"%s\" είναι σε κοινή χρήση μαζί σας από %s.",
"{user} has shared {deck-board} with you." : "Ο/Η διαμοιράστηκε μαζί σας το {deck-board}",
"Card comments" : "Σχόλια καρτέλας",
"%s on %s" : "%s στο %s",
"No data was provided to create an attachment." : "Δεν δόθηκαν στοιχεία για δημιουργία συνημμένου.",
"Finished" : "Ολοκληρώθηκε",
"To review" : "Προς επισκόπηση",
"Action needed" : "Απαιτείται ενέργεια",
"Later" : "Αργότερα",
"copy" : "Αντιγραφή",
"To do" : "Να κάνω",
"To do" : "Προς Ενέργεια",
"Doing" : "Σε εξέλιξη",
"Done" : "Ολοκληρώθηκε",
"Example Task 3" : "Παράδειγμα Εργασίας 3",
"Example Task 2" : "Παράδειγμα Εργασίας 2",
"Example Task 1" : "Παράδειγμα Εργασίας 1",
"The file was uploaded" : "Το αρχείο μεταφορτώθηκε",
"The uploaded file exceeds the upload_max_filesize directive in php.ini" : "Το μεταφορτωμένο αρχείο υπερβαίνει την οδηγία upload_max_filesize στο php.ini",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Το μεταφορτωμένο αρχείο υπερβαίνει την οδηγία MAX_FILE_SIZE που καθορίστηκε στην φόρμα HTML.",
"The uploaded file exceeds the upload_max_filesize directive in php.ini" : "Το αρχείο που εστάλη υπερβαίνει την οδηγία μέγιστου επιτρεπτού μεγέθους \"upload_max_filesize\" του php.ini",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Το ανεβασμένο αρχείο υπερβαίνει το MAX_FILE_SIZE που ορίζεται στην HTML φόρμα",
"The file was only partially uploaded" : "Το αρχείο μεταφορτώθηκε εν μέρει",
"No file was uploaded" : "Δεν μεταφορτώθηκε κάποιο αρχείο",
"Missing a temporary folder" : "Λείπει κάποιος προσωρινός φάκελος",
"Could not write file to disk" : "Αδυναμία εγγραφής αρχείου στον δίσκο",
"A PHP extension stopped the file upload" : "Ένα πρόσθετο PHP διέκοψε την μεταφόρτωση του αρχείου",
"No file uploaded or file size exceeds maximum of %s" : "Δεν μεταφορτώθηκε αρχείο ή το μέγεθος αρχείου υπερβαίνει το μέγιστο %s",
"Card not found" : "Η κάρτα δεν βρέθηκε",
"Path is already shared with this card" : "Η διαδρομή κοινοποιείται ήδη σε αυτήν την κάρτα",
"This comment has more than %s characters.\nAdded as an attachment to the card with name %s.\nAccessible on URL: %s." : "Αυτό το σχόλιο έχει περισσότερους από %s χαρακτήρες.\nΠροστέθηκε ως συνημμένο στην καρτέλα με όνομα %s .\nΠροσβάσιμο στη διεύθυνση URL: %s.",
"Card not found" : "Η καρτέλα δεν βρέθηκε",
"Path is already shared with this card" : "Η διαδρομή κοινοποιείται ήδη σε αυτήν την καρτέλα",
"Invalid date, date format must be YYYY-MM-DD" : "Μη έγκυρη ημερομηνία, η μορφή ημερομηνίας πρέπει να είναι ΕΕΕΕ-ΜΜ-ΗΗ",
"Personal planning and team project organization" : "Προσωπικός σχεδιασμός και ομαδική οργάνωση",
"Personal planning and team project organization" : "Προσωπικός σχεδιασμός και οργάνωση ομαδικών έργων",
"Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.\n\n\n- 📥 Add your tasks to cards and put them in order\n- 📄 Write down additional notes in Markdown\n- 🔖 Assign labels for even better organization\n- 👥 Share with your team, friends or family\n- 📎 Attach files and embed them in your Markdown description\n- 💬 Discuss with your team using comments\n- ⚡ Keep track of changes in the activity stream\n- 🚀 Get your project organized" : "Το Deck είναι ένα εργαλείο οργάνωσης τύπου kanban με στόχο τον προσωπικό προγραμματισμό και την οργάνωση έργων για ομάδες που έχουν ενσωματωθεί στο Nextcloud.\n\n\n- 📥 Προσθέστε τις εργασίες σας στις καρτέλες και βάλτε τες στη σειρά\n- 📄 Γράψτε τις πρόσθετες σημειώσεις\n- 🔖 Αντιστοιχίστε τις ετικέτες για ακόμη καλύτερη οργάνωση\n- 👥 Μοιραστείτε με την ομάδα, φίλους ή την οικογένειά σας\n- 📎 Επισυνάψτε αρχεία και ενσωματώστε τα στην περιγραφή\n- 💬 Συζητήστε με την ομάδα σας χρησιμοποιώντας σχόλια\n- ⚡ Παρακολουθήστε τις αλλαγές στη ροή δραστηριοτήτων\n- 🚀 Έχετε τα όλα οργανωμένα",
"Card details" : "Λεπτομέρειες καρτέλας",
"Add board" : "Προσθήκη πίνακα",
"Select the board to link to a project" : "Επιλέξτε πίνακα και συνδέστε τον σε έργο",
"Select the board to link to a project" : "Επιλέξτε πίνακα και συνδέστε τον σε ένα έργο",
"Search by board title" : "Αναζήτηση με το όνομα πίνακα",
"Select board" : "Επιλογή πίνακα",
"Create a new card" : "Δημιουργία νέας κάρτας",
"Create a new card" : "Δημιουργία νέας καρτέλας",
"Select a board" : "Επιλογή ενός πίνακα",
"Select a list" : "Επιλέξτε μια λίστα",
"Card title" : "Τίτλος κάρτας",
"Card title" : "Τίτλος καρτέλας",
"Cancel" : "Aκύρωση",
"Open card" : "Άνοιγμα κάρτας",
"Creating the new card" : "Γίνεται δημιουργία της νέας καρτέλας...",
"Card \"{card}\" was added to \"{board}\"" : "Η καρτέλα \"{card}\" προστέθηκε στο \"{board}\"",
"Open card" : "Άνοιγμα καρτέλας",
"Close" : "Κλείσιμο",
"Create card" : "Δημιουργία κάρτας",
"Select a card" : "Επίλογή μιας καρτέλας",
"Create card" : "Δημιουργία καρτέλας",
"Select a card" : "Επιλογή μιας καρτέλας",
"Select the card to link to a project" : "Επιλογή καρτέλας για σύνδεση στο έργο",
"Link to card" : "Σύνδεσμος σε καρτέλα",
"File already exists" : "Το αρχείο υπάρχει ήδη",
"A file with the name {filename} already exists." : "Το αρχείο με όνομα {filename} υπάρχει ήδη.",
"Do you want to overwrite it?" : "Επιθυμείτε να γίνει αντικατάσταση του?",
"Overwrite file" : "Αντικατάσταση αρχείου",
"Keep existing file" : "Διατήρηση υπάρχων αρχείου",
"Keep existing file" : "Διατήρηση υπάρχοντος αρχείου",
"This board is read only" : "Ο πίνακας είναι μόνο για ανάγνωση",
"Drop your files to upload" : "Αποθέστε τα αρχεία σας για ανέβασμα",
"Add card" : "Προσθήκη κάρτας",
"Archived cards" : "Αρχειοθετημένες κάρτες",
"Add card" : "Προσθήκη καρτέλας",
"Archived cards" : "Αρχειοθετημένες καρτέλες",
"Add list" : "Προσθήκη λίστας",
"List name" : "Λίστα ονομάτων",
"List name" : "Όνομα λίστας",
"Apply filter" : "Εφαρμογή φίλτρου",
"Filter by tag" : "Φίλτρο ανά ετικέτα",
"Filter by assigned user" : "Φίλτρο ανά χρήστη",
"Unassigned" : "Χωρίς ανάθεση",
"Filter by due date" : "Φίλτρο ανά ημερομηνία λήξης",
"Overdue" : "Εκπρόθεσμος",
"Overdue" : "Εκπρόθεσμες",
"Next 24 hours" : "Επόμενες 24 ώρες",
"Next 7 days" : "Επόμενες 7 ημέρες",
"Next 30 days" : "Επόμενες 30 ημέρες",
@@ -145,7 +155,7 @@
"Sharing" : "Διαμοιρασμός",
"Tags" : "Ετικέτες",
"Deleted items" : "Διαγραμμένα αντικείμενα",
"Timeline" : "Χρονοδιάγραμμα",
"Timeline" : "Χρονολόγιο",
"Deleted lists" : "Διαγραμμένες λίστες",
"Undo" : "Αναίρεση",
"Deleted cards" : "Διαγραμμένες καρτέλες",
@@ -165,7 +175,7 @@
"Delete list" : "Διαγραφή λίστας",
"Archive all cards in this list" : "Αρχειοθέτηση όλων των καρτελών σε αυτή τη λίστα.",
"Add a new card" : "Προσθήκη νέας καρτέλας",
"Card name" : "Όνομα κάρτας",
"Card name" : "Όνομα καρτέλας",
"List deleted" : "Η λίστα διαγράφηκε",
"Edit" : "Επεξεργασία",
"Add a new tag" : "Προσθήκη νέας ετικέτας",
@@ -173,14 +183,17 @@
"Board name" : "Όνομα πίνακα",
"Members" : "Μέλη",
"Upload new files" : "Ανεβάστε νέα αρχεία",
"Share from Files" : "Κοινή χρήση από αρχεία",
"Share from Files" : "Κοινή χρήση από Αρχεία",
"Pending share" : "Κοινή χρήση σε εκκρεμότητα",
"Add this attachment" : "Προσθήκη αυτού του συνημμένου",
"Show in Files" : "Εμφάνιση σε αρχεία",
"Delete Attachment" : "Διαγραφή Συνημμένου",
"Restore Attachment" : "Επαναφορά Συνημμένου",
"Show in Files" : "Εμφάνιση σε Αρχεία",
"Download" : "Λήψη",
"Remove attachment" : "Αφαίρεση συνημμένου",
"Delete Attachment" : "Διαγραφή συνημμένου",
"Restore Attachment" : "Επαναφορά συνημμένου",
"File to share" : "Αρχείο για κοινή χρήση",
"Invalid path selected" : "Επιλέχθηκε μη έγκυρη διαδρομή",
"Open in sidebar view" : "Άνοιγμα σε προβολή πλευρικής γραμμής",
"Open in sidebar view" : "Άνοιγμα σε προβολή πλευρικής στήλης",
"Open in bigger view" : "Άνοιγμα σε μεγαλύτερη προβολή",
"Attachments" : "Συνημμένα",
"Comments" : "Σχόλια",
@@ -188,20 +201,24 @@
"Created" : "Δημιουργήθηκε",
"The title cannot be empty." : "Ο τίτλος δεν μπορεί να είναι κενός.",
"No comments yet. Begin the discussion!" : "Χωρίς σχόλια ακόμη. Ξεκινήστε την συζήτηση!",
"Failed to load comments" : "Αποτυχία φόρτωσης σχολίων",
"Assign a tag to this card…" : "Ορίστε μια ετικέτα σε αυτήν την καρτέλα...",
"Assign to users" : "Αναθέστε στους χρήστες",
"Assign to users" : "Ανάθεση σε χρήστες",
"Assign to users/groups/circles" : "Ανάθεση σε χρήστες/ομάδες/κύκλους",
"Assign a user to this card…" : "Αναθέστε χρήστη στην καρτέλα...",
"Assign a user to this card…" : "Ανάθεση χρήστη στην καρτέλα...",
"Due date" : "Ημερομηνία λήξης",
"Set a due date" : "Καθορίστε ημερομηνίας λήξης",
"Remove due date" : "Αφαίρεση ημερομηνίας λήξης",
"Select Date" : "Επέλεξε Ημέρα",
"Select Date" : "Επιλέξτε ημερομηνία",
"Today" : "Σήμερα",
"Tomorrow" : "Αύριο",
"Next week" : "Επόμενη εβδομάδα",
"Next month" : "Επόμενος μήνας",
"Save" : "Αποθήκευση",
"The comment cannot be empty." : "Το σχόλιο δεν μπορεί να είναι κενό.",
"The comment cannot be longer than 1000 characters." : "Το σχόλιο δεν μπορεί να έχι περισσότερους από 1000 χαρακτήρες.",
"In reply to" : "Ως απάντηση σε",
"In reply to" : "Σε απάντηση σε",
"Cancel reply" : "Ακύρωση απάντησης",
"Reply" : "Απάντηση",
"Update" : "Ενημέρωση",
"Description" : "Περιγραφή",
@@ -214,21 +231,23 @@
"Write a description …" : "Γράψτε μια περιγραφή…",
"Choose attachment" : "Επιλογή συνημμένου",
"(group)" : "(ομάδα)",
"{count} comments, {unread} unread" : "{count} σχόλια, {unread} μη αναγνωσμένα",
"Assign to me" : "Ανάθεση σε εμένα",
"Unassign myself" : "Αποδέσμευσή μου",
"Move card" : "Μετακίνηση κάρτας",
"Unarchive card" : "Αναίρεση αρχειοθέτησης κάρτας",
"Archive card" : "Αρχειοθέτηση κάρτας",
"Delete card" : "Διαγραφή κάρτας",
"Move card" : "Μετακίνηση καρτέλας",
"Unarchive card" : "Αναίρεση αρχειοθέτησης καρτέλας",
"Archive card" : "Αρχειοθέτηση καρτέλας",
"Delete card" : "Διαγραφή καρτέλας",
"Move card to another board" : "Μετακίνηση καρτέλας σε άλλο πίνακα",
"Card deleted" : "Η κάρτα διαγράφηκε",
"List is empty" : "Η λίστα είναι άδεια.",
"Card deleted" : "Η καρτέλα διαγράφηκε",
"seconds ago" : " δευτερόλεπτα πριν ",
"All boards" : "Όλοι οι πίνακες",
"Archived boards" : "Αρχειοθέτηση πινάκων ",
"Shared with you" : "Διαμοιρασμένα μαζί σας",
"Use bigger card view" : "Χρησιμοποιήστε μεγαλύτερη προβολή κάρτας",
"Use bigger card view" : "Χρησιμοποιήστε μεγαλύτερη προβολή καρτέλας",
"Show boards in calendar/tasks" : "Εμφάνιση πινάκων στο ημερολόγιο / εργασίες",
"Limit deck usage of groups" : "Περιορίστε τη χρήση της εφαρμογής σε ομάδες",
"Limit deck usage of groups" : "Περιορίστε τη χρήση της εφαρμογής deck σε ομάδες",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Ο περιορισμός του Deck θα εμποδίσει τους χρήστες που δεν είναι μέρος αυτών των ομάδων να δημιουργούν δικούς τους πίνακες. Οι χρήστες θα εξακολουθήσουν να εργάζονται σε πίνακες που έχουν διαμοιραστεί μαζί τους.",
"Board details" : "Λεπτομέριες πίνακα",
"Edit board" : "Επεξεργασία πίνακα",
@@ -238,31 +257,37 @@
"Turn on due date reminders" : "Ενεργοποιήστε τις υπενθυμίσεις ημερομηνίας προθεσμίας",
"Turn off due date reminders" : "Απενεργοποιήστε τις υπενθυμίσεις ημερομηνίας προθεσμίας",
"Due date reminders" : "Υπενθυμίσεις ημερομηνίας προθεσμίας",
"All cards" : "Όλες οι κάρτες",
"Assigned cards" : "Ανατεθείς κάρτες",
"All cards" : "Όλες οι καρτέλες",
"Assigned cards" : "Ανατεθειμένες καρτέλες",
"No notifications" : "Δεν υπάρχουν ειδοποιήσεις",
"Delete board" : "Διαγραφή πίνακα",
"Board {0} deleted" : "Διαγράφηκε {0} πίνακας",
"Only assigned cards" : "Μόνο κάρτες που έχουν ανατεθεί",
"Board {0} deleted" : "Διαγράφηκε {0} πίνακας ",
"Only assigned cards" : "Μόνο καρτέλες που έχουν ανατεθεί",
"No reminder" : "Δεν υπάρχει υπενθύμιση",
"An error occurred" : "Παρουσιάστηκε σφάλμα",
"Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον πίνακα {title}; Θα διαγραφούν όλα τα δεδομένα.",
"Delete the board?" : "Διαγραφή πίνακα;",
"Delete the board?" : "Διαγραφή του πίνακα;",
"Loading filtered view" : "Φόρτωση εμφάνισης με βάση το φίλτρο",
"This week" : "Αυτή την εβδομάδα",
"No due" : "Χωρίς λήξη",
"No upcoming cards" : "Δεν υπάρχουν επερχόμενες κάρτες",
"upcoming cards" : "Επερχόμενες κάρτες",
"Search for {searchQuery} in all boards" : "Αναζήτηση για {searchQuery} σε όλους τους πίνακες",
"No results found" : "Δεν βρέθηκαν αποτελέσματα",
"No upcoming cards" : "Δεν υπάρχουν επερχόμενες καρτέλες",
"upcoming cards" : "επερχόμενες καρτέλες",
"Link to a board" : "Σύνδεσμος στον πίνακα",
"Link to a card" : "Σύνδεσμος σε καρτέλα",
"Create a card" : "Δημιουργία κάρτας",
"Create a card" : "Δημιουργία καρτέλας",
"Message from {author} in {conversationName}" : "Μήνυμα από {author} σε {conversationName}",
"Something went wrong" : "Κάτι πήγε στραβά",
"Failed to upload {name}" : "Αποτυχία μεταφόρτωσης {όνομα}",
"Failed to upload {name}" : "Αποτυχία μεταφόρτωσης {name}",
"Maximum file size of {size} exceeded" : "Υπέρβαση επιτρεπόμενου μεγέθους αρχείου {size}",
"Error creating the share" : "Σφάλμα κατά τη δημιουργία της κοινοποίησης",
"Share with a Deck card" : "Μοιραστείτε με μια κάρτα Deck",
"Share {file} with a Deck card" : "Μοιραστείτε {αρχείο} με μια κάρτα Deck",
"Share with a Deck card" : "Μοιραστείτε με μια καρτέλα Deck",
"Share {file} with a Deck card" : "Μοιραστείτε το {file} με μια καρτέλα Deck",
"Share" : "Μοιραστείτε",
"Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.\n\n\n- 📥 Add your tasks to cards and put them in order\n- 📄 Write down additional notes in markdown\n- 🔖 Assign labels for even better organization\n- 👥 Share with your team, friends or family\n- 📎 Attach files and embed them in your markdown description\n- 💬 Discuss with your team using comments\n- ⚡ Keep track of changes in the activity stream\n- 🚀 Get your project organized" : "Το Deck είναι ένα εργαλείο οργάνωσης τύπου kanban με στόχο τον προσωπικό προγραμματισμό και την ομαδική οργάνωση για ομάδες που έχουν ενσωματωθεί στο Nextcloud.\n\n\n- 📥 Προσθέστε τις εργασίες σας στις καρτέλες και βάλτε τες στη σειρά\n- 📄 Γράψτε τις πρόσθετες σημειώσεις\n- 🔖 Αντιστοιχίστε τις ετικέτες για ακόμη καλύτερη οργάνωση\n- 👥 Μοιραστείτε με την ομάδα, φίλους ή την οικογένειά σας\n- 📎 Συνδέστε αρχεία και ενσωματώστε τα στην περιγραφή\n- 💬 Συζητήστε με την ομάδα σας χρησιμοποιώντας σχόλια\n- ⚡ Παρακολουθήστε τις αλλαγές στη ροή δραστηριοτήτων\n- 🚀 Έχετε τα όλα οργανωμένα"
"Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.\n\n\n- 📥 Add your tasks to cards and put them in order\n- 📄 Write down additional notes in markdown\n- 🔖 Assign labels for even better organization\n- 👥 Share with your team, friends or family\n- 📎 Attach files and embed them in your markdown description\n- 💬 Discuss with your team using comments\n- ⚡ Keep track of changes in the activity stream\n- 🚀 Get your project organized" : "Το Deck είναι ένα εργαλείο οργάνωσης τύπου kanban με στόχο τον προσωπικό προγραμματισμό και την οργάνωση έργων για ομάδες που έχουν ενσωματωθεί στο Nextcloud.\n\n\n- 📥 Προσθέστε τις εργασίες σας στις καρτέλες και βάλτε τες στη σειρά\n- 📄 Γράψτε τις πρόσθετες σημειώσεις\n- 🔖 Αντιστοιχίστε τις ετικέτες για ακόμη καλύτερη οργάνωση\n- 👥 Μοιραστείτε με την ομάδα, φίλους ή την οικογένειά σας\n- 📎 Συνδέστε αρχεία και ενσωματώστε τα στην περιγραφή\n- 💬 Συζητήστε με την ομάδα σας χρησιμοποιώντας σχόλια\n- ⚡ Παρακολουθήστε τις αλλαγές στη ροή δραστηριοτήτων\n- 🚀 Έχετε τα όλα οργανωμένα",
"Creating the new card…" : "Δημιουργία νέας καρτέλας...",
"\"{card}\" was added to \"{board}\"" : "\"{card}\" προστέθηκε στο \"{board}\"",
"(circle)" : "(κύκλος)"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}
+1
View File
@@ -239,6 +239,7 @@ OC.L10N.register(
"Archive card" : "Archivar tarjeta",
"Delete card" : "Eliminar tarjeta",
"Move card to another board" : "Mover la tarjeta a otro tablero",
"List is empty" : "La lista está vacía",
"Card deleted" : "Tarjeta borrada",
"seconds ago" : "hace unos segundos",
"All boards" : "Todos los tableros",
+1
View File
@@ -237,6 +237,7 @@
"Archive card" : "Archivar tarjeta",
"Delete card" : "Eliminar tarjeta",
"Move card to another board" : "Mover la tarjeta a otro tablero",
"List is empty" : "La lista está vacía",
"Card deleted" : "Tarjeta borrada",
"seconds ago" : "hace unos segundos",
"All boards" : "Todos los tableros",
+24 -18
View File
@@ -31,7 +31,6 @@ use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Db\AttachmentMapper;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\Card;
@@ -50,12 +49,15 @@ use OCP\L10N\IFactory;
class ActivityManager {
public const DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED = 'DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED';
public const SUBJECT_PARAMS_MAX_LENGTH = 4000;
public const SHORTENED_DESCRIPTION_MAX_LENGTH = 2000;
private $manager;
private $userId;
private $permissionService;
private $boardMapper;
private $cardMapper;
private $attachmentMapper;
private $aclMapper;
private $stackMapper;
private $l10nFactory;
@@ -110,7 +112,6 @@ class ActivityManager {
BoardMapper $boardMapper,
CardMapper $cardMapper,
StackMapper $stackMapper,
AttachmentMapper $attachmentMapper,
AclMapper $aclMapper,
IFactory $l10nFactory,
$userId
@@ -120,7 +121,6 @@ class ActivityManager {
$this->boardMapper = $boardMapper;
$this->cardMapper = $cardMapper;
$this->stackMapper = $stackMapper;
$this->attachmentMapper = $attachmentMapper;
$this->aclMapper = $aclMapper;
$this->l10nFactory = $l10nFactory;
$this->userId = $userId;
@@ -249,19 +249,6 @@ class ActivityManager {
try {
$event = $this->createEvent($objectType, $entity, $subject, $additionalParams, $author);
if ($event !== null) {
$json = json_encode($event->getSubjectParameters());
if (mb_strlen($json) > 4000) {
$params = json_decode(json_encode($event->getSubjectParameters()), true);
$newContent = $params['after'];
unset($params['before'], $params['after'], $params['card']['description']);
$params['after'] = mb_substr($newContent, 0, 2000);
if (mb_strlen($newContent) > 2000) {
$params['after'] .= '...';
}
$event->setSubject($event->getSubject(), $params);
}
$this->sendToUsers($event);
}
} catch (\Exception $e) {
@@ -410,12 +397,31 @@ class ActivityManager {
$subjectParams['author'] = $author === null ? $this->userId : $author;
$subjectParams = array_merge($subjectParams, $additionalParams);
$json = json_encode($subjectParams);
if (mb_strlen($json) > self::SUBJECT_PARAMS_MAX_LENGTH) {
$params = json_decode(json_encode($subjectParams), true);
if ($subject === self::SUBJECT_CARD_UPDATE_DESCRIPTION && isset($params['after'])) {
$newContent = $params['after'];
unset($params['before'], $params['after'], $params['card']['description']);
$params['after'] = mb_substr($newContent, 0, self::SHORTENED_DESCRIPTION_MAX_LENGTH);
if (mb_strlen($newContent) > self::SHORTENED_DESCRIPTION_MAX_LENGTH) {
$params['after'] .= '...';
}
$subjectParams = $params;
} else {
throw new \Exception('Subject parameters too long');
}
}
$event = $this->manager->generateEvent();
$event->setApp('deck')
->setType($eventType)
->setAuthor($subjectParams['author'])
->setObject($objectType, (int)$object->getId(), $object->getTitle())
->setSubject($subject, array_merge($subjectParams, $additionalParams))
->setSubject($subject, $subjectParams)
->setTimestamp(time());
if ($message !== null) {
+92
View File
@@ -0,0 +1,92 @@
<?php
/**
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @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\Command;
use OCA\Deck\Service\Importer\BoardImportCommandService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class BoardImport extends Command {
/** @var BoardImportCommandService */
private $boardImportCommandService;
public function __construct(
BoardImportCommandService $boardImportCommandService
) {
$this->boardImportCommandService = $boardImportCommandService;
parent::__construct();
}
/**
* @return void
*/
protected function configure() {
$allowedSystems = $this->boardImportCommandService->getAllowedImportSystems();
$names = array_column($allowedSystems, 'name');
$this
->setName('deck:import')
->setDescription('Import data')
->addOption(
'system',
null,
InputOption::VALUE_REQUIRED,
'Source system for import. Available options: ' . implode(', ', $names) . '.',
null
)
->addOption(
'config',
null,
InputOption::VALUE_REQUIRED,
'Configuration json file.',
'config.json'
)
->addOption(
'data',
null,
InputOption::VALUE_OPTIONAL,
'Data file to import.',
'data.json'
)
;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$this
->boardImportCommandService
->setInput($input)
->setOutput($output)
->setCommand($this)
->import();
$output->writeln('Done!');
return 0;
}
}
+105
View File
@@ -0,0 +1,105 @@
<?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;
}
}
+15 -1
View File
@@ -24,9 +24,12 @@
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 {
@@ -150,9 +153,20 @@ class BoardController extends ApiController {
/**
* @NoAdminRequired
* @param $boardId
* @return \OCP\Deck\DB\Board
* @return 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);
}
}
@@ -0,0 +1,85 @@
<?php
/**
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @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\Controller;
use OCA\Deck\Service\Importer\BoardImportService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
class BoardImportApiController extends OCSController {
/** @var BoardImportService */
private $boardImportService;
/** @var string */
private $userId;
public function __construct(
string $appName,
IRequest $request,
BoardImportService $boardImportService,
string $userId
) {
parent::__construct($appName, $request);
$this->boardImportService = $boardImportService;
$this->userId = $userId;
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*/
public function import(string $system, array $config, array $data): DataResponse {
$this->boardImportService->setSystem($system);
$config = json_decode(json_encode($config));
$config->owner = $this->userId;
$this->boardImportService->setConfigInstance($config);
$this->boardImportService->setData(json_decode(json_encode($data)));
$this->boardImportService->import();
return new DataResponse($this->boardImportService->getBoard(), Http::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*/
public function getAllowedSystems(): DataResponse {
$allowedSystems = $this->boardImportService->getAllowedImportSystems();
return new DataResponse($allowedSystems, Http::STATUS_OK);
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*/
public function getConfigSchema(string $name): DataResponse {
$this->boardImportService->setSystem($name);
$this->boardImportService->validateSystem();
$jsonSchemaPath = json_decode(file_get_contents($this->boardImportService->getJsonSchemaPath()));
return new DataResponse($jsonSchemaPath, Http::STATUS_OK);
}
}
+1 -1
View File
@@ -24,7 +24,7 @@
namespace OCA\Deck\Cron;
use OC\BackgroundJob\Job;
use OCP\BackgroundJob\Job;
use OCA\Deck\Activity\ActivityManager;
use OCA\Deck\Db\CardMapper;
+6 -2
View File
@@ -24,13 +24,14 @@
namespace OCA\Deck\Cron;
use OC\BackgroundJob\Job;
use OCP\BackgroundJob\TimedJob;
use OCA\Deck\Db\AttachmentMapper;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\InvalidAttachmentType;
use OCA\Deck\Service\AttachmentService;
use OCP\BackgroundJob\IJob;
class DeleteCron extends Job {
class DeleteCron extends TimedJob {
/** @var BoardMapper */
private $boardMapper;
@@ -43,6 +44,9 @@ class DeleteCron extends Job {
$this->boardMapper = $boardMapper;
$this->attachmentService = $attachmentService;
$this->attachmentMapper = $attachmentMapper;
$this->setInterval(60 * 60 * 24);
$this->setTimeSensitivity(IJob::TIME_INSENSITIVE);
}
/**
+1 -1
View File
@@ -23,7 +23,7 @@
namespace OCA\Deck\Cron;
use OC\BackgroundJob\Job;
use OCP\BackgroundJob\Job;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Notification\NotificationHelper;
+13
View File
@@ -25,6 +25,7 @@ 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 {
@@ -57,4 +58,16 @@ 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();
}
}
+36
View File
@@ -29,6 +29,7 @@ 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;
@@ -146,4 +147,39 @@ 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();
}
}
+30
View File
@@ -475,4 +475,34 @@ 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;
}
}
}
+45 -2
View File
@@ -265,7 +265,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
public function findOverdue() {
$qb = $this->db->getQueryBuilder();
$qb->select('id','title','duedate','notified')
$qb->select('id', 'title', 'duedate', 'notified')
->from('deck_cards')
->where($qb->expr()->lt('duedate', $qb->createFunction('NOW()')))
->andWhere($qb->expr()->eq('notified', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
@@ -276,7 +276,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
public function findUnexposedDescriptionChances() {
$qb = $this->db->getQueryBuilder();
$qb->select('id','title','duedate','notified','description_prev','last_editor','description')
$qb->select('id', 'title', 'duedate', 'notified', 'description_prev', 'last_editor', 'description')
->from('deck_cards')
->where($qb->expr()->isNotNull('last_editor'))
->andWhere($qb->expr()->isNotNull('description_prev'));
@@ -586,4 +586,47 @@ 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();
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
/*
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
declare(strict_types=1);
namespace OCA\Deck\Event;
use OCA\Deck\Service\Importer\BoardImportService;
use OCP\EventDispatcher\Event;
abstract class ABoardImportGetAllowedEvent extends Event {
private $service;
public function __construct(BoardImportService $service) {
parent::__construct();
$this->service = $service;
}
public function getService(): BoardImportService {
return $this->service;
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
/*
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
declare(strict_types=1);
namespace OCA\Deck\Event;
class BoardImportGetAllowedEvent extends ABoardImportGetAllowedEvent {
}
+1 -1
View File
@@ -33,6 +33,6 @@ use OCP\Search\SearchResultEntry;
class CardSearchResultEntry extends SearchResultEntry {
public function __construct(Board $board, Stack $stack, Card $card, $urlGenerator) {
parent::__construct('', $card->getTitle(), $board->getTitle() . ' » ' . $stack->getTitle() , $urlGenerator->linkToRouteAbsolute('deck.page.index') . '#/board/' . $board->getId() . '/card/' . $card->getId(), 'icon-deck');
parent::__construct('', $card->getTitle(), $board->getTitle() . ' » ' . $stack->getTitle(), $urlGenerator->linkToRouteAbsolute('deck.page.index') . '#/board/' . $board->getId() . '/card/' . $card->getId(), 'icon-deck');
}
}
+62 -1
View File
@@ -24,12 +24,14 @@
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;
@@ -69,6 +71,8 @@ class BoardService {
private $activityManager;
private $eventDispatcher;
private $changeHelper;
private $cardMapper;
private $boardsCache = null;
private $urlGenerator;
@@ -83,6 +87,7 @@ class BoardService {
PermissionService $permissionService,
NotificationHelper $notificationHelper,
AssignmentMapper $assignedUsersMapper,
CardMapper $cardMapper,
IUserManager $userManager,
IGroupManager $groupManager,
ActivityManager $activityManager,
@@ -107,6 +112,7 @@ class BoardService {
$this->changeHelper = $changeHelper;
$this->userId = $userId;
$this->urlGenerator = $urlGenerator;
$this->cardMapper = $cardMapper;
}
/**
@@ -518,11 +524,14 @@ class BoardService {
$acl->setPermissionManage($manage);
$newAcl = $this->aclMapper->insert($acl);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE, [], $this->userId);
$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);
@@ -673,6 +682,43 @@ 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);
@@ -704,4 +750,19 @@ class BoardService {
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]);
}
}
+26 -2
View File
@@ -66,7 +66,8 @@ class ConfigService {
}
$data = [
'calendar' => $this->isCalendarEnabled()
'calendar' => $this->isCalendarEnabled(),
'cardDetailsInModal' => $this->isCardDetailsInModal(),
];
if ($this->groupManager->isAdmin($this->getUserId())) {
$data['groupLimit'] = $this->get('groupLimit');
@@ -88,6 +89,11 @@ class ConfigService {
return false;
}
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', true);
case 'cardDetailsInModal':
if ($this->getUserId() === null) {
return false;
}
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', true);
}
}
@@ -96,7 +102,8 @@ class ConfigService {
return false;
}
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', true);
$appConfigState = $this->config->getAppValue(Application::APP_ID, 'calendar', 'yes') === 'yes';
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', $appConfigState);
if ($boardId === null) {
return $defaultState;
}
@@ -104,6 +111,19 @@ class ConfigService {
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':calendar', $defaultState);
}
public function isCardDetailsInModal(int $boardId = null): bool {
if ($this->getUserId() === null) {
return false;
}
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', true);
if ($boardId === null) {
return $defaultState;
}
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':cardDetailsInModal', $defaultState);
}
public function set($key, $value) {
if ($this->getUserId() === null) {
throw new NoPermissionException('Must be logged in to set user config');
@@ -122,6 +142,10 @@ class ConfigService {
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'calendar', (string)$value);
$result = $value;
break;
case 'cardDetailsInModal':
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', (string)$value);
$result = $value;
break;
case 'board':
[$boardId, $boardConfigKey] = explode(':', $key);
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)) {
+1 -1
View File
@@ -86,7 +86,7 @@ class FileService implements IAttachmentService {
* @return ISimpleFolder
* @throws NotPermittedException
*/
private function getFolder(Attachment $attachment) {
public function getFolder(Attachment $attachment) {
$folderName = 'file-card-' . (int)$attachment->getCardId();
try {
$folder = $this->appData->getFolder($folderName);
@@ -0,0 +1,136 @@
<?php
/**
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @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\Service\Importer;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\Label;
use OCA\Deck\Db\Stack;
use OCP\AppFramework\Db\Entity;
use OCP\Comments\IComment;
abstract class ABoardImportService {
/** @var string */
public static $name = '';
/** @var BoardImportService */
private $boardImportService;
/** @var bool */
protected $needValidateData = true;
/** @var Stack[] */
protected $stacks = [];
/** @var Label[] */
protected $labels = [];
/** @var Card[] */
protected $cards = [];
/** @var Acl[] */
protected $acls = [];
/** @var IComment[][] */
protected $comments = [];
/** @var Assignment[] */
protected $assignments = [];
/** @var string[][] */
protected $labelCardAssignments = [];
/**
* Configure import service
*
* @return void
*/
abstract public function bootstrap(): void;
abstract public function getBoard(): ?Board;
/**
* @return Acl[]
*/
abstract public function getAclList(): array;
/**
* @return Stack[]
*/
abstract public function getStacks(): array;
/**
* @return Card[]
*/
abstract public function getCards(): array;
abstract public function getCardAssignments(): array;
abstract public function getCardLabelAssignment(): array;
/**
* @return IComment[][]|array
*/
abstract public function getComments(): array;
/** @return Label[] */
abstract public function getLabels(): array;
abstract public function validateUsers(): void;
abstract public function getJsonSchemaPath(): string;
public function updateStack(string $id, Stack $stack): void {
$this->stacks[$id] = $stack;
}
public function updateCard(string $id, Card $card): void {
$this->cards[$id] = $card;
}
public function updateLabel(string $code, Label $label): void {
$this->labels[$code] = $label;
}
public function updateAcl(string $code, Acl $acl): void {
$this->acls[$code] = $acl;
}
public function updateComment(string $cardId, string $commentId, IComment $comment): void {
$this->comments[$cardId][$commentId] = $comment;
}
public function updateCardAssignment(string $cardId, string $assignmentId, Entity $assignment): void {
$this->assignments[$cardId][$assignmentId] = $assignment;
}
public function updateCardLabelsAssignment(string $cardId, string $assignmentId, string $assignment): void {
$this->labelCardAssignments[$cardId][$assignmentId] = $assignment;
}
public function setImportService(BoardImportService $service): void {
$this->boardImportService = $service;
}
public function getImportService(): BoardImportService {
return $this->boardImportService;
}
public function needValidateData(): bool {
return $this->needValidateData;
}
}
@@ -0,0 +1,199 @@
<?php
/**
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @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\Service\Importer;
use OCA\Deck\Exceptions\ConflictException;
use OCA\Deck\NotFoundException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
class BoardImportCommandService extends BoardImportService {
/**
* @var Command
* @psalm-suppress PropertyNotSetInConstructor
*/
private $command;
/**
* @var InputInterface
* @psalm-suppress PropertyNotSetInConstructor
*/
private $input;
/**
* @var OutputInterface
* @psalm-suppress PropertyNotSetInConstructor
*/
private $output;
public function setCommand(Command $command): self {
$this->command = $command;
return $this;
}
public function getCommand(): Command {
return $this->command;
}
public function setInput(InputInterface $input): self {
$this->input = $input;
return $this;
}
public function getInput(): InputInterface {
return $this->input;
}
public function setOutput(OutputInterface $output): self {
$this->output = $output;
return $this;
}
public function getOutput(): OutputInterface {
return $this->output;
}
protected function validateConfig(): void {
try {
$config = $this->getInput()->getOption('config');
if (is_string($config)) {
if (!is_file($config)) {
throw new NotFoundException('It\'s not a valid config file.');
}
$config = json_decode(file_get_contents($config));
if (!$config instanceof \stdClass) {
throw new NotFoundException('Failed to parse JSON.');
}
$this->setConfigInstance($config);
}
parent::validateConfig();
return;
} catch (NotFoundException $e) {
$this->getOutput()->writeln('<error>' . $e->getMessage() . '</error>');
$helper = $this->getCommand()->getHelper('question');
$question = new Question(
"<info>You can get more info on https://deck.readthedocs.io/en/latest/User_documentation_en/#6-import-boards</info>\n" .
'Please inform a valid config json file: ',
'config.json'
);
$question->setValidator(function (string $answer) {
if (!is_file($answer)) {
throw new \RuntimeException(
'config file not found'
);
}
return $answer;
});
$configFile = $helper->ask($this->getInput(), $this->getOutput(), $question);
$this->getInput()->setOption('config', $configFile);
} catch (ConflictException $e) {
$this->getOutput()->writeln('<error>Invalid config file</error>');
$this->getOutput()->writeln(array_map(function (array $v): string {
return $v['message'];
}, $e->getData()));
$this->getOutput()->writeln('Valid schema:');
$this->getOutput()->writeln(print_r(file_get_contents($this->getJsonSchemaPath()), true));
$this->getInput()->setOption('config', '');
}
$this->validateConfig();
}
public function validateSystem(): void {
try {
parent::validateSystem();
return;
} catch (\Throwable $th) {
}
$helper = $this->getCommand()->getHelper('question');
$allowedSystems = $this->getAllowedImportSystems();
$names = array_column($allowedSystems, 'name');
$question = new ChoiceQuestion(
'Please inform a source system',
$names,
0
);
$question->setErrorMessage('System %s is invalid.');
$selectedName = $helper->ask($this->getInput(), $this->getOutput(), $question);
$className = $allowedSystems[array_flip($names)[$selectedName]]['internalName'];
$this->setSystem($className);
return;
}
protected function validateData(): void {
if (!$this->getImportSystem()->needValidateData()) {
return;
}
$data = $this->getInput()->getOption('data');
if (is_string($data)) {
$data = json_decode(file_get_contents($data));
if ($data instanceof \stdClass) {
$this->setData($data);
return;
}
}
$helper = $this->getCommand()->getHelper('question');
$question = new Question(
'Please provide a valid data json file: ',
'data.json'
);
$question->setValidator(function (string $answer) {
if (!is_file($answer)) {
throw new \RuntimeException(
'Data file not found'
);
}
return $answer;
});
$data = $helper->ask($this->getInput(), $this->getOutput(), $question);
$this->getInput()->setOption('data', $data);
$this->validateData();
}
public function bootstrap(): void {
$this->setSystem($this->getInput()->getOption('system'));
parent::bootstrap();
}
public function import(): void {
$this->getOutput()->writeln('Starting import...');
$this->bootstrap();
$this->getOutput()->writeln('Importing board...');
$this->importBoard();
$this->getOutput()->writeln('Assign users to board...');
$this->importAcl();
$this->getOutput()->writeln('Importing labels...');
$this->importLabels();
$this->getOutput()->writeln('Importing stacks...');
$this->importStacks();
$this->getOutput()->writeln('Importing cards...');
$this->importCards();
$this->getOutput()->writeln('Assign cards to labels...');
$this->assignCardsToLabels();
$this->getOutput()->writeln('Importing comments...');
$this->importComments();
$this->getOutput()->writeln('Importing participants...');
$this->importCardAssignments();
}
}
+449
View File
@@ -0,0 +1,449 @@
<?php
/**
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @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\Service\Importer;
use JsonSchema\Constraints\Constraint;
use JsonSchema\Validator;
use OCA\Deck\AppInfo\Application;
use OCA\Deck\BadRequestException;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Db\AttachmentMapper;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Db\StackMapper;
use OCA\Deck\Event\BoardImportGetAllowedEvent;
use OCA\Deck\Exceptions\ConflictException;
use OCA\Deck\NotFoundException;
use OCA\Deck\Service\FileService;
use OCA\Deck\Service\Importer\Systems\TrelloApiService;
use OCA\Deck\Service\Importer\Systems\TrelloJsonService;
use OCP\Comments\IComment;
use OCP\Comments\ICommentsManager;
use OCP\Comments\NotFoundException as CommentNotFoundException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUserManager;
class BoardImportService {
/** @var IUserManager */
private $userManager;
/** @var BoardMapper */
private $boardMapper;
/** @var AclMapper */
private $aclMapper;
/** @var LabelMapper */
private $labelMapper;
/** @var StackMapper */
private $stackMapper;
/** @var CardMapper */
private $cardMapper;
/** @var AssignmentMapper */
private $assignmentMapper;
/** @var AttachmentMapper */
private $attachmentMapper;
/** @var ICommentsManager */
private $commentsManager;
/** @var IEventDispatcher */
private $eventDispatcher;
/** @var string */
private $system = '';
/** @var null|ABoardImportService */
private $systemInstance;
/** @var array */
private $allowedSystems = [];
/**
* Data object created from config JSON
*
* @var \stdClass
* @psalm-suppress PropertyNotSetInConstructor
*/
public $config;
/**
* Data object created from JSON of origin system
*
* @var \stdClass
* @psalm-suppress PropertyNotSetInConstructor
*/
private $data;
/**
* @var Board
*/
private $board;
public function __construct(
IUserManager $userManager,
BoardMapper $boardMapper,
AclMapper $aclMapper,
LabelMapper $labelMapper,
StackMapper $stackMapper,
AssignmentMapper $assignmentMapper,
AttachmentMapper $attachmentMapper,
CardMapper $cardMapper,
ICommentsManager $commentsManager,
IEventDispatcher $eventDispatcher
) {
$this->userManager = $userManager;
$this->boardMapper = $boardMapper;
$this->aclMapper = $aclMapper;
$this->labelMapper = $labelMapper;
$this->stackMapper = $stackMapper;
$this->cardMapper = $cardMapper;
$this->assignmentMapper = $assignmentMapper;
$this->attachmentMapper = $attachmentMapper;
$this->commentsManager = $commentsManager;
$this->eventDispatcher = $eventDispatcher;
$this->board = new Board();
$this->disableCommentsEvents();
}
private function disableCommentsEvents(): void {
if (defined('PHPUNIT_RUN')) {
return;
}
$propertyEventHandlers = new \ReflectionProperty($this->commentsManager, 'eventHandlers');
$propertyEventHandlers->setAccessible(true);
$propertyEventHandlers->setValue($this->commentsManager, []);
$propertyEventHandlerClosures = new \ReflectionProperty($this->commentsManager, 'eventHandlerClosures');
$propertyEventHandlerClosures->setAccessible(true);
$propertyEventHandlerClosures->setValue($this->commentsManager, []);
}
public function import(): void {
$this->bootstrap();
try {
$this->importBoard();
$this->importAcl();
$this->importLabels();
$this->importStacks();
$this->importCards();
$this->assignCardsToLabels();
$this->importComments();
$this->importCardAssignments();
} catch (\Throwable $th) {
throw new BadRequestException($th->getMessage());
}
}
public function validateSystem(): void {
$allowedSystems = $this->getAllowedImportSystems();
$allowedSystems = array_column($allowedSystems, 'internalName');
if (!in_array($this->getSystem(), $allowedSystems)) {
throw new NotFoundException('Invalid system');
}
}
/**
* @param mixed $system
* @return self
*/
public function setSystem($system): self {
$this->system = $system;
return $this;
}
public function getSystem(): string {
return $this->system;
}
public function addAllowedImportSystem($system): self {
$this->allowedSystems[] = $system;
return $this;
}
public function getAllowedImportSystems(): array {
if (!$this->allowedSystems) {
$this->addAllowedImportSystem([
'name' => TrelloApiService::$name,
'class' => TrelloApiService::class,
'internalName' => 'TrelloApi'
]);
$this->addAllowedImportSystem([
'name' => TrelloJsonService::$name,
'class' => TrelloJsonService::class,
'internalName' => 'TrelloJson'
]);
}
$this->eventDispatcher->dispatchTyped(new BoardImportGetAllowedEvent($this));
return $this->allowedSystems;
}
public function getImportSystem(): ABoardImportService {
if (!$this->getSystem()) {
throw new NotFoundException('System to import not found');
}
if (!is_object($this->systemInstance)) {
$systemClass = 'OCA\\Deck\\Service\\Importer\\Systems\\' . ucfirst($this->getSystem()) . 'Service';
$this->systemInstance = \OC::$server->get($systemClass);
$this->systemInstance->setImportService($this);
}
return $this->systemInstance;
}
public function setImportSystem(ABoardImportService $instance): void {
$this->systemInstance = $instance;
}
public function importBoard(): void {
$board = $this->getImportSystem()->getBoard();
if ($board) {
$this->boardMapper->insert($board);
$this->board = $board;
}
}
public function getBoard(bool $reset = false): Board {
if ($reset) {
$this->board = new Board();
}
return $this->board;
}
public function importAcl(): void {
$aclList = $this->getImportSystem()->getAclList();
foreach ($aclList as $code => $acl) {
$this->aclMapper->insert($acl);
$this->getImportSystem()->updateAcl($code, $acl);
}
$this->getBoard()->setAcl($aclList);
}
public function importLabels(): void {
$labels = $this->getImportSystem()->getLabels();
foreach ($labels as $code => $label) {
$this->labelMapper->insert($label);
$this->getImportSystem()->updateLabel($code, $label);
}
$this->getBoard()->setLabels($labels);
}
public function importStacks(): void {
$stacks = $this->getImportSystem()->getStacks();
foreach ($stacks as $code => $stack) {
$this->stackMapper->insert($stack);
$this->getImportSystem()->updateStack($code, $stack);
}
$this->getBoard()->setStacks(array_values($stacks));
}
public function importCards(): void {
$cards = $this->getImportSystem()->getCards();
foreach ($cards as $code => $card) {
$createdAt = $card->getCreatedAt();
$lastModified = $card->getLastModified();
$this->cardMapper->insert($card);
$updateDate = false;
if ($createdAt && $createdAt !== $card->getCreatedAt()) {
$card->setCreatedAt($createdAt);
$updateDate = true;
}
if ($lastModified && $lastModified !== $card->getLastModified()) {
$card->setLastModified($lastModified);
$updateDate = true;
}
if ($updateDate) {
$this->cardMapper->update($card, false);
}
$this->getImportSystem()->updateCard($code, $card);
}
}
/**
* @param mixed $cardId
* @param mixed $labelId
* @return self
*/
public function assignCardToLabel($cardId, $labelId): self {
$this->cardMapper->assignLabel(
$cardId,
$labelId
);
return $this;
}
public function assignCardsToLabels(): void {
$data = $this->getImportSystem()->getCardLabelAssignment();
foreach ($data as $cardId => $assignemnt) {
foreach ($assignemnt as $assignmentId => $labelId) {
$this->assignCardToLabel(
$cardId,
$labelId
);
$this->getImportSystem()->updateCardLabelsAssignment($cardId, $assignmentId, $labelId);
}
}
}
public function importComments(): void {
$allComments = $this->getImportSystem()->getComments();
foreach ($allComments as $cardId => $comments) {
foreach ($comments as $commentId => $comment) {
$this->insertComment($cardId, $comment);
$this->getImportSystem()->updateComment($cardId, $commentId, $comment);
}
}
}
private function insertComment(string $cardId, IComment $comment): void {
$comment->setObject('deckCard', $cardId);
$comment->setVerb('comment');
// Check if parent is a comment on the same card
if ($comment->getParentId() !== '0') {
try {
$parent = $this->commentsManager->get($comment->getParentId());
if ($parent->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $parent->getObjectId() !== $cardId) {
throw new CommentNotFoundException();
}
} catch (CommentNotFoundException $e) {
throw new BadRequestException('Invalid parent id: The parent comment was not found or belongs to a different card');
}
}
try {
$this->commentsManager->save($comment);
} catch (\InvalidArgumentException $e) {
throw new BadRequestException('Invalid input values');
} catch (CommentNotFoundException $e) {
throw new NotFoundException('Could not create comment.');
}
}
public function importCardAssignments(): void {
$allAssignments = $this->getImportSystem()->getCardAssignments();
foreach ($allAssignments as $cardId => $assignments) {
foreach ($assignments as $assignmentId => $assignment) {
$this->assignmentMapper->insert($assignment);
$this->getImportSystem()->updateCardAssignment($cardId, $assignmentId, $assignment);
}
}
}
public function insertAttachment(Attachment $attachment, string $content): Attachment {
$service = \OC::$server->get(FileService::class);
$folder = $service->getFolder($attachment);
if ($folder->fileExists($attachment->getData())) {
$attachment = $this->attachmentMapper->findByData($attachment->getCardId(), $attachment->getData());
throw new ConflictException('File already exists.', $attachment);
}
$target = $folder->newFile($attachment->getData());
$target->putContent($content);
$attachment = $this->attachmentMapper->insert($attachment);
$service->extendData($attachment);
return $attachment;
}
public function setData(\stdClass $data): void {
$this->data = $data;
}
public function getData(): \stdClass {
return $this->data;
}
/**
* Define a config
*
* @param string $configName
* @param mixed $value
* @return void
*/
public function setConfig(string $configName, $value): void {
if (empty((array) $this->config)) {
$this->setConfigInstance(new \stdClass);
}
$this->config->$configName = $value;
}
/**
* Get a config
*
* @param string $configName config name
* @return mixed
*/
public function getConfig(string $configName) {
if (!property_exists($this->config, $configName)) {
return;
}
return $this->config->$configName;
}
/**
* @param \stdClass $config
* @return self
*/
public function setConfigInstance($config): self {
$this->config = $config;
return $this;
}
public function getConfigInstance(): \stdClass {
return $this->config;
}
protected function validateConfig(): void {
$config = $this->getConfigInstance();
$schemaPath = $this->getJsonSchemaPath();
$validator = new Validator();
$newConfig = clone $config;
$validator->validate(
$newConfig,
(object)['$ref' => 'file://' . realpath($schemaPath)],
Constraint::CHECK_MODE_APPLY_DEFAULTS
);
if (!$validator->isValid()) {
throw new ConflictException('Invalid config file', $validator->getErrors());
}
$this->setConfigInstance($newConfig);
$this->validateOwner();
}
public function getJsonSchemaPath(): string {
return $this->getImportSystem()->getJsonSchemaPath();
}
public function validateOwner(): void {
$owner = $this->userManager->get($this->getConfig('owner'));
if (!$owner) {
throw new \LogicException('Owner "' . $this->getConfig('owner')->getUID() . '" not found on Nextcloud. Check setting json.');
}
$this->setConfig('owner', $owner);
}
protected function validateData(): void {
}
public function bootstrap(): void {
$this->validateSystem();
$this->validateConfig();
$this->validateData();
$this->getImportSystem()->bootstrap();
}
}
@@ -0,0 +1,214 @@
<?php
/**
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @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\Service\Importer\Systems;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUserManager;
use Psr\Log\LoggerInterface;
class TrelloApiService extends TrelloJsonService {
/** @var string */
public static $name = 'Trello API';
protected $needValidateData = false;
/** @var IClient */
private $httpClient;
/** @var LoggerInterface */
protected $logger;
/** @var string */
private $baseApiUrl = 'https://api.trello.com/1';
/** @var ?\stdClass[] */
private $boards;
public function __construct(
IUserManager $userManager,
IURLGenerator $urlGenerator,
IL10N $l10n,
LoggerInterface $logger,
IClientService $httpClientService
) {
parent::__construct($userManager, $urlGenerator, $l10n);
$this->logger = $logger;
$this->httpClient = $httpClientService->newClient();
}
public function bootstrap(): void {
$this->populateBoard();
$this->populateMembers();
$this->populateLabels();
$this->populateLists();
$this->populateCheckLists();
$this->populateCards();
$this->populateActions();
parent::bootstrap();
}
public function getJsonSchemaPath(): string {
return implode(DIRECTORY_SEPARATOR, [
__DIR__,
'..',
'fixtures',
'config-trelloApi-schema.json',
]);
}
private function populateActions(): void {
$data = $this->getImportService()->getData();
$data->actions = $this->doRequest(
'/boards/' . $data->id . '/actions',
[
'filter' => 'commentCard,createCard',
'fields=memberCreator,type,data,date',
'memberCreator_fields' => 'username',
'limit' => 1000
]
);
}
private function populateCards(): void {
$data = $this->getImportService()->getData();
$data->cards = $this->doRequest(
'/boards/' . $data->id . '/cards',
[
'fields' => 'id,idMembers,dateLastActivity,closed,idChecklists,name,idList,pos,desc,due,labels',
'attachments' => true,
'attachment_fields' => 'name,url,date',
'limit' => 1000
]
);
}
private function populateCheckLists(): void {
$data = $this->getImportService()->getData();
$data->checklists = $this->doRequest(
'/boards/' . $data->id . '/checkLists',
[
'fields' => 'id,idCard,name',
'checkItem_fields' => 'id,state,name',
'limit' => 1000
]
);
}
private function populateLists(): void {
$data = $this->getImportService()->getData();
$data->lists = $this->doRequest(
'/boards/' . $data->id . '/lists',
[
'fields' => 'id,name,closed',
'limit' => 1000
]
);
}
private function populateLabels(): void {
$data = $this->getImportService()->getData();
$data->labels = $this->doRequest(
'/boards/' . $data->id . '/labels',
[
'fields' => 'id,color,name',
'limit' => 1000
]
);
}
private function populateMembers(): void {
$data = $this->getImportService()->getData();
$data->members = $this->doRequest(
'/boards/' . $data->id . '/members',
[
'fields' => 'username',
'limit' => 1000
]
);
}
private function populateBoard(): void {
$toImport = $this->getImportService()->getConfig('board');
$board = $this->doRequest(
'/boards/' . $toImport,
['fields' => 'id,name']
);
if ($board instanceof \stdClass) {
$this->getImportService()->setData($board);
return;
}
throw new \Exception('Invalid board id to import');
}
/**
* @return array|\stdClass
*/
private function doRequest(string $path = '', array $queryString = []) {
$target = $this->baseApiUrl . $path;
try {
$result = $this->httpClient
->get($target, $this->getQueryString($queryString))
->getBody();
if (is_string($result)) {
$data = json_decode($result);
if (is_array($data)) {
$data = array_merge(
$data,
$this->paginate($path, $queryString, $data)
);
}
return $data;
}
throw new \Exception('Invalid return of api');
} catch (\Throwable $e) {
$this->logger->critical(
$e->getMessage(),
['app' => 'deck']
);
throw new \Exception($e->getMessage());
}
}
private function paginate(string $path = '', array $queryString = [], array $data = []): array {
if (empty($queryString['limit'])) {
return [];
}
if (count($data) < $queryString['limit']) {
return [];
}
$queryString['before'] = end($data)->id;
$return = $this->doRequest($path, $queryString);
if (is_array($return)) {
return $return;
}
throw new \Exception('Invalid return of api');
}
private function getQueryString(array $params = []): array {
$apiSettings = $this->getImportService()->getConfig('api');
$params['key'] = $apiSettings->key;
$params['token'] = $apiSettings->token;
return [
'query' => $params
];
}
}
@@ -0,0 +1,400 @@
<?php
/**
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @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\Service\Importer\Systems;
use OC\Comments\Comment;
use OCA\Deck\BadRequestException;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\Label;
use OCA\Deck\Db\Stack;
use OCA\Deck\Service\Importer\ABoardImportService;
use OCP\Comments\IComment;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
class TrelloJsonService extends ABoardImportService {
/** @var string */
public static $name = 'Trello JSON';
/** @var IUserManager */
private $userManager;
/** @var IURLGenerator */
private $urlGenerator;
/** @var IL10N */
private $l10n;
/** @var IUser[] */
private $members = [];
public function __construct(
IUserManager $userManager,
IURLGenerator $urlGenerator,
IL10N $l10n
) {
$this->userManager = $userManager;
$this->urlGenerator = $urlGenerator;
$this->l10n = $l10n;
}
public function bootstrap(): void {
$this->validateUsers();
}
public function getJsonSchemaPath(): string {
return implode(DIRECTORY_SEPARATOR, [
__DIR__,
'..',
'fixtures',
'config-trelloJson-schema.json',
]);
}
public function validateUsers(): void {
if (empty($this->getImportService()->getConfig('uidRelation'))) {
return;
}
foreach ($this->getImportService()->getConfig('uidRelation') as $trelloUid => $nextcloudUid) {
$user = array_filter($this->getImportService()->getData()->members, function (\stdClass $u) use ($trelloUid) {
return $u->username === $trelloUid;
});
if (!$user) {
throw new \LogicException('Trello user ' . $trelloUid . ' not found in property "members" of json data');
}
if (!is_string($nextcloudUid) && !is_numeric($nextcloudUid)) {
throw new \LogicException('User on setting uidRelation is invalid');
}
$nextcloudUid = (string) $nextcloudUid;
$this->getImportService()->getConfig('uidRelation')->$trelloUid = $this->userManager->get($nextcloudUid);
if (!$this->getImportService()->getConfig('uidRelation')->$trelloUid) {
throw new \LogicException('User on setting uidRelation not found: ' . $nextcloudUid);
}
$user = current($user);
$this->members[$user->id] = $this->getImportService()->getConfig('uidRelation')->$trelloUid;
}
}
public function getCardAssignments(): array {
$assignments = [];
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
foreach ($trelloCard->idMembers as $idMember) {
if (empty($this->members[$idMember])) {
continue;
}
$assignment = new Assignment();
$assignment->setCardId($this->cards[$trelloCard->id]->getId());
$assignment->setParticipant($this->members[$idMember]->getUID());
$assignment->setType(Assignment::TYPE_USER);
$assignments[$trelloCard->id][] = $assignment;
}
}
return $assignments;
}
public function getComments(): array {
$comments = [];
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
$values = array_filter(
$this->getImportService()->getData()->actions,
function (\stdClass $a) use ($trelloCard) {
return $a->type === 'commentCard' && $a->data->card->id === $trelloCard->id;
}
);
$keys = array_map(function (\stdClass $c): string {
return $c->id;
}, $values);
$trelloComments = array_combine($keys, $values);
$trelloComments = $this->sortComments($trelloComments);
foreach ($trelloComments as $commentId => $trelloComment) {
$cardId = $this->cards[$trelloCard->id]->getId();
$comment = new Comment();
if (!empty($this->getImportService()->getConfig('uidRelation')->{$trelloComment->memberCreator->username})) {
$actor = $this->getImportService()->getConfig('uidRelation')->{$trelloComment->memberCreator->username}->getUID();
} else {
$actor = $this->getImportService()->getConfig('owner')->getUID();
}
$message = $this->replaceUsernames($trelloComment->data->text);
if (mb_strlen($message, 'UTF-8') > IComment::MAX_MESSAGE_LENGTH) {
$attachment = new Attachment();
$attachment->setCardId($cardId);
$attachment->setType('deck_file');
$attachment->setCreatedBy($actor);
$attachment->setLastModified(time());
$attachment->setCreatedAt(time());
$attachment->setData('comment_' . $commentId . '.md');
$attachment = $this->getImportService()->insertAttachment($attachment, $message);
$urlToDownloadAttachment = $this->urlGenerator->linkToRouteAbsolute(
'deck.attachment.display',
[
'cardId' => $cardId,
'attachmentId' => $attachment->getId()
]
);
$message = $this->l10n->t(
"This comment has more than %s characters.\n" .
"Added as an attachment to the card with name %s.\n" .
"Accessible on URL: %s.",
[
IComment::MAX_MESSAGE_LENGTH,
'comment_' . $commentId . '.md',
$urlToDownloadAttachment
]
);
}
$comment
->setActor('users', $actor)
->setMessage($message)
->setCreationDateTime(
\DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloComment->date)
);
$comments[$cardId][$commentId] = $comment;
}
}
return $comments;
}
private function sortComments(array $comments): array {
$comparison = function (\stdClass $a, \stdClass $b): int {
if ($a->date == $b->date) {
return 0;
}
return ($a->date < $b->date) ? -1 : 1;
};
usort($comments, $comparison);
return $comments;
}
public function getCardLabelAssignment(): array {
$cardsLabels = [];
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
foreach ($trelloCard->labels as $label) {
$cardId = $this->cards[$trelloCard->id]->getId();
$labelId = $this->labels[$label->id]->getId();
$cardsLabels[$cardId][] = $labelId;
}
}
return $cardsLabels;
}
public function getBoard(): Board {
$board = $this->getImportService()->getBoard();
if (empty($this->getImportService()->getData()->name)) {
throw new BadRequestException('Invalid name of board');
}
$board->setTitle($this->getImportService()->getData()->name);
$board->setOwner($this->getImportService()->getConfig('owner')->getUID());
$board->setColor($this->getImportService()->getConfig('color'));
return $board;
}
/**
* @return Label[]
*/
public function getLabels(): array {
foreach ($this->getImportService()->getData()->labels as $trelloLabel) {
$label = new Label();
if (empty($trelloLabel->name)) {
$label->setTitle('Unnamed ' . $trelloLabel->color . ' label');
} else {
$label->setTitle($trelloLabel->name);
}
$label->setColor($this->translateColor($trelloLabel->color));
$label->setBoardId($this->getImportService()->getBoard()->getId());
$this->labels[$trelloLabel->id] = $label;
}
return $this->labels;
}
/**
* @return Stack[]
*/
public function getStacks(): array {
$return = [];
foreach ($this->getImportService()->getData()->lists as $order => $list) {
$stack = new Stack();
if ($list->closed) {
$stack->setDeletedAt(time());
}
$stack->setTitle($list->name);
$stack->setBoardId($this->getImportService()->getBoard()->getId());
$stack->setOrder($order + 1);
$return[$list->id] = $stack;
}
return $return;
}
/**
* @return Card[]
*/
public function getCards(): array {
$checklists = [];
foreach ($this->getImportService()->getData()->checklists as $checklist) {
$checklists[$checklist->idCard][$checklist->id] = $this->formulateChecklistText($checklist);
}
$this->getImportService()->getData()->checklists = $checklists;
$cards = [];
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
$card = new Card();
$lastModified = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->dateLastActivity);
$card->setLastModified($lastModified->format('Y-m-d H:i:s'));
if ($trelloCard->closed) {
$card->setArchived(true);
}
if ((count($trelloCard->idChecklists) !== 0)) {
foreach ($this->getImportService()->getData()->checklists[$trelloCard->id] as $checklist) {
$trelloCard->desc .= "\n" . $checklist;
}
}
$this->appendAttachmentsToDescription($trelloCard);
$card->setTitle($trelloCard->name);
$card->setStackId($this->stacks[$trelloCard->idList]->getId());
$cardsOnStack = $this->stacks[$trelloCard->idList]->getCards();
$cardsOnStack[] = $card;
$this->stacks[$trelloCard->idList]->setCards($cardsOnStack);
$card->setType('plain');
$card->setOrder($trelloCard->pos);
$card->setOwner($this->getImportService()->getConfig('owner')->getUID());
$lastModified = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->dateLastActivity);
$card->setLastModified($lastModified->format('U'));
$createCardDate = array_filter(
$this->getImportService()->getData()->actions,
function (\stdClass $a) use ($trelloCard) {
return $a->type === 'createCard' && $a->data->card->id === $trelloCard->id;
}
);
$createCardDate = current($createCardDate);
$createCardDate = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $createCardDate->date);
if ($createCardDate) {
$card->setCreatedAt($createCardDate->format('U'));
} else {
$card->setCreatedAt($lastModified->format('U'));
}
$card->setDescription($trelloCard->desc);
if ($trelloCard->due) {
$duedate = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->due)
->format('Y-m-d H:i:s');
$card->setDuedate($duedate);
}
$cards[$trelloCard->id] = $card;
}
return $cards;
}
/**
* @return Acl[]
*/
public function getAclList(): array {
$return = [];
foreach ($this->members as $member) {
if ($member->getUID() === $this->getImportService()->getConfig('owner')->getUID()) {
continue;
}
$acl = new Acl();
$acl->setBoardId($this->getImportService()->getBoard()->getId());
$acl->setType(Acl::PERMISSION_TYPE_USER);
$acl->setParticipant($member->getUID());
$acl->setPermissionEdit(false);
$acl->setPermissionShare(false);
$acl->setPermissionManage(false);
$return[] = $acl;
}
return $return;
}
private function translateColor(string $color): string {
switch ($color) {
case 'red':
return 'ff0000';
case 'yellow':
return 'ffff00';
case 'orange':
return 'ff6600';
case 'green':
return '00ff00';
case 'purple':
return '9900ff';
case 'blue':
return '0000ff';
case 'sky':
return '00ccff';
case 'lime':
return '00ff99';
case 'pink':
return 'ff66cc';
case 'black':
return '000000';
default:
return 'ffffff';
}
}
private function replaceUsernames(string $text): string {
foreach ($this->getImportService()->getConfig('uidRelation') as $trello => $nextcloud) {
$text = str_replace($trello, $nextcloud->getUID(), $text);
}
return $text;
}
private function checklistItem(\stdClass $item): string {
if (($item->state == 'incomplete')) {
$string_start = '- [ ]';
} else {
$string_start = '- [x]';
}
$check_item_string = $string_start . ' ' . $item->name . "\n";
return $check_item_string;
}
private function formulateChecklistText(\stdClass $checklist): string {
$checklist_string = "\n\n## {$checklist->name}\n";
foreach ($checklist->checkItems as $item) {
$checklist_item_string = $this->checklistItem($item);
$checklist_string = $checklist_string . "\n" . $checklist_item_string;
}
return $checklist_string;
}
private function appendAttachmentsToDescription(\stdClass $trelloCard): void {
if (empty($trelloCard->attachments)) {
return;
}
$trelloCard->desc .= "\n\n## {$this->l10n->t('Attachments')}\n";
$trelloCard->desc .= "| {$this->l10n->t('File')} | {$this->l10n->t('date')} |\n";
$trelloCard->desc .= "|---|---\n";
foreach ($trelloCard->attachments as $attachment) {
$name = mb_strlen($attachment->name, 'UTF-8') ? $attachment->name : $attachment->url;
$trelloCard->desc .= "| [{$name}]({$attachment->url}) | {$attachment->date} |\n";
}
}
}
@@ -0,0 +1,41 @@
{
"type": "object",
"properties": {
"api": {
"type": "object",
"properties": {
"key": {
"type": "string",
"pattern": "^[0-9a-fA-F]{32}$"
},
"token": {
"type": "string",
"pattern": "^[0-9a-fA-F]{64}$"
}
}
},
"board": {
"type": "string",
"pattern": "^\\w{1,}$"
},
"uidRelation": {
"type": "object",
"comment": "Relationship between Trello and Nextcloud usernames",
"example": {
"johndoe": "admin"
}
},
"owner": {
"type": "string",
"required": true,
"comment": "Nextcloud owner username"
},
"color": {
"type": "string",
"required": true,
"pattern": "^[0-9a-fA-F]{6}$",
"comment": "Default color for the board. If you don't inform, the default color will be used.",
"default": "0800fd"
}
}
}
@@ -0,0 +1,24 @@
{
"type": "object",
"properties": {
"uidRelation": {
"type": "object",
"comment": "Relationship between Trello and Nextcloud usernames",
"example": {
"johndoe": "admin"
}
},
"owner": {
"type": "string",
"required": true,
"comment": "Nextcloud owner username"
},
"color": {
"type": "string",
"required": true,
"pattern": "^[0-9a-fA-F]{6}$",
"comment": "Default color for the board. If you don't inform, the default color will be used.",
"default": "0800fd"
}
}
}
+1 -1
View File
@@ -138,7 +138,7 @@ class OverviewService {
private function findAllBoardsFromUser(string $userId): array {
$userInfo = $this->getBoardPrerequisites($userId);
$userBoards = $this->boardMapper->findAllByUser($userInfo['user'], null, null);
$groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups'],null, null);
$groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups'], null, null);
$circleBoards = $this->boardMapper->findAllByCircles($userInfo['user'], null, null);
return array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
}
+10
View File
@@ -241,6 +241,7 @@ 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) {
@@ -332,4 +333,13 @@ 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;
}
}
+1 -1
View File
@@ -500,7 +500,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
);
}
$qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
$qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
$qb->orderBy('s.id');
+3 -1
View File
@@ -11,4 +11,6 @@ pages:
- Nextcloud API: API-Nextcloud.md
- Developer documentation:
- Data structure: structure.md
- Import documentation:
- Implement import: implement-import.md
- Class diagram: import-class-diagram.md
+4394 -4016
View File
File diff suppressed because it is too large Load Diff
+17 -17
View File
@@ -1,7 +1,7 @@
{
"name": "deck",
"description": "",
"version": "1.6.1",
"version": "1.7.0-beta1",
"authors": [
{
"name": "Julius Härtl",
@@ -29,25 +29,25 @@
},
"dependencies": {
"@babel/polyfill": "^7.12.1",
"@babel/runtime": "^7.16.0",
"@babel/runtime": "^7.17.8",
"@juliushaertl/vue-richtext": "^1.0.1",
"@nextcloud/auth": "^1.3.0",
"@nextcloud/axios": "^1.7.0",
"@nextcloud/axios": "^1.9.0",
"@nextcloud/dialogs": "^3.1.2",
"@nextcloud/event-bus": "^2.1.1",
"@nextcloud/files": "^2.1.0",
"@nextcloud/initial-state": "^1.2.1",
"@nextcloud/l10n": "^1.4.1",
"@nextcloud/moment": "^1.1.1",
"@nextcloud/moment": "^1.2.0",
"@nextcloud/router": "^2.0.0",
"@nextcloud/vue": "^4.2.0",
"@nextcloud/vue": "^5.1.1",
"@nextcloud/vue-dashboard": "^2.0.1",
"blueimp-md5": "^2.19.0",
"dompurify": "^2.3.3",
"dompurify": "^2.3.6",
"lodash": "^4.17.21",
"markdown-it": "^12.2.0",
"markdown-it": "^12.3.2",
"markdown-it-task-lists": "^2.1.1",
"markdown-it-link-attributes": "^3.0.0",
"markdown-it-link-attributes": "^4.0.0",
"moment": "^2.29.1",
"nextcloud-vue-collections": "^0.9.0",
"p-queue": "^6.6.2",
@@ -66,18 +66,18 @@
"extends @nextcloud/browserslist-config"
],
"engines": {
"node": ">=14.0.0",
"npm": ">=7.0.0"
"node": "^14.0.0",
"npm": "^7.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",
"@nextcloud/eslint-config": "^6.1.2",
"@nextcloud/stylelint-config": "^2.1.2",
"@nextcloud/webpack-vue-config": "^5.0.0",
"@relative-ci/agent": "^3.1.2",
"@vue/test-utils": "^1.3.0",
"jest": "^27.5.1",
"jest-serializer-vue": "^2.0.2",
"vue-jest": "^3.0.7"
},
@@ -97,4 +97,4 @@
"<rootDir>/node_modules/jest-serializer-vue"
]
}
}
}
+16 -4
View File
@@ -88,7 +88,6 @@ export default {
navShown: state => state.navShown,
sidebarShownState: state => state.sidebarShown,
currentBoard: state => state.currentBoard,
cardDetailsInModal: state => state.cardDetailsInModal,
}),
// TODO: properly handle sidebar showing for route subview and board sidebar
sidebarRouterView() {
@@ -98,6 +97,14 @@ export default {
sidebarShown() {
return this.sidebarRouterView || this.sidebarShownState
},
cardDetailsInModal: {
get() {
return this.$store.getters.config('cardDetailsInModal')
},
set(newValue) {
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
},
created() {
this.$store.dispatch('loadBoards')
@@ -150,10 +157,9 @@ export default {
.modal__card {
min-width: 320px;
width: 50vw;
max-width: 800px;
width: 100%;
min-height: 200px;
height: 80vh;
height: auto;
}
</style>
@@ -163,4 +169,10 @@ export default {
width: 100%;
}
.modal-wrapper--normal .modal-container{
width: 900px !important;
height: 800px !important;
overflow-y: hidden !important;
}
</style>
+23
View File
@@ -239,6 +239,7 @@ export default {
isAddStackVisible: false,
filter: { tags: [], users: [], due: '', unassigned: false },
showAddCardModal: false,
defaultPageTitle: false,
}
},
@@ -266,11 +267,17 @@ export default {
return [...this.board.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
},
},
beforeDestroy() {
this.setPageTitle('')
},
watch: {
board(current, previous) {
if (current?.id !== previous?.id) {
this.clearFilter()
}
if (current) {
this.setPageTitle(current.title)
}
},
},
methods: {
@@ -330,6 +337,22 @@ export default {
clickHideAddCardModel() {
this.showAddCardModal = false
},
setPageTitle(title) {
if (this.defaultPageTitle === false) {
this.defaultPageTitle = window.document.title
if (this.defaultPageTitle.indexOf(' - Deck - ') !== -1) {
this.defaultPageTitle = this.defaultPageTitle.substring(this.defaultPageTitle.indexOf(' - Deck - ') + 3)
}
if (this.defaultPageTitle.indexOf('Deck - ') !== 0) {
this.defaultPageTitle = 'Deck - ' + this.defaultPageTitle
}
}
let newTitle = this.defaultPageTitle
if (title !== '') {
newTitle = `${title} - ${newTitle}`
}
window.document.title = newTitle
},
},
}
</script>
+3
View File
@@ -45,4 +45,7 @@ export default {
#app-sidebar .icon-close {
z-index: 100;
}
.app-deck .app-sidebar {
z-index: 20000 !important;
}
</style>
+2
View File
@@ -77,6 +77,7 @@ import Controls from '../Controls'
import Stack from './Stack'
import { EmptyContent } from '@nextcloud/vue'
import GlobalSearchResults from '../search/GlobalSearchResults'
import { showError } from '../../helpers/errors'
export default {
name: 'Board',
@@ -139,6 +140,7 @@ export default {
await this.$store.dispatch('loadStacks', this.id)
} catch (e) {
console.error(e)
showError(e)
}
this.loading = false
},
+37 -1
View File
@@ -53,6 +53,9 @@
<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>
@@ -72,7 +75,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 } from '@nextcloud/dialogs'
import { showError, showSuccess } from '@nextcloud/dialogs'
import debounce from 'lodash/debounce'
export default {
@@ -97,6 +100,7 @@ export default {
isSearching: false,
addAcl: null,
addAclForAPI: null,
newOwner: null,
}
},
computed: {
@@ -194,6 +198,38 @@ 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>
+8 -1
View File
@@ -162,7 +162,6 @@ export default {
]),
...mapState({
showArchived: state => state.showArchived,
cardDetailsInModal: state => state.cardDetailsInModal,
}),
cardsByStack() {
return this.$store.getters.cardsByStack(this.stack.id).filter((card) => {
@@ -175,6 +174,14 @@ export default {
dragHandleSelector() {
return this.canEdit ? null : '.no-drag'
},
cardDetailsInModal: {
get() {
return this.$store.getters.config('cardDetailsInModal')
},
set(newValue) {
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
},
methods: {
+1 -1
View File
@@ -269,7 +269,7 @@ export default {
padding-left: 44px;
background-position: 16px center;
flex-grow: 1;
height: 44px;
height: auto;
margin-bottom: 12px;
text-align: left;
}
+39
View File
@@ -0,0 +1,39 @@
<template>
<div v-if="activeTabs.includes('attachment')" class="section-details">
<AttachmentList
:card-id="card.id"
:removable="true"
@delete-attachment="deleteAttachment"
@restore-attachment="restoreAttachment" />
</div>
</template>
<script>
import AttachmentList from './AttachmentList'
export default {
components: { AttachmentList },
props: {
card: {
type: Object,
default: null,
},
activeTabs: {
type: Array,
default: () => [],
},
},
data() {
return {
}
},
methods: {
deleteAttachment(attachment) {
this.$store.dispatch('deleteAttachment', attachment)
},
restoreAttachment(attachment) {
this.$store.dispatch('restoreAttachment', attachment)
},
},
}
</script>
+460
View File
@@ -0,0 +1,460 @@
<!--
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div v-if="currentCard" ref="modalContainer" class="container">
<div :class="{defaultTop: scrollPosition < 100, fixedTop: scrollPosition > 100}">
<div class="top">
<h1 class="top-title">
{{ currentCard.title }}
</h1>
<p class="top-modified">
{{ t('deck', 'Modified') }}: {{ currentCard.lastModified | fromNow }}. {{ t('deck', 'Created') }} {{ currentCard.createdAt | fromNow }}
</p>
</div>
<div class="tabs">
<div class="tab members" :class="{active: activeTabs.includes('members')}" @click="changeActiveTab('members')">
<i class="icon-user icon" />
{{ t('deck', 'Members') }}
</div>
<div class="tab tags" :class="{active: activeTabs.includes('tags')}" @click="changeActiveTab('tags')">
<i class="icon icon-tag" />
{{ t('deck', 'Tags') }}
</div>
<div class="tab due-date" :class="{active: activeTabs.includes('duedate')}" @click="changeActiveTab('duedate')">
<i class="icon icon-calendar-dark" />
{{ t('deck', 'Due date') }}
</div>
<div class="tab project" :class="{active: activeTabs.includes('project')}" @click="changeActiveTab('project')">
<i class="icon icon-deck" />
{{ t('deck', 'Project') }}
</div>
<div class="tab attachments" :class="{active: activeTabs.includes('attachment')}" @click="changeActiveTab('attachment')">
<i class="icon-attach icon icon-attach-dark" />
{{ t('deck', 'Attachments') }}
</div>
</div>
</div>
<div class="content">
<div :class="[currentCard.labels.length > 3 ? 'content-two-tabs' : 'content-three-tabs']">
<MembersTab
:card="currentCard"
:active-tabs="activeTabs"
:current-tab="currentTab"
@click="activeTabs.push('members')"
@active-tab="changeActiveTab"
@remove-active-tab="removeActiveTab" />
<TagsTab
:active-tabs="activeTabs"
:card="currentCard"
:current-tab="currentTab"
@click="activeTabs.push('tags')"
@active-tab="changeActiveTab"
@remove-active-tab="removeActiveTab" />
<DueDateTab
:active-tabs="activeTabs"
:card="currentCard"
:current-tab="currentTab"
@click="activeTabs.push('duedate')"
@active-tab="changeActiveTab"
@remove-active-tab="removeActiveTab" />
<ProjectTab
:active-tabs="activeTabs"
:card="currentCard"
:current-tab="currentTab"
@click="activeTabs.push('project')"
@active-tab="changeActiveTab" />
<AttachmentsTab
:active-tabs="activeTabs"
:card="currentCard"
:current-tab="currentTab"
@click="activeTabs.push('attachment')"
@active-tab="changeActiveTab" />
</div>
<Description :key="currentCard.id" :card="currentCard" @change="descriptionChanged" />
</div>
<div class="activities">
<div class="activities-header">
<div class="activities-title">
<i class="icon-activity" /> {{ t('deck', 'Activity') }}
</div>
<div class="show-details-btn" @click="showDetails = !showDetails">
{{ showDetails ? t('deck', 'Hide details') : t('deck', 'Show details') }}
</div>
</div>
<template v-if="showDetails">
<CardSidebarTabComments :card="currentCard" :tab-query="tabQuery" />
<ActivityList v-if="hasActivity"
filter="deck"
:object-id="currentBoard.id"
object-type="deck"
type="deck" />
</template>
</div>
</div>
</template>
<script>
import { generateUrl } from '@nextcloud/router'
import { mapState, mapGetters } from 'vuex'
import relativeDate from '../../mixins/relativeDate'
import { showError } from '@nextcloud/dialogs'
import { getCurrentUser } from '@nextcloud/auth'
import MembersTab from './MembersTab.vue'
import TagsTab from './TagsTab.vue'
import DueDateTab from './DueDateTab.vue'
import Description from './Description.vue'
import ProjectTab from './ProjectTab.vue'
import AttachmentsTab from './AttachmentsTab.vue'
import CardSidebarTabComments from './CardSidebarTabComments'
import moment from '@nextcloud/moment'
import ActivityList from '../ActivityList'
const capabilities = window.OC.getCapabilities()
export default {
name: 'CardModal',
components: {
MembersTab,
Description,
TagsTab,
DueDateTab,
ProjectTab,
AttachmentsTab,
CardSidebarTabComments,
ActivityList,
},
filters: {
fromNow(value) {
return moment.unix(value).fromNow()
},
},
mixins: [relativeDate],
props: {
id: {
type: Number,
required: true,
},
tabId: {
type: String,
required: false,
default: null,
},
tabQuery: {
type: String,
required: false,
default: null,
},
},
data() {
return {
newComment: '',
titleEditable: false,
titleEditing: '',
hasActivity: capabilities && capabilities.activity,
currentUser: getCurrentUser(),
comment: '',
currentTab: null,
activeTabs: [],
showDetails: false,
scrollPosition: null,
}
},
computed: {
...mapGetters([
'getCommentsForCard',
'hasMoreComments',
]),
...mapState({
currentBoard: state => state.currentBoard,
replyTo: state => state.comment.replyTo,
}),
...mapGetters(['canEdit', 'assignables', 'cardActions', 'stackById']),
title() {
return this.titleEditable ? this.titleEditing : this.currentCard.title
},
currentCard() {
return this.$store.getters.cardById(this.id)
},
subtitle() {
return t('deck', 'Modified') + ': ' + this.relativeDate(this.currentCard.lastModified * 1000) + ' ' + t('deck', 'Created') + ': ' + this.relativeDate(this.currentCard.createdAt * 1000)
},
cardRichObject() {
return {
id: '' + this.currentCard.id,
name: this.currentCard.title,
boardname: this.currentBoard.title,
stackname: this.stackById(this.currentCard.stackId)?.title,
link: window.location.protocol + '//' + window.location.host + generateUrl('/apps/deck/') + `#/board/${this.currentBoard.id}/card/${this.currentCard.id}`,
}
},
cardDetailsInModal: {
get() {
return this.$store.getters.config('cardDetailsInModal')
},
set(newValue) {
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
},
mounted() {
this.$nextTick(() => {
this.$refs.modalContainer.addEventListener('scroll', this.onScroll)
})
this.loadComments()
},
methods: {
cancelReply() {
this.$store.dispatch('setReplyTo', null)
},
async createComment(content) {
const commentObj = {
cardId: this.currentCard.id,
comment: content,
}
await this.$store.dispatch('createComment', commentObj)
this.$store.dispatch('setReplyTo', null)
this.newComment = ''
await this.loadComments()
},
async loadComments() {
this.$store.dispatch('setReplyTo', null)
this.error = null
this.isLoading = true
try {
await this.$store.dispatch('fetchComments', { cardId: this.currentCard.id })
this.isLoading = false
if (this.currentCard.commentsUnread > 0) {
await this.$store.dispatch('markCommentsAsRead', this.currentCard.id)
}
} catch (e) {
this.isLoading = false
console.error('Failed to fetch more comments during infinite loading', e)
this.error = t('deck', 'Failed to load comments')
}
},
descriptionChanged(newDesc) {
this.copiedCard.description = newDesc
},
handleUpdateTitleEditable(value) {
this.titleEditable = value
if (value) {
this.titleEditing = this.currentCard.title
}
},
handleUpdateTitle(value) {
this.titleEditing = value
},
handleSubmitTitle(value) {
if (value.trim === '') {
showError(t('deck', 'The title cannot be empty.'))
return
}
this.titleEditable = false
this.$store.dispatch('updateCardTitle', { ...this.currentCard, title: this.titleEditing })
},
closeSidebar() {
this.$router.push({ name: 'board' })
},
showModal() {
this.$store.dispatch('setConfig', { cardDetailsInModal: true })
},
closeModal() {
this.$store.dispatch('setConfig', { cardDetailsInModal: false })
},
changeActiveTab(tab) {
this.currentTab = tab
this.activeTabs = this.activeTabs.filter((item) => !['project', 'attachment'].includes(item))
if (!this.activeTabs.includes(tab)) {
this.activeTabs.push(tab)
}
},
removeActiveTab(tab) {
const index = this.activeTabs.indexOf(tab)
if (index > -1) {
this.activeTabs = this.activeTabs.splice(index, 1)
}
},
onScroll() {
this.scrollPosition = this.$refs.modalContainer.scrollTop
},
},
}
</script>
<style lang="scss" scoped>
.show-details-btn {
cursor: pointer;
text-decoration: underline;
color: var(--color-text-maxcontrast);
}
.activities-header{
display: flex;
justify-content: space-between;
}
.content-two-tabs, .content-three-tabs {
display: grid;
align-items: flex-start;
}
.content-two-tabs {
grid-template-columns: 1fr 2fr;
}
.content-three-tabs {
grid-template-columns: 1fr 2fr 1fr;
}
.icon-activity {
background-image: url(../../../img/flash-black.svg);
width: 15px;
height: 15px;
margin-right: 5px;
}
.icon {
margin-right: 5px;
}
.icon-plus {
background-image: url(../../../img/plus.svg);
width: 15px;
height: 15px;
margin-right: 5px;
}
.log-item {
display: flex;
justify-content: flex-start;
line-height: 45px;
align-items: center;
}
.activities {
&-title{
display: flex;
justify-content: flex-start;
align-items: center;
margin-bottom: 15px;
font-weight: bold;
}
margin-top: 100px;
padding-left: 20px !important;
padding-right: 20px !important;
}
.content{
padding-left: 20px;
padding-right: 20px;
}
.comments {
display: flex;
justify-content: space-between;
align-items: center;
&-input{
width: 100%;
margin-left: 10px;
}
.comment-form{
width: 95%;
}
}
.container {
overflow-y: scroll;
height: 800px;
}
.top {
padding: 20px 20px 0px 20px;
&-title {
color: black;
font-size: 20px;
font-weight: bold;
}
&-modified {
color: #767676;
line-height: 40px;
}
}
.tabs {
padding-left: 20px;
padding-right: 20px;
margin-top: 20px;
margin-bottom: 5px;
display: flex;
}
.tab {
cursor: pointer;
font-weight: bold;
background-color: #ededed;
color: rgb(0, 0, 0);
flex-grow: 0;
flex-shrink: 1;
display: flex;
flex-direction: row;
overflow: hidden;
padding: 10px 20px;
border-radius: 10px;
font-size: 85%;
margin-bottom: 3px;
margin-right: 15px;
}
.action-btn {
list-style: none;
}
.edit-btns {
display: flex;
align-items: center;
}
.description {
display: flex;
justify-content: space-between;
margin-top: 30px;
}
.active {
color: #409eff;
background-color: #ecf5ff;
}
.fixedTop {
position: sticky;
top: 0;
background-color: #ffffff;
z-index: 1000;
margin-top: 0px;
padding-bottom: 5px;
}
</style>
+23 -3
View File
@@ -25,13 +25,14 @@
:active="tabId"
:title="title"
:subtitle="subtitle"
:subtitle-tooltip="subtitleTooltip"
:title-editable="titleEditable"
@update:titleEditable="handleUpdateTitleEditable"
@update:title="handleUpdateTitle"
@submit-title="handleSubmitTitle"
@close="closeSidebar">
<template #secondary-actions>
<ActionButton v-if="cardDetailsInModal" icon="icon-menu-sidebar" @click.stop="showModal()">
<ActionButton v-if="cardDetailsInModal" icon="icon-menu-sidebar" @click.stop="closeModal()">
{{ t('deck', 'Open in sidebar view') }}
</ActionButton>
<ActionButton v-else icon="icon-external" @click.stop="showModal()">
@@ -88,8 +89,10 @@ import CardSidebarTabAttachments from './CardSidebarTabAttachments'
import CardSidebarTabComments from './CardSidebarTabComments'
import CardSidebarTabActivity from './CardSidebarTabActivity'
import relativeDate from '../../mixins/relativeDate'
import moment from '@nextcloud/moment'
import { showError } from '@nextcloud/dialogs'
import { getLocale } from '@nextcloud/l10n'
const capabilities = window.OC.getCapabilities()
@@ -126,12 +129,12 @@ export default {
titleEditable: false,
titleEditing: '',
hasActivity: capabilities && capabilities.activity,
locale: getLocale(),
}
},
computed: {
...mapState({
currentBoard: state => state.currentBoard,
cardDetailsInModal: state => state.cardDetailsInModal,
}),
...mapGetters(['canEdit', 'assignables', 'cardActions', 'stackById']),
title() {
@@ -143,6 +146,9 @@ export default {
subtitle() {
return t('deck', 'Modified') + ': ' + this.relativeDate(this.currentCard.lastModified * 1000) + ' ' + t('deck', 'Created') + ': ' + this.relativeDate(this.currentCard.createdAt * 1000)
},
subtitleTooltip() {
return t('deck', 'Modified') + ': ' + this.formatDate(this.currentCard.lastModified) + '\n' + t('deck', 'Created') + ': ' + this.formatDate(this.currentCard.createdAt)
},
cardRichObject() {
return {
id: '' + this.currentCard.id,
@@ -152,6 +158,14 @@ export default {
link: window.location.protocol + '//' + window.location.host + generateUrl('/apps/deck/') + `#/board/${this.currentBoard.id}/card/${this.currentCard.id}`,
}
},
cardDetailsInModal: {
get() {
return this.$store.getters.config('cardDetailsInModal')
},
set(newValue) {
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
},
methods: {
handleUpdateTitleEditable(value) {
@@ -177,7 +191,13 @@ export default {
},
showModal() {
this.$store.dispatch('setCardDetailsInModal', true)
this.$store.dispatch('setConfig', { cardDetailsInModal: true })
},
closeModal() {
this.$store.dispatch('setConfig', { cardDetailsInModal: false })
},
formatDate(timestamp) {
return moment.unix(timestamp).locale(this.locale).format('LLLL')
},
},
}
+11 -15
View File
@@ -1,19 +1,16 @@
<template>
<div>
<div class="comment--header">
<Avatar :user="currentUser.uid" />
<span class="has-tooltip username">
{{ currentUser.displayName }}
</span>
<div class="comment-wrapper">
<div class="comment--header">
<Avatar :user="currentUser.uid" />
</div>
<CommentItem v-if="replyTo"
:comment="replyTo"
:reply="true"
:preview="true"
@cancel="cancelReply" />
<CommentForm v-model="newComment" @submit="createComment" />
</div>
<CommentItem v-if="replyTo"
:comment="replyTo"
:reply="true"
:preview="true"
@cancel="cancelReply" />
<CommentForm v-model="newComment" @submit="createComment" />
<ul v-if="getCommentsForCard(card.id).length > 0" id="commentsFeed">
<CommentItem v-for="comment in getCommentsForCard(card.id)"
:key="comment.id"
@@ -26,8 +23,7 @@
</InfiniteLoading>
</ul>
<div v-else-if="isLoading" class="icon icon-loading" />
<div v-else class="emptycontent">
<div :class="{ 'icon-comment': !error, 'icon-error': error }" />
<div v-else>
<p>{{ error || t('deck', 'No comments yet. Begin the discussion!') }}</p>
</div>
</div>
@@ -224,7 +224,6 @@ export default {
computed: {
...mapState({
currentBoard: state => state.currentBoard,
cardDetailsInModal: state => state.cardDetailsInModal,
}),
...mapGetters(['canEdit', 'assignables']),
formatedAssignables() {
@@ -250,6 +249,14 @@ export default {
return assignable
})
},
cardDetailsInModal: {
get() {
return this.$store.getters.config('cardDetailsInModal')
},
set(newValue) {
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
duedate: {
get() {
return this.card.duedate ? new Date(this.card.duedate) : null
+16 -2
View File
@@ -132,7 +132,6 @@ export default {
computed: {
...mapState({
currentBoard: state => state.currentBoard,
cardDetailsInModal: state => state.cardDetailsInModal,
}),
...mapGetters(['canEdit']),
attachments() {
@@ -261,6 +260,8 @@ export default {
#description-preview {
min-height: 100px;
width: auto;
overflow-x: auto;
&::v-deep {
@import './../../css/markdown';
@@ -279,7 +280,9 @@ h5 {
border-bottom: 1px solid var(--color-border);
margin-top: 20px;
margin-bottom: 5px;
color: var(--color-text-maxcontrast);
color: var(--color-main-text);
font-weight: bold;
font-size: 20px;
.icon-info {
display: inline-block;
@@ -331,4 +334,15 @@ h5 {
#app-sidebar .app-sidebar-header__desc h4 {
font-size: 12px !important;
}
.vue-easymde .cm-s-easymde .cm-link {
color: var(--color-main-text);
}
.vue-easymde .cm-s-easymde .cm-string.cm-url,
.vue-easymde .cm-s-easymde .cm-formatting.cm-link,
.vue-easymde .cm-s-easymde .cm-formatting.cm-url,
.vue-easymde .cm-s-easymde .cm-formatting.cm-image {
color: var(--color-text-maxcontrast);
}
</style>
+187
View File
@@ -0,0 +1,187 @@
<template>
<div v-if="activeTabs.includes('duedate') || (copiedCard && copiedCard.duedate)"
v-show="!['project', 'attachment'].includes(currentTab)"
class="section-details">
<div @click="$emit('active-tab', 'duedate')">
<DatetimePicker v-model="duedate"
:placeholder="t('deck', 'Set a due date')"
type="datetime"
:minute-step="5"
:show-second="false"
:lang="lang"
:disabled="saving || !canEdit"
:shortcuts="shortcuts"
confirm />
</div>
<Actions v-if="canEdit">
<ActionButton v-if="copiedCard.duedate" icon="icon-delete" @click="removeDue()">
{{ t('deck', 'Remove due date') }}
</ActionButton>
</Actions>
</div>
</template>
<script>
import { DatetimePicker, Actions, ActionButton } from '@nextcloud/vue'
import { mapState, mapGetters } from 'vuex'
import Color from '../../mixins/color'
import labelStyle from '../../mixins/labelStyle'
import {
getDayNamesMin,
getFirstDay,
getMonthNamesShort,
} from '@nextcloud/l10n'
import moment from '@nextcloud/moment'
export default {
components: { DatetimePicker, Actions, ActionButton },
mixins: [Color, labelStyle],
props: {
card: {
type: Object,
default: null,
},
activeTabs: {
type: Array,
default: () => [],
},
currentTab: {
type: String,
default: '',
},
},
data() {
return {
saving: false,
copiedCard: null,
lang: {
days: getDayNamesMin(),
months: getMonthNamesShort(),
formatLocale: {
firstDayOfWeek: getFirstDay() === 0 ? 7 : getFirstDay(),
},
placeholder: {
date: t('deck', 'Select Date'),
},
},
format: {
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: {
...mapState({
currentBoard: state => state.currentBoard,
}),
...mapGetters(['canEdit']),
labelsSorted() {
return [...this.currentBoard.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
},
duedate: {
get() {
return this.card.duedate ? new Date(this.card.duedate) : null
},
async set(val) {
this.saving = true
await this.$store.dispatch('updateCardDue', {
...this.copiedCard,
duedate: val ? moment(val).format('YYYY-MM-DD H:mm:ss') : null,
})
this.saving = false
},
},
},
watch: {
card() {
this.initialize()
if (this.copiedCard.duedate) {
this.$emit('active-tab', 'duedate')
} else {
this.$emit('remove-active-tab', 'duedate')
}
},
},
mounted() {
this.initialize()
if (this.copiedCard.duedate) {
this.$emit('active-tab', 'duedate')
} else {
this.$emit('remove-active-tab', 'duedate')
}
},
methods: {
async initialize() {
if (!this.card) {
return
}
this.copiedCard = JSON.parse(JSON.stringify(this.card))
},
removeDue() {
this.copiedCard.duedate = null
this.$store.dispatch('updateCardDue', this.copiedCard)
},
},
}
</script>
<style lang="scss" scoped>
.section-details{
margin-right: 5px;
display: flex;
align-items: flex-start;
margin-top: 10px;
}
</style>
<style>
.section-details .mx-input{
height: 36px !important;
margin: 0;
}
.section-details .action-item {
height: 30px !important;
}
</style>
+208
View File
@@ -0,0 +1,208 @@
<template>
<div v-if="activeTabs.includes('members') || (assignedUsers && assignedUsers.length > 0)"
v-show="!['project', 'attachment'].includes(currentTab)"
class="section-details">
<div v-if="showSelelectMembers" @mouseleave="showSelelectMembers = false">
<Multiselect v-if="canEdit"
v-model="assignedUsers"
:multiple="true"
:options="formatedAssignables"
:user-select="true"
:auto-limit="false"
:placeholder="t('deck', 'Assign a user to this card…')"
label="displayname"
track-by="multiselectKey"
@select="assignUserToCard"
@remove="removeUserFromCard">
<template #tag="scope">
<div class="avatarlist--inline">
<Avatar :user="scope.option.uid"
:display-name="scope.option.displayname"
:size="24"
:is-no-user="scope.option.isNoUser"
:disable-menu="true" />
</div>
</template>
</Multiselect>
<div v-else class="avatar-list--readonly">
<Avatar v-for="option in assignedUsers"
:key="option.primaryKey"
:user="option.uid"
:display-name="option.displayname"
:is-no-user="option.isNoUser"
:size="32" />
</div>
</div>
<template v-else>
<div class="members">
<Avatar v-for="option in assignedUsers"
:key="option.primaryKey"
:user="option.uid"
:display-name="option.displayname"
:is-no-user="option.isNoUser"
:size="32" />
<div class="button new select-member-btn" @click="selectMembers">
<span class="icon icon-add" />
<span class="hidden-visually" />
</div>
</div>
</template>
</div>
</template>
<script>
import { Multiselect, Avatar } from '@nextcloud/vue'
import { mapState, mapGetters } from 'vuex'
export default {
name: 'MembersTab',
components: {
Multiselect,
Avatar,
},
props: {
card: {
type: Object,
default: null,
},
activeTabs: {
type: Array,
default: () => [],
},
currentTab: {
type: String,
default: '',
},
},
data() {
return {
assignedUsers: null,
copiedCard: null,
showSelelectMembers: false,
}
},
computed: {
...mapState({
currentBoard: state => state.currentBoard,
}),
...mapGetters(['canEdit', 'assignables']),
formatedAssignables() {
return this.assignables.map(item => {
const assignable = {
...item,
user: item.primaryKey,
displayName: item.displayname,
icon: 'icon-user',
isNoUser: false,
multiselectKey: item.type + ':' + item.uid,
}
if (item.type === 1) {
assignable.icon = 'icon-group'
assignable.isNoUser = true
}
if (item.type === 7) {
assignable.icon = 'icon-circles'
assignable.isNoUser = true
}
return assignable
})
},
},
watch: {
card() {
this.initialize()
},
assignedUsers(value) {
if (value.length > 0) {
this.$emit('active-tab', 'members')
} else {
this.$emit('remove-active-tab', 'members')
}
},
},
mounted() {
this.initialize()
},
methods: {
selectMembers() {
this.showSelelectMembers = true
this.$emit('active-tab', 'members')
},
removeUserFromCard(user) {
this.$store.dispatch('removeUserFromCard', {
card: this.copiedCard,
assignee: {
userId: user.uid,
type: user.type,
},
})
},
addLabelToCard(newLabel) {
this.copiedCard.labels.push(newLabel)
const data = {
card: this.copiedCard,
labelId: newLabel.id,
}
this.$store.dispatch('addLabel', data)
},
assignUserToCard(user) {
this.$store.dispatch('assignCardToUser', {
card: this.copiedCard,
assignee: {
userId: user.uid,
type: user.type,
},
})
},
async initialize() {
if (!this.card) {
return
}
this.copiedCard = JSON.parse(JSON.stringify(this.card))
this.assignedLabels = [...this.card.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
if (this.card.assignedUsers && this.card.assignedUsers.length > 0) {
this.assignedUsers = this.card.assignedUsers.map((item) => ({
...item.participant,
isNoUser: item.participant.type !== 0,
multiselectKey: item.participant.type + ':' + item.participant.primaryKey,
}))
} else {
this.assignedUsers = []
}
},
},
}
</script>
<style lang="scss" scoped>
.select-member-btn {
box-sizing: border-box;
display: flex;
height: 32px;
width: 34px;
padding: 5px 9px;
align-items: center;
justify-content: center;
margin-left: 5px;
}
.section-details {
width: 100%;
margin-top: 10px;
}
.members {
display: flex;
align-items: center;
}
</style>
<style>
.members .multiselect__tags{
height: 34px !important;
}
</style>
+53
View File
@@ -0,0 +1,53 @@
<template>
<div v-if="activeTabs.includes('project')" class="section-details">
<div class="section-wrapper project-tab">
<CollectionList v-if="card.id"
:id="`${card.id}`"
:name="card.title"
type="deck-card" />
</div>
</div>
</template>
<script>
import { CollectionList } from 'nextcloud-vue-collections'
export default {
components: { CollectionList },
props: {
card: {
type: Object,
default: null,
},
activeTabs: {
type: Array,
default: () => [],
},
},
}
</script>
<style scoped>
.section-details{
margin-top: 10px;
min-width: 500px;
}
</style>
<style lang="scss">
#collection-select-container p {
display: none;
}
#collection-list li {
align-items: flex-end;
}
.project-tab .collection-list-item {
display: flex !important;
align-items: center !important;
}
.project-tab .linked-icons {
img {
height: 20px !important;
}
}
</style>
+171
View File
@@ -0,0 +1,171 @@
<template>
<div v-if="activeTabs.includes('tags') || card.labels.length > 0"
v-show="!['project', 'attachment'].includes(currentTab)"
class="section-details">
<div v-if="showSelelectTags || card.labels.length <= 0" @mouseleave="showSelelectTags = false">
<Multiselect v-model="assignedLabels"
:multiple="true"
:disabled="!canEdit"
:options="labelsSorted"
:placeholder="t('deck', 'Assign a tag to this card')"
:taggable="true"
label="title"
track-by="id"
@select="addLabelToCard"
@remove="removeLabelFromCard">
<template #option="scope">
<div :style="{ backgroundColor: '#' + scope.option.color, color: textColor(scope.option.color)}" class="tag">
{{ scope.option.title }}
</div>
</template>
<template #tag="scope">
<div :style="{ backgroundColor: '#' + scope.option.color, color: textColor(scope.option.color)}" class="tag">
{{ scope.option.title }}
</div>
</template>
</Multiselect>
</div>
<div v-else-if="card.labels.length > 0" class="labels">
<div v-for="label in card.labels"
:key="label.id"
:style="labelStyle(label)"
class="labels-item">
<span @click.stop="applyLabelFilter(label)">{{ label.title }}</span>
</div>
<div class="button new select-tag" @click="add">
<span class="icon icon-add" />
<span class="hidden-visually" />
</div>
</div>
</div>
</template>
<script>
import { Multiselect } from '@nextcloud/vue'
import { mapState, mapGetters } from 'vuex'
import Color from '../../mixins/color'
import labelStyle from '../../mixins/labelStyle'
export default {
components: { Multiselect },
mixins: [Color, labelStyle],
props: {
card: {
type: Object,
default: null,
},
activeTabs: {
type: Array,
default: () => [],
},
currentTab: {
type: String,
default: '',
},
},
data() {
return {
assignedLabels: null,
showSelelectTags: false,
copiedCard: null,
}
},
computed: {
...mapState({
currentBoard: state => state.currentBoard,
}),
...mapGetters(['canEdit']),
labelsSorted() {
return [...this.currentBoard.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
},
},
watch: {
card(value) {
if (value.labels.length > 0) {
this.$emit('active-tab', 'tags')
} else {
this.$emit('remove-active-tab', 'tags')
}
},
},
mounted() {
this.initialize()
if (this.card.labels.length > 0) {
this.$emit('active-tab', 'tags')
}
},
methods: {
add() {
this.showSelelectTags = true
this.$emit('active-tab', 'tags')
},
async initialize() {
if (!this.card) {
return
}
this.copiedCard = JSON.parse(JSON.stringify(this.card))
this.assignedLabels = [...this.card.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
},
openCard() {
const boardId = this.card && this.card.boardId ? this.card.boardId : this.$route.params.id
this.$router.push({ name: 'card', params: { id: boardId, cardId: this.card.id } }).catch(() => {})
},
addLabelToCard(newLabel) {
this.copiedCard.labels.push(newLabel)
const data = {
card: this.copiedCard,
labelId: newLabel.id,
}
this.$store.dispatch('addLabel', data)
},
removeLabelFromCard(removedLabel) {
const removeIndex = this.copiedCard.labels.findIndex((label) => {
return label.id === removedLabel.id
})
if (removeIndex !== -1) {
this.copiedCard.labels.splice(removeIndex, 1)
}
const data = {
card: this.copiedCard,
labelId: removedLabel.id,
}
this.$store.dispatch('removeLabel', data)
},
},
}
</script>
<style lang="scss" scoped>
.labels {
display: flex;
justify-content: flex-start;
align-items: center;
&-item {
border-radius: 15px;
margin-right: 5px;
min-width: 110px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
}
}
.select-tag {
height: 32px;
width: 34px;
padding: 5px 8px;
}
.tag{
padding: 0px 5px;
border-radius: 15px;
}
.section-details{
margin-top: 10px;
}
</style>
+6 -2
View File
@@ -66,7 +66,11 @@
:placeholder="t('deck', 'Select a list')"
:options="stacksFromBoard"
:max-height="100"
label="title" />
label="title">
<span slot="noOptions">
{{ t('deck', 'List is empty') }}
</span>
</Multiselect>
<button :disabled="!isBoardAndStackChoosen" class="primary" @click="moveCard">
{{ t('deck', 'Move card') }}
@@ -131,7 +135,7 @@ export default {
},
activeBoards() {
return this.$store.getters.boards.filter((item) => item.deletedAt === 0 && item.archived === false)
}
},
},
methods: {
openCard() {
+2 -2
View File
@@ -144,10 +144,10 @@ export default {
},
cardDetailsInModal: {
get() {
return this.$store.getters.cardDetailsInModal
return this.$store.getters.config('cardDetailsInModal')
},
set(newValue) {
this.$store.dispatch('setCardDetailsInModal', newValue)
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
configCalendar: {
@@ -78,7 +78,7 @@ export default {
},
computed: {
boardsSorted() {
return [...this.boards].sort((a, b) => (a.title < b.title) ? -1 : 1)
return [...this.boards].sort((a, b) => a.title.localeCompare(b.title))
},
collapsible() {
return this.boards.length > 0
+14
View File
@@ -34,6 +34,7 @@
display: flex;
align-items: center;
color: var(--color-text-light);
margin-right: 10px;
.username {
padding: 10px;
@@ -50,3 +51,16 @@
margin-left: 44px;
word-break: break-word;
}
.comment-wrapper {
display: flex;
align-items: center;
}
.comment-form {
width: 100%;
}
.comment-form .comment-form__contenteditable {
border-radius: 4px;
}
+27
View File
@@ -0,0 +1,27 @@
import { showError as errorDialog } from '@nextcloud/dialogs'
const showAxiosError = err => {
const response = err?.response || {}
const message = response?.data.message
if (message) {
errorDialog(message)
return
}
errorDialog(err.message)
}
const showError = err => {
// axios error
if (err.response) {
showAxiosError(err)
return
}
errorDialog(err.message)
}
export {
showError,
}
+11
View File
@@ -63,6 +63,17 @@ Vue.config.errorHandler = (err, vm, info) => {
console.error(err)
}
// TODO: remove when we have a proper fileinfo standalone library
// original scripts are loaded from
// https://github.com/nextcloud/server/blob/5bf3d1bb384da56adbf205752be8f840aac3b0c5/lib/private/legacy/template.php#L120-L122
window.addEventListener('DOMContentLoaded', () => {
if (!window.OCA.Files) {
window.OCA.Files = {}
}
// register unused client for the sidebar to have access to its parser methods
Object.assign(window.OCA.Files, { App: { fileList: { filesClient: OC.Files.getClient() } } }, window.OCA.Files)
})
/* eslint-disable-next-line no-new */
new Vue({
el: '#content',
+2 -2
View File
@@ -28,7 +28,7 @@ import Boards from './components/boards/Boards'
import Board from './components/board/Board'
import Sidebar from './components/Sidebar'
import BoardSidebar from './components/board/BoardSidebar'
import CardSidebar from './components/card/CardSidebar'
import CardModal from './components/card/CardModal'
import Overview from './components/overview/Overview'
Vue.use(Router)
@@ -119,7 +119,7 @@ export default new Router({
path: 'card/:cardId/:tabId?/:tabQuery?',
name: 'card',
components: {
sidebar: CardSidebar,
sidebar: CardModal,
},
props: {
default: (route) => {
+7 -12
View File
@@ -26,7 +26,7 @@ import { loadState } from '@nextcloud/initial-state'
import Vue from 'vue'
import Vuex from 'vuex'
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import { generateOcsUrl, generateUrl } from '@nextcloud/router'
import { BoardApi } from '../services/BoardApi'
import actions from './actions'
import stack from './stack'
@@ -62,7 +62,6 @@ export default new Vuex.Store({
showArchived: false,
navShown: localStorage.getItem('deck.navShown') === 'true',
compactMode: localStorage.getItem('deck.compactMode') === 'true',
cardDetailsInModal: localStorage.getItem('deck.cardDetailsInModal') === 'true',
sidebarShown: false,
currentBoard: null,
currentCard: null,
@@ -79,9 +78,6 @@ export default new Vuex.Store({
config: state => (key) => {
return state.config[key]
},
cardDetailsInModal: state => {
return state.cardDetailsInModal
},
getSearchQuery: state => {
return state.searchQuery
},
@@ -233,10 +229,6 @@ export default new Vuex.Store({
state.compactMode = !state.compactMode
localStorage.setItem('deck.compactMode', state.compactMode)
},
setCardDetailsInModal(state) {
state.cardDetailsInModal = !state.cardDetailsInModal
localStorage.setItem('deck.cardDetailsInModal', state.cardDetailsInModal)
},
setBoards(state, boards) {
state.boards = boards
},
@@ -441,9 +433,6 @@ export default new Vuex.Store({
toggleCompactMode({ commit }) {
commit('toggleCompactMode')
},
setCardDetailsInModal({ commit }, show) {
commit('setCardDetailsInModal', show)
},
setCurrentBoard({ commit }, board) {
commit('setCurrentBoard', board)
},
@@ -497,5 +486,11 @@ export default new Vuex.Store({
dispatch('loadBoardById', acl.boardId)
})
},
async transferOwnership({ commit }, { boardId, owner, newOwner }) {
await axios.put(generateUrl(`apps/deck/boards/${boardId}/transferOwner`), {
owner,
newOwner,
})
},
},
})
+7
View File
@@ -0,0 +1,7 @@
{
"owner": "admin",
"color": "0800fd",
"uidRelation": {
"johndoe": "johndoe"
}
}
+582
View File
@@ -0,0 +1,582 @@
{
"id": "fakeboardidhash",
"name": "Test Board Name",
"desc": "",
"descData": null,
"closed": false,
"dateClosed": null,
"idOrganization": null,
"shortLink": "qwerty",
"powerUps": [],
"dateLastActivity": "2021-07-10T17:01:58.633Z",
"idTags": [],
"datePluginDisable": null,
"creationMethod": null,
"idBoardSource": null,
"idMemberCreator": "fakeidmemberhash",
"idEnterprise": null,
"pinned": false,
"starred": false,
"url": "https://trello.com/b/qwerty/fakeboardurl",
"prefs": {
"permissionLevel": "private",
"hideVotes": false,
"voting": "disabled",
"comments": "members",
"invitations": "members",
"selfJoin": false,
"cardCovers": true,
"isTemplate": false,
"cardAging": "regular",
"calendarFeedEnabled": false,
"background": "blue",
"backgroundImage": null,
"backgroundImageScaled": null,
"backgroundTile": false,
"backgroundBrightness": "dark",
"backgroundColor": "#0079BF",
"backgroundBottomColor": "#0079BF",
"backgroundTopColor": "#0079BF",
"canBePublic": true,
"canBeEnterprise": true,
"canBeOrg": true,
"canBePrivate": true,
"canInvite": true
},
"shortUrl": "https://trello.com/b/qwerty",
"premiumFeatures": [],
"enterpriseOwned": false,
"ixUpdate": "67",
"limits": {
"attachments": {
"perBoard": {
"status": "ok",
"disableAt": 36000,
"warnAt": 32400
},
"perCard": {
"status": "ok",
"disableAt": 1000,
"warnAt": 900
}
},
"boards": {
"totalMembersPerBoard": {
"status": "ok",
"disableAt": 1600,
"warnAt": 1440
}
},
"cards": {
"openPerBoard": {
"status": "ok",
"disableAt": 5000,
"warnAt": 4500
},
"openPerList": {
"status": "ok",
"disableAt": 5000,
"warnAt": 4500
},
"totalPerBoard": {
"status": "ok",
"disableAt": 2000000,
"warnAt": 1800000
},
"totalPerList": {
"status": "ok",
"disableAt": 1000000,
"warnAt": 900000
}
},
"checklists": {
"perBoard": {
"status": "ok",
"disableAt": 2000000,
"warnAt": 1800000
},
"perCard": {
"status": "ok",
"disableAt": 500,
"warnAt": 450
}
},
"checkItems": {
"perChecklist": {
"status": "ok",
"disableAt": 200,
"warnAt": 180
}
},
"customFields": {
"perBoard": {
"status": "ok",
"disableAt": 50,
"warnAt": 45
}
},
"customFieldOptions": {
"perField": {
"status": "ok",
"disableAt": 50,
"warnAt": 45
}
},
"labels": {
"perBoard": {
"status": "ok",
"disableAt": 1000,
"warnAt": 900
}
},
"lists": {
"openPerBoard": {
"status": "ok",
"disableAt": 500,
"warnAt": 450
},
"totalPerBoard": {
"status": "ok",
"disableAt": 3000,
"warnAt": 2700
}
},
"stickers": {
"perCard": {
"status": "ok",
"disableAt": 70,
"warnAt": 63
}
},
"reactions": {
"perAction": {
"status": "ok",
"disableAt": 1000,
"warnAt": 900
},
"uniquePerAction": {
"status": "ok",
"disableAt": 17,
"warnAt": 16
}
}
},
"subscribed": false,
"templateGallery": null,
"dateLastView": "2021-07-10T17:01:58.665Z",
"labelNames": {
"green": "",
"yellow": "",
"orange": "",
"red": "",
"purple": "",
"blue": "",
"sky": "",
"lime": "",
"pink": "",
"black": ""
},
"actions": [
{
"id": "60e9d2869efe2e1141be2798",
"idMemberCreator": "fakeidmemberhash",
"data": {
"idMember": "fakeidmemberhash",
"deactivated": false,
"card": {
"id": "hashcard7",
"name": "Name Card 7",
"idShort": 7,
"shortLink": "fakeshortlinkcard7"
},
"board": {
"id": "fakeboardidhash",
"name": "Test Board Name",
"shortLink": "qwerty"
},
"member": {
"id": "fakeidmemberhash",
"name": "John Doe"
}
},
"type": "removeMemberFromCard",
"date": "2021-07-10T17:01:58.636Z",
"appCreator": null,
"limits": {},
"member": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
},
"memberCreator": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
}
},
{
"id": "60e9d1832ff82d10c0cea6ba",
"idMemberCreator": "fakeidmemberhash",
"data": {
"idMember": "fakeidmemberhash",
"card": {
"id": "hashcard7",
"name": "Name Card 7",
"idShort": 7,
"shortLink": "fakeshortlinkcard7"
},
"board": {
"id": "fakeboardidhash",
"name": "Test Board Name",
"shortLink": "qwerty"
},
"member": {
"id": "fakeidmemberhash",
"name": "John Doe"
}
},
"type": "addMemberToCard",
"date": "2021-07-10T16:57:39.999Z",
"appCreator": null,
"limits": {},
"member": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
},
"memberCreator": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
}
},
{
"id": "59bbfc4bf36aa0270d6bfd43",
"idMemberCreator": "fakeidmemberhash",
"data": {
"board": {
"shortLink": "qwerty",
"name": "Test Board Name",
"id": "fakeboardidhash"
},
"list": {
"name": "TODO",
"id": "hashlisttodo"
},
"card": {
"shortLink": "fakeshortlinkcard7",
"idShort": 7,
"name": "Name Card 7",
"id": "hashcard7"
}
},
"type": "createCard",
"date": "2017-09-15T16:14:03.187Z",
"appCreator": null,
"limits": {},
"memberCreator": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
}
},
{
"id": "59bbfb8e4a6f8ca35be9b82a",
"idMemberCreator": "fakeidmemberhash",
"data": {
"board": {
"shortLink": "qwerty",
"name": "Test Board Name",
"id": "fakeboardidhash"
},
"list": {
"name": "TODO",
"id": "hashlisttodo"
}
},
"type": "createList",
"date": "2017-09-15T16:10:54.714Z",
"appCreator": null,
"limits": {},
"memberCreator": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
}
},
{
"id": "59bbfb88973b76e586edec5e",
"idMemberCreator": "fakeidmemberhash",
"data": {
"board": {
"shortLink": "qwerty",
"name": "Test Board Name",
"id": "fakeboardidhash"
}
},
"type": "createBoard",
"date": "2017-09-15T16:10:48.069Z",
"appCreator": null,
"limits": {},
"memberCreator": {
"id": "fakeidmemberhash",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idMemberReferrer": null,
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true
}
}
],
"cards": [
{
"id": "hashcard7",
"address": null,
"checkItemStates": null,
"closed": false,
"coordinates": null,
"creationMethod": null,
"dateLastActivity": "2021-07-10T17:01:58.633Z",
"desc": "",
"descData": null,
"dueReminder": null,
"idBoard": "fakeboardidhash",
"idLabels": [],
"idList": "hashlisttodo",
"idMembersVoted": [],
"idShort": 7,
"idAttachmentCover": null,
"locationName": null,
"manualCoverAttachment": false,
"name": "Name Card 7",
"pos": 65535,
"shortLink": "fakeshortlinkcard7",
"isTemplate": false,
"cardRole": null,
"badges": {
"attachmentsByType": {
"trello": {
"board": 0,
"card": 0
}
},
"location": false,
"votes": 0,
"viewingMemberVoted": false,
"subscribed": false,
"fogbugz": "",
"checkItems": 0,
"checkItemsChecked": 0,
"checkItemsEarliestDue": null,
"comments": 0,
"attachments": 0,
"description": false,
"due": null,
"dueComplete": false,
"start": null
},
"dueComplete": false,
"due": null,
"email": "johndoe+card7@boards.trello.com",
"idChecklists": [],
"idMembers": [],
"labels": [],
"limits": {
"attachments": {
"perCard": {
"status": "ok",
"disableAt": 1000,
"warnAt": 900
}
},
"checklists": {
"perCard": {
"status": "ok",
"disableAt": 500,
"warnAt": 450
}
},
"stickers": {
"perCard": {
"status": "ok",
"disableAt": 70,
"warnAt": 63
}
}
},
"shortUrl": "https://trello.com/c/fakeshortlinkcard7",
"start": null,
"subscribed": false,
"url": "https://trello.com/c/fakeshortlinkcard7/7-name-card-7",
"cover": {
"idAttachment": null,
"color": null,
"idUploadedBackground": null,
"size": "normal",
"brightness": "dark",
"idPlugin": null
},
"attachments": [],
"pluginData": [],
"customFieldItems": []
}
],
"labels": [
{
"id": "59bbfb881314a339999eb855",
"idBoard": "fakeboardidhash",
"name": "",
"color": "yellow"
}
],
"lists": [
{
"id": "hashlisttodo",
"name": "TODO",
"closed": false,
"pos": 65535,
"softLimit": null,
"creationMethod": null,
"idBoard": "fakeboardidhash",
"limits": {
"cards": {
"openPerList": {
"status": "ok",
"disableAt": 5000,
"warnAt": 4500
},
"totalPerList": {
"status": "ok",
"disableAt": 1000000,
"warnAt": 900000
}
}
},
"subscribed": false
}
],
"members": [
{
"id": "fakeidmemberhash",
"bio": "",
"bioData": {
"emoji": {}
},
"confirmed": true,
"memberType": "normal",
"username": "johndoe",
"activityBlocked": false,
"avatarHash": "fakeavatarhash",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"fullName": "John Doe",
"idEnterprise": null,
"idEnterprisesDeactivated": [],
"idMemberReferrer": null,
"idPremOrgsAdmin": [],
"initials": "JD",
"nonPublic": {
"fullName": "John Doe",
"initials": "JD",
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
"avatarHash": "fakeavatarhash"
},
"nonPublicAvailable": true,
"products": [],
"url": "https://trello.com/johndoe",
"status": "disconnected"
}
],
"checklists": [],
"customFields": [],
"memberships": [
{
"id": "59bbfb88973b76e586edec5d",
"idMember": "fakeidmemberhash",
"memberType": "admin",
"unconfirmed": false,
"deactivated": false
}
],
"pluginData": []
}
@@ -70,7 +70,7 @@ class BoardDatabaseTest extends \Test\TestCase {
$board->setOwner(self::TEST_USER1);
$board->setColor('000000');
$board->setLabels([]);
$created = $this->boardService->create('Test', self::TEST_USER1, '000000');
$created = $this->boardService->create('Test', self::TEST_USER1, '000000');
$id = $created->getId();
$actual = $this->boardService->find($id);
$this->assertEquals($actual->getTitle(), $board->getTitle());
@@ -0,0 +1,314 @@
<?php
namespace OCA\Deck\Service;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\Card;
/**
* @group DB
* @coversDefaultClass \OCA\Deck\Service\BoardService
*/
class TransferOwnershipTest extends \Test\TestCase {
private const TEST_USER_1 = 'test-share-user1';
private const TEST_USER_2 = 'test-user2';
private const TEST_USER_3 = 'test-user3';
private const TEST_GROUP = 'test-share-user1';
/** @var BoardService */
protected $boardService;
/** @var CardService */
protected $cardService;
/** @var StackService */
protected $stackService;
/** @var AssignmentMapper */
protected $assignmentMapper;
/** @var AssignmentService */
private $assignmentService;
/** @var Board */
private $board;
private $cards;
private $stacks;
public static function setUpBeforeClass(): void {
parent::setUpBeforeClass();
$backend = new \Test\Util\User\Dummy();
\OC_User::useBackend($backend);
\OC::$server->getUserManager()->registerBackend($backend);
$backend->createUser(self::TEST_USER_1, self::TEST_USER_1);
$backend->createUser(self::TEST_USER_2, self::TEST_USER_2);
$backend->createUser(self::TEST_USER_3, self::TEST_USER_3);
// create group
$groupBackend = new \Test\Util\Group\Dummy();
$groupBackend->createGroup(self::TEST_GROUP);
$groupBackend->addToGroup(self::TEST_USER_1, self::TEST_GROUP);
\OC::$server->getGroupManager()->addBackend($groupBackend);
}
public function setUp(): void {
parent::setUp();
\OC::$server->getUserSession()->login(self::TEST_USER_1, self::TEST_USER_1);
$this->boardService = \OC::$server->query(BoardService::class);
$this->stackService = \OC::$server->query(StackService::class);
$this->cardService = \OC::$server->query(CardService::class);
$this->assignmentService = \OC::$server->query(AssignmentService::class);
$this->assignmentMapper = \OC::$server->query(AssignmentMapper::class);
$this->createBoardWithExampleData();
}
public function createBoardWithExampleData() {
$stacks = [];
$board = $this->boardService->create('Test', self::TEST_USER_1, '000000');
$id = $board->getId();
$this->boardService->addAcl($id, Acl::PERMISSION_TYPE_GROUP, self::TEST_GROUP, true, true, true);
$this->boardService->addAcl($id, Acl::PERMISSION_TYPE_USER, self::TEST_USER_3, false, true, false);
$stacks[] = $this->stackService->create('Stack A', $id, 1);
$stacks[] = $this->stackService->create('Stack B', $id, 1);
$stacks[] = $this->stackService->create('Stack C', $id, 1);
$cards[] = $this->cardService->create('Card 1', $stacks[0]->getId(), 'text', 0, self::TEST_USER_1);
$cards[] = $this->cardService->create('Card 2', $stacks[0]->getId(), 'text', 0, self::TEST_USER_1);
$this->assignmentService->assignUser($cards[0]->getId(), self::TEST_USER_1);
$this->board = $board;
$this->cards = $cards;
$this->stacks = $stacks;
}
/**
* @covers ::transferOwnership
*/
public function testTransferBoardOwnership() {
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_2));
$board = $this->boardService->find($this->board->getId());
$boardOwner = $board->getOwner();
$this->assertEquals(self::TEST_USER_2, $boardOwner);
}
/**
* @covers ::transferOwnership
*/
public function testTransferBoardOwnershipWithData() {
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_2));
$board = $this->boardService->find($this->board->getId());
$boardOwner = $board->getOwner();
$this->assertEquals(self::TEST_USER_2, $boardOwner);
$cards = $this->cards;
$newOwnerOwnsTheCards = (bool)array_product(array_filter($cards, function (Card $card) {
$cardUpdated = $this->cardService->find($card->getId());
return $cardUpdated->getOwner() === self::TEST_USER_2;
}));
$this->assertTrue($newOwnerOwnsTheCards);
}
/**
* @covers ::transferOwnership
*/
public function testTransferACLOwnership() {
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_2, true));
$board = $this->boardService->find($this->board->getId());
$acl = $board->getAcl();
$this->assertBoardDoesNotHaveAclUser($board, self::TEST_USER_1);
}
/**
* @covers ::transferOwnership
*/
public function testTransferACLOwnershipPreserveOwner() {
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_2, false));
$board = $this->boardService->find($this->board->getId());
$acl = $board->getAcl();
$this->assertBoardHasAclUser($board, self::TEST_USER_1);
}
/**
* @covers ::transferOwnership
*/
public function testNoTransferAclOwnershipIfGroupType() {
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_2));
$board = $this->boardService->find($this->board->getId());
$acl = $board->getAcl();
$isGroupInAcl = (bool)array_filter($acl, function ($item) {
return $item->getParticipant() === self::TEST_GROUP && $item->getType() === Acl::PERMISSION_TYPE_GROUP;
});
$this->assertTrue($isGroupInAcl);
}
/**
* @covers ::transferOwnership
*/
public function testTransferCardOwnership() {
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_2, true));
$card = $this->cardService->find($this->cards[0]->getId());
$cardOwner = $card->getOwner();
$this->assertEquals(self::TEST_USER_2, $cardOwner);
}
/**
* @covers ::transferOwnership
*/
public function testTransferPreserveCardOwnership() {
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_2, false));
$card = $this->cardService->find($this->cards[0]->getId());
$cardOwner = $card->getOwner();
$this->assertEquals(self::TEST_USER_1, $cardOwner);
}
/**
* @covers ::transferOwnership
*/
public function testReassignCardToNewOwner() {
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_2, true));
$participantsUIDs = array_map(function ($user) {
return $user->getParticipant();
}, $this->assignmentMapper->findAll($this->cards[0]->getId()));
$this->assertContains(self::TEST_USER_2, $participantsUIDs);
$this->assertNotContains(self::TEST_USER_1, $participantsUIDs);
}
/**
* @covers ::transferOwnership
*/
public function testNoReassignCardToNewOwner() {
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_2, false));
$participantsUIDs = array_map(function ($user) {
return $user->getParticipant();
}, $this->assignmentMapper->findAll($this->cards[0]->getId()));
$this->assertContains(self::TEST_USER_1, $participantsUIDs);
$this->assertNotContains(self::TEST_USER_2, $participantsUIDs);
}
/**
* @covers ::transferOwnership
*/
public function testReassignCardToNewParticipantOnlyIfParticipantHasUserType() {
$this->assignmentService->assignUser($this->cards[1]->getId(), self::TEST_USER_1, Assignment::TYPE_GROUP);
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_2));
$participantsUIDs = array_map(function ($user) {
return $user->getParticipant();
}, $this->assignmentMapper->findAll($this->cards[1]->getId()));
$this->assertContains(self::TEST_USER_1, $participantsUIDs);
$this->assertNotContains(self::TEST_USER_2, $participantsUIDs);
}
/**
* @covers ::transferOwnership
*/
public function testTargetAlreadyParticipantOfBoard() {
$this->expectNotToPerformAssertions();
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_3));
}
private function assertBoardHasAclUser($board, $userId) {
$hasUser = (bool)array_filter($board->getAcl(), function ($item) use ($userId) {
return $item->getParticipant() === $userId && $item->getType() === Acl::PERMISSION_TYPE_USER;
});
self::assertTrue($hasUser, 'user ' . $userId . ' should be in the board acl list');
}
private function assertBoardDoesNotHaveAclUser($board, $userId) {
$hasUser = (bool)array_filter($board->getAcl(), function ($item) use ($userId) {
return $item->getParticipant() === $userId && $item->getType() === Acl::PERMISSION_TYPE_USER;
});
self::assertFalse($hasUser, 'user ' . $userId . ' should not be in the board acl list');
}
/**
* @covers ::transferOwnership
*/
public function testDontRemoveOldOwnerFromAcl() {
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_2));
$board = $this->boardService->find($this->board->getId());
$this->assertBoardDoesNotHaveAclUser($board, self::TEST_USER_2);
$this->assertBoardHasAclUser($board, self::TEST_USER_3);
$this->assertBoardHasAclUser($board, self::TEST_USER_1);
}
/**
* @covers ::transferOwnership
*/
public function testRemoveOldOwnerFromAclForChange() {
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_2, true));
$board = $this->boardService->find($this->board->getId());
$this->assertBoardDoesNotHaveAclUser($board, self::TEST_USER_2);
$this->assertBoardHasAclUser($board, self::TEST_USER_3);
$this->assertBoardDoesNotHaveAclUser($board, self::TEST_USER_1);
}
/**
* @covers ::transferOwnership
*/
public function testMergePermissions() {
$this->boardService->addAcl($this->board->getId(), Acl::PERMISSION_TYPE_USER, self::TEST_USER_2, true, false, true);
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_3));
$board = $this->boardService->find($this->board->getId());
$acl = $board->getAcl();
$isMerged = (bool)array_filter($acl, function ($item) {
return $item->getParticipant() === self::TEST_USER_1
&& $item->getType() === Acl::PERMISSION_TYPE_USER
&& $item->getPermission(Acl::PERMISSION_EDIT)
&& $item->getPermission(Acl::PERMISSION_SHARE)
&& $item->getPermission(Acl::PERMISSION_MANAGE);
});
$this->assertTrue($isMerged);
}
/**
* @covers ::transferOwnership
*/
public function testTargetAlreadyParticipantOfCard() {
$this->expectNotToPerformAssertions();
$this->assignmentService->assignUser($this->cards[0]->getId(), self::TEST_USER_3, Assignment::TYPE_USER);
iterator_to_array($this->boardService->transferOwnership(self::TEST_USER_1, self::TEST_USER_3));
}
/**
* @covers ::transferOwnership
*/
public function testTransferSingleBoardAssignment() {
// Arrange separate board next to the one being transferred
$board = $this->boardService->create('Test 2', self::TEST_USER_1, '000000');
$id = $board->getId();
$this->boardService->addAcl($id, Acl::PERMISSION_TYPE_USER, self::TEST_USER_1, true, true, true);
$this->boardService->addAcl($id, Acl::PERMISSION_TYPE_GROUP, self::TEST_GROUP, true, true, true);
$this->boardService->addAcl($id, Acl::PERMISSION_TYPE_USER, self::TEST_USER_3, false, true, false);
$stacks[] = $this->stackService->create('Stack A', $id, 1);
$stacks[] = $this->stackService->create('Stack B', $id, 1);
$stacks[] = $this->stackService->create('Stack C', $id, 1);
$cards[] = $this->cardService->create('Card 1', $stacks[0]->getId(), 'text', 0, self::TEST_USER_1);
$cards[] = $this->cardService->create('Card 2', $stacks[0]->getId(), 'text', 0, self::TEST_USER_1);
$this->assignmentService->assignUser($cards[0]->getId(), self::TEST_USER_1);
// Act
$this->boardService->transferBoardOwnership($this->board->getId(), self::TEST_USER_2, true);
// Assert that the selected board was transferred
$card = $this->cardService->find($this->cards[0]->getId());
$this->assertEquals(self::TEST_USER_2, $card->getOwner());
$participantsUIDs = array_map(function ($assignment) {
return $assignment->getParticipant();
}, $this->assignmentMapper->findAll($this->cards[0]->getId()));
$this->assertContains(self::TEST_USER_2, $participantsUIDs);
$this->assertNotContains(self::TEST_USER_1, $participantsUIDs);
// Assert that other board remained unchanged
$card = $this->cardService->find($cards[0]->getId());
$this->assertEquals(self::TEST_USER_1, $card->getOwner());
$participantsUIDs = array_map(function ($assignment) {
return $assignment->getParticipant();
}, $this->assignmentMapper->findAll($cards[0]->getId()));
$this->assertContains(self::TEST_USER_1, $participantsUIDs);
$this->assertNotContains(self::TEST_USER_2, $participantsUIDs);
}
public function tearDown(): void {
$this->boardService->deleteForce($this->board->getId());
parent::tearDown();
}
}
+1 -1
View File
@@ -26,7 +26,7 @@ composer dump-autoload
if [ -z "$EXECUTOR_NUMBER" ]; then
EXECUTOR_NUMBER=0
fi
PORT=$((8080 + $EXECUTOR_NUMBER))
PORT=$((9090 + $EXECUTOR_NUMBER))
echo $PORT
php -S localhost:$PORT -t $OC_PATH &
PHPPID=$!
+172 -42
View File
@@ -26,7 +26,6 @@ namespace OCA\Deck\Activity;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Db\AttachmentMapper;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\Card;
@@ -41,7 +40,7 @@ use OCP\IL10N;
use OCP\IUser;
use OCP\L10N\IFactory;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use PHPUnit\Framework\MockObject\MockObject;
class ActivityManagerTest extends TestCase {
@@ -57,8 +56,6 @@ class ActivityManagerTest extends TestCase {
private $cardMapper;
/** @var StackMapper|MockObject */
private $stackMapper;
/** @var AttachmentMapper|MockObject */
private $attachmentMapper;
/** @var AclMapper|MockObject */
private $aclMapper;
/** @var IFactory|MockObject */
@@ -74,7 +71,6 @@ class ActivityManagerTest extends TestCase {
$this->boardMapper = $this->createMock(BoardMapper::class);
$this->cardMapper = $this->createMock(CardMapper::class);
$this->stackMapper = $this->createMock(StackMapper::class);
$this->attachmentMapper = $this->createMock(AttachmentMapper::class);
$this->aclMapper = $this->createMock(AclMapper::class);
$this->l10nFactory = $this->createMock(IFactory::class);
$this->l10n = $this->createMock(IL10N::class);
@@ -84,7 +80,6 @@ class ActivityManagerTest extends TestCase {
$this->boardMapper,
$this->cardMapper,
$this->stackMapper,
$this->attachmentMapper,
$this->aclMapper,
$this->l10nFactory,
$this->userId
@@ -93,7 +88,7 @@ class ActivityManagerTest extends TestCase {
public function testGetActivityFormatOwn() {
$managerClass = new \ReflectionClass(ActivityManager::class);
$this->l10n->expects($this->any())
$this->l10n->expects(self::any())
->method('t')
->will($this->returnCallback(function ($s) {
return $s;
@@ -122,22 +117,29 @@ class ActivityManagerTest extends TestCase {
}
}
private function expectEventCreation($subject, $subjectParams) {
$event = $this->createMock(IEvent::class);
$this->manager->expects(self::once())
->method('generateEvent')
->willReturn($event);
$event->expects(self::once())->method('setApp')->willReturn($event);
$event->expects(self::once())->method('setType')->willReturn($event);
$event->expects(self::once())->method('setAuthor')->willReturn($event);
$event->expects(self::once())->method('setObject')->willReturn($event);
$event->expects(self::once())->method('setSubject')->with($subject, $subjectParams)->willReturn($event);
$event->expects(self::once())->method('setTimestamp')->willReturn($event);
return $event;
}
public function testCreateEvent() {
$board = new Board();
$board->setTitle('');
$this->boardMapper->expects($this->once())
$this->boardMapper->expects(self::once())
->method('find')
->willReturn($board);
$event = $this->createMock(IEvent::class);
$this->manager->expects($this->once())
->method('generateEvent')
->willReturn($event);
$event->expects($this->once())->method('setApp')->willReturn($event);
$event->expects($this->once())->method('setType')->willReturn($event);
$event->expects($this->once())->method('setAuthor')->willReturn($event);
$event->expects($this->once())->method('setObject')->willReturn($event);
$event->expects($this->once())->method('setSubject')->willReturn($event);
$event->expects($this->once())->method('setTimestamp')->willReturn($event);
$event = $this->expectEventCreation(ActivityManager::SUBJECT_BOARD_CREATE, [
'author' => 'admin'
]);
$actual = $this->invokePrivate($this->activityManager, 'createEvent', [
ActivityManager::DECK_OBJECT_BOARD,
$board,
@@ -146,6 +148,133 @@ class ActivityManagerTest extends TestCase {
$this->assertEquals($event, $actual);
}
public function testCreateEventDescription() {
$board = new Board();
$board->setTitle('');
$this->boardMapper->expects(self::once())
->method('find')
->willReturn($board);
$card = Card::fromRow([
'id' => 123,
'title' => 'My card',
'description' => str_repeat('A', 1000),
]);
$this->cardMapper->expects(self::any())
->method('find')
->willReturn($card);
$stack = Stack::fromRow([]);
$this->stackMapper->expects(self::any())
->method('find')
->willReturn($stack);
$expectedCard = $card->jsonSerialize();
unset($expectedCard['description']);
$event = $this->expectEventCreation(ActivityManager::SUBJECT_CARD_UPDATE_DESCRIPTION, [
'card' => $expectedCard,
'stack' => $stack->jsonSerialize(),
'board' => $board->jsonSerialize(),
'diff' => true,
'author' => 'admin',
'after' => str_repeat('C', 2000),
]);
$actual = $this->invokePrivate($this->activityManager, 'createEvent', [
ActivityManager::DECK_OBJECT_CARD,
$card,
ActivityManager::SUBJECT_CARD_UPDATE_DESCRIPTION,
[
'before' => str_repeat('B', 2000),
'after' => str_repeat('C', 2000)
],
]);
$this->assertEquals($event, $actual);
}
public function testCreateEventLongDescription() {
$board = new Board();
$board->setTitle('');
$this->boardMapper->expects(self::once())
->method('find')
->willReturn($board);
$card = new Card();
$card->setDescription(str_repeat('A', 5000));
$card->setTitle('My card');
$card->setId(123);
$this->cardMapper->expects(self::any())
->method('find')
->willReturn($card);
$stack = new Stack();
$this->stackMapper->expects(self::any())
->method('find')
->willReturn($stack);
$expectedCard = $card->jsonSerialize();
unset($expectedCard['description']);
$event = $this->expectEventCreation(ActivityManager::SUBJECT_CARD_UPDATE_DESCRIPTION, [
'card' => $expectedCard,
'stack' => $stack->jsonSerialize(),
'board' => $board->jsonSerialize(),
'diff' => true,
'author' => 'admin',
'after' => str_repeat('C', 2000) . '...',
]);
$actual = $this->invokePrivate($this->activityManager, 'createEvent', [
ActivityManager::DECK_OBJECT_CARD,
$card,
ActivityManager::SUBJECT_CARD_UPDATE_DESCRIPTION,
[
'before' => str_repeat('B', 5000),
'after' => str_repeat('C', 5000)
],
]);
$this->assertEquals($event, $actual);
}
public function testCreateEventLabel() {
$board = Board::fromRow([
'title' => 'My board'
]);
$this->boardMapper->expects(self::once())
->method('find')
->willReturn($board);
$card = Card::fromParams([]);
$card->setDescription(str_repeat('A', 5000));
$card->setTitle('My card');
$card->setId(123);
$this->cardMapper->expects(self::any())
->method('find')
->willReturn($card);
$stack = Stack::fromParams([]);
$this->stackMapper->expects(self::any())
->method('find')
->willReturn($stack);
$event = $this->expectEventCreation(ActivityManager::SUBJECT_CARD_UPDATE_TITLE, [
'card' => [
'id' => 123,
'title' => 'My card',
'archived' => false,
],
'stack' => $stack,
'board' => $board,
'author' => 'admin',
]);
$actual = $this->invokePrivate($this->activityManager, 'createEvent', [
ActivityManager::DECK_OBJECT_CARD,
$card,
ActivityManager::SUBJECT_CARD_UPDATE_TITLE
]);
$this->assertEquals($event, $actual);
}
public function dataSendToUsers() {
return [
[ActivityManager::DECK_OBJECT_BOARD],
@@ -155,7 +284,7 @@ class ActivityManagerTest extends TestCase {
private function mockUser($uid) {
$user = $this->createMock(IUser::class);
$user->expects($this->any())
$user->expects(self::any())
->method('getUID')
->willReturn($uid);
return $user;
@@ -169,18 +298,21 @@ class ActivityManagerTest extends TestCase {
$this->mockUser('user2'),
];
$event = $this->createMock(IEvent::class);
$event->expects($this->at(0))
$event->expects(self::once())
->method('getObjectType')
->willReturn($objectType);
$event->expects($this->at(0))
$event->expects(self::once())
->method('getObjectId')
->willReturn(1);
$event->expects($this->at(2))
$event->expects(self::exactly(2))
->method('setAffectedUser')
->with('user1');
$event->expects($this->at(3))
->method('setAffectedUser')
->with('user2');
->withConsecutive(
['user1'],
['user2'],
)
->willReturnSelf();
$mapper = null;
switch ($objectType) {
case ActivityManager::DECK_OBJECT_BOARD:
@@ -190,16 +322,14 @@ class ActivityManagerTest extends TestCase {
$mapper = $this->cardMapper;
break;
}
$mapper->expects($this->once())
$mapper->expects(self::once())
->method('findBoardId')
->willReturn(123);
$this->permissionService->expects($this->once())
$this->permissionService->expects(self::once())
->method('findUsers')
->willReturn($users);
$this->manager->expects($this->at(0))
->method('publish')
->with($event);
$this->manager->expects($this->at(1))
$this->manager->expects(self::exactly(2))
->method('publish')
->with($event);
$this->invokePrivate($this->activityManager, 'sendToUsers', [$event]);
@@ -243,14 +373,14 @@ class ActivityManagerTest extends TestCase {
$card->setId(3);
$expected = null;
if ($objectType === ActivityManager::DECK_OBJECT_BOARD) {
$this->boardMapper->expects($this->once())
$this->boardMapper->expects(self::once())
->method('find')
->with(1)
->willReturn($board);
$expected = $board;
}
if ($objectType === ActivityManager::DECK_OBJECT_CARD) {
$this->cardMapper->expects($this->once())
$this->cardMapper->expects(self::once())
->method('find')
->with(3)
->willReturn($card);
@@ -266,11 +396,11 @@ class ActivityManagerTest extends TestCase {
$stack->setBoardId(999);
$board = new Board();
$board->setId(999);
$this->stackMapper->expects($this->once())
$this->stackMapper->expects(self::once())
->method('find')
->with(123)
->willReturn($stack);
$this->boardMapper->expects($this->once())->method('find')
$this->boardMapper->expects(self::once())->method('find')
->with(999)
->willReturn($board);
$this->assertEquals([
@@ -289,15 +419,15 @@ class ActivityManagerTest extends TestCase {
$stack->setBoardId(999);
$board = new Board();
$board->setId(999);
$this->cardMapper->expects($this->once())
$this->cardMapper->expects(self::once())
->method('find')
->with(555)
->willReturn($card);
$this->stackMapper->expects($this->once())
$this->stackMapper->expects(self::once())
->method('find')
->with(123)
->willReturn($stack);
$this->boardMapper->expects($this->once())->method('find')
$this->boardMapper->expects(self::once())->method('find')
->with(999)
->willReturn($board);
$this->assertEquals([
@@ -323,15 +453,15 @@ class ActivityManagerTest extends TestCase {
$stack->setBoardId(999);
$board = new Board();
$board->setId(999);
$this->cardMapper->expects($this->once())
$this->cardMapper->expects(self::once())
->method('find')
->with(555)
->willReturn($card);
$this->stackMapper->expects($this->once())
$this->stackMapper->expects(self::once())
->method('find')
->with(123)
->willReturn($stack);
$this->boardMapper->expects($this->once())->method('find')
$this->boardMapper->expects(self::once())->method('find')
->with(999)
->willReturn($board);
$this->assertEquals([
+77
View File
@@ -0,0 +1,77 @@
<?php
/**
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
*
* @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\Command;
use OCA\Deck\Service\Importer\BoardImportCommandService;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class BoardImportTest extends \Test\TestCase {
/** @var BoardImportCommandService */
private $boardImportCommandService;
/** @var BoardImport */
private $boardImport;
public function setUp(): void {
parent::setUp();
$this->boardImportCommandService = $this->createMock(BoardImportCommandService::class);
$this->boardImport = new BoardImport(
$this->boardImportCommandService
);
$questionHelper = new QuestionHelper();
$this->boardImport->setHelperSet(
new HelperSet([
$questionHelper
])
);
}
public function testExecuteWithSuccess() {
$input = $this->createMock(InputInterface::class);
$input
->method('getOption')
->withConsecutive(
['system'],
['config']
)
->will($this->returnValueMap([
['system', 'trelloJson'],
['config', null]
]));
$output = $this->createMock(OutputInterface::class);
$output
->expects($this->once())
->method('writeLn')
->with('Done!');
$actual = $this->invokePrivate($this->boardImport, 'interact', [$input, $output]);
$this->assertNull($actual);
$actual = $this->invokePrivate($this->boardImport, 'execute', [$input, $output]);
$this->assertEquals(0, $actual);
}
}
+7 -11
View File
@@ -66,18 +66,14 @@ class DeleteCronTest extends \Test\TestCase {
$this->boardMapper->expects($this->once())
->method('findToDelete')
->willReturn($boards);
$this->boardMapper->expects($this->at(1))
$this->boardMapper->expects($this->exactly(count($boards)))
->method('delete')
->with($boards[0]);
$this->boardMapper->expects($this->at(2))
->method('delete')
->with($boards[1]);
$this->boardMapper->expects($this->at(3))
->method('delete')
->with($boards[2]);
$this->boardMapper->expects($this->at(4))
->method('delete')
->with($boards[3]);
->withConsecutive(
[$boards[0]],
[$boards[1]],
[$boards[2]],
[$boards[3]]
);
$attachment = new Attachment();
$attachment->setType('deck_file');
@@ -54,10 +54,7 @@ class ScheduledNoificationsTest extends \Test\TestCase {
$this->cardMapper->expects($this->once())
->method('findOverdue')
->willReturn($cards);
$this->notificationHelper->expects($this->at(0))
->method('sendCardDuedate')
->with($c1);
$this->notificationHelper->expects($this->at(1))
$this->notificationHelper->expects($this->exactly(2))
->method('sendCardDuedate')
->with($c1);
$this->scheduledNotifications->run(null);
+4 -4
View File
@@ -67,10 +67,10 @@ class AclMapperTest extends MapperTestUtility {
$this->boardMapper->insert($this->getBoard('MyBoard 3', 'user3'))
];
$this->acls = [
$this->aclMapper->insert($this->getAcl('user','user1', false, false, false, $this->boards[1]->getId())),
$this->aclMapper->insert($this->getAcl('user','user2', true, false, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user','user3', true, true, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user','user1', false, false, false, $this->boards[2]->getId()))
$this->aclMapper->insert($this->getAcl('user', 'user1', false, false, false, $this->boards[1]->getId())),
$this->aclMapper->insert($this->getAcl('user', 'user2', true, false, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user', 'user3', true, true, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user', 'user1', false, false, false, $this->boards[2]->getId()))
];
foreach ($this->acls as $acl) {
+4 -4
View File
@@ -76,10 +76,10 @@ class BoardMapperTest extends MapperTestUtility {
$this->boardMapper->insert($this->getBoard('MyBoard 3', 'user3'))
];
$this->acls = [
$this->aclMapper->insert($this->getAcl('user','user1', false, false, false, $this->boards[1]->getId())),
$this->aclMapper->insert($this->getAcl('user','user2', true, false, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user','user3', true, true, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user','user1', false, false, false, $this->boards[2]->getId()))
$this->aclMapper->insert($this->getAcl('user', 'user1', false, false, false, $this->boards[1]->getId())),
$this->aclMapper->insert($this->getAcl('user', 'user2', true, false, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user', 'user3', true, true, false, $this->boards[0]->getId())),
$this->aclMapper->insert($this->getAcl('user', 'user1', false, false, false, $this->boards[2]->getId()))
];
foreach ($this->acls as $acl) {
@@ -114,17 +114,18 @@ class NotificationHelperTest extends \Test\TestCase {
}
public function testSendCardDuedate() {
$this->config->expects($this->at(0))
$param1 = ['foo', 'bar', 'asd'];
$param2 = 'deck';
$param3 = 'board:234:notify-due';
$DUE_ASSIGNED = ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED;
$this->config->expects($this->exactly(3))
->method('getUserValue')
->with('foo', 'deck', 'board:234:notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED)
->willReturn(ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ALL);
$this->config->expects($this->at(1))
->method('getUserValue')
->with('bar', 'deck', 'board:234:notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED)
->willReturn(ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ALL);
$this->config->expects($this->at(2))
->method('getUserValue')
->with('asd', 'deck', 'board:234:notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED)
->withConsecutive(
[$param1[0], $param2, $param3, $DUE_ASSIGNED],
[$param1[1], $param2, $param3, $DUE_ASSIGNED],
[$param1[2], $param2, $param3, $DUE_ASSIGNED],
)
->willReturn(ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ALL);
$card = Card::fromParams([
@@ -180,24 +181,12 @@ class NotificationHelperTest extends \Test\TestCase {
$n3->expects($this->once())->method('setSubject')->with('card-overdue', ['MyCardTitle', 'MyBoardTitle'])->willReturn($n3);
$n3->expects($this->once())->method('setDateTime')->willReturn($n3);
$this->notificationManager->expects($this->at(0))
$this->notificationManager->expects($this->exactly(3))
->method('createNotification')
->willReturn($n1);
$this->notificationManager->expects($this->at(1))
->willReturnOnConsecutiveCalls($n1, $n2, $n3);
$this->notificationManager->expects($this->exactly(3))
->method('notify')
->with($n1);
$this->notificationManager->expects($this->at(2))
->method('createNotification')
->willReturn($n2);
$this->notificationManager->expects($this->at(3))
->method('notify')
->with($n2);
$this->notificationManager->expects($this->at(4))
->method('createNotification')
->willReturn($n3);
$this->notificationManager->expects($this->at(5))
->method('notify')
->with($n3);
->withConsecutive([$n1], [$n2], [$n3]);
$this->cardMapper->expects($this->once())
->method('markNotified')
@@ -207,18 +196,19 @@ class NotificationHelperTest extends \Test\TestCase {
}
public function testSendCardDuedateAssigned() {
$this->config->expects($this->at(0))
$param1 = ['foo', 'bar', 'asd'];
$param2 = 'deck';
$param3 = 'board:234:notify-due';
$DUE_ASSIGNED = ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED;
$this->config->expects($this->exactly(3))
->method('getUserValue')
->with('foo', 'deck', 'board:234:notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED)
->willReturn(ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED);
$this->config->expects($this->at(1))
->method('getUserValue')
->with('bar', 'deck', 'board:234:notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED)
->willReturn(ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED);
$this->config->expects($this->at(2))
->method('getUserValue')
->with('asd', 'deck', 'board:234:notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED)
->willReturn(ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED);
->withConsecutive(
[$param1[0], $param2, $param3, $DUE_ASSIGNED],
[$param1[1], $param2, $param3, $DUE_ASSIGNED],
[$param1[2], $param2, $param3, $DUE_ASSIGNED]
)
->willReturn($DUE_ASSIGNED);
$users = [
new DummyUser('foo'), new DummyUser('bar'), new DummyUser('asd')
@@ -278,24 +268,12 @@ class NotificationHelperTest extends \Test\TestCase {
$n3->expects($this->once())->method('setSubject')->with('card-overdue', ['MyCardTitle', 'MyBoardTitle'])->willReturn($n3);
$n3->expects($this->once())->method('setDateTime')->willReturn($n3);
$this->notificationManager->expects($this->at(0))
$this->notificationManager->expects($this->exactly(3))
->method('createNotification')
->willReturn($n1);
$this->notificationManager->expects($this->at(1))
->willReturnOnConsecutiveCalls($n1, $n2, $n3);
$this->notificationManager->expects($this->exactly(3))
->method('notify')
->with($n1);
$this->notificationManager->expects($this->at(2))
->method('createNotification')
->willReturn($n2);
$this->notificationManager->expects($this->at(3))
->method('notify')
->with($n2);
$this->notificationManager->expects($this->at(4))
->method('createNotification')
->willReturn($n3);
$this->notificationManager->expects($this->at(5))
->method('notify')
->with($n3);
->withConsecutive([$n1], [$n2], [$n3]);
$this->cardMapper->expects($this->once())
->method('markNotified')
@@ -306,18 +284,20 @@ class NotificationHelperTest extends \Test\TestCase {
public function testSendCardDuedateNever() {
$this->config->expects($this->at(0))
$param1 = ['foo', 'bar', 'asd'];
$param2 = 'deck';
$param3 = 'board:234:notify-due';
$DUE_ASSIGNED = ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED;
$DUE_OFF = ConfigService::SETTING_BOARD_NOTIFICATION_DUE_OFF;
$this->config->expects($this->exactly(3))
->method('getUserValue')
->with('foo', 'deck', 'board:234:notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED)
->willReturn(ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED);
$this->config->expects($this->at(1))
->method('getUserValue')
->with('bar', 'deck', 'board:234:notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED)
->willReturn(ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED);
$this->config->expects($this->at(2))
->method('getUserValue')
->with('asd', 'deck', 'board:234:notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED)
->willReturn(ConfigService::SETTING_BOARD_NOTIFICATION_DUE_OFF);
->withConsecutive(
[$param1[0], $param2, $param3, $DUE_ASSIGNED],
[$param1[1], $param2, $param3, $DUE_ASSIGNED],
[$param1[2], $param2, $param3, $DUE_ASSIGNED]
)
->willReturnOnConsecutiveCalls($DUE_ASSIGNED, $DUE_ASSIGNED, $DUE_OFF);
$users = [
new DummyUser('foo'), new DummyUser('bar'), new DummyUser('asd')
@@ -370,18 +350,12 @@ class NotificationHelperTest extends \Test\TestCase {
$n2->expects($this->once())->method('setSubject')->with('card-overdue', ['MyCardTitle', 'MyBoardTitle'])->willReturn($n2);
$n2->expects($this->once())->method('setDateTime')->willReturn($n2);
$this->notificationManager->expects($this->at(0))
$this->notificationManager->expects($this->exactly(2))
->method('createNotification')
->willReturn($n1);
$this->notificationManager->expects($this->at(1))
->willReturnOnConsecutiveCalls($n1, $n2);
$this->notificationManager->expects($this->exactly(2))
->method('notify')
->with($n1);
$this->notificationManager->expects($this->at(2))
->method('createNotification')
->willReturn($n2);
$this->notificationManager->expects($this->at(3))
->method('notify')
->with($n2);
->withConsecutive([$n1], [$n2]);
$this->cardMapper->expects($this->once())
->method('markNotified')
@@ -423,10 +397,10 @@ class NotificationHelperTest extends \Test\TestCase {
$notification->expects($this->once())->method('setSubject')->with('card-assigned', ['MyCardTitle', 'MyBoardTitle', 'admin'])->willReturn($notification);
$notification->expects($this->once())->method('setDateTime')->willReturn($notification);
$this->notificationManager->expects($this->at(0))
$this->notificationManager->expects($this->once())
->method('createNotification')
->willReturn($notification);
$this->notificationManager->expects($this->at(1))
$this->notificationManager->expects($this->once())
->method('notify')
->with($notification);
@@ -451,10 +425,10 @@ class NotificationHelperTest extends \Test\TestCase {
$notification->expects($this->once())->method('setSubject')->with('board-shared', ['MyBoardTitle', 'admin'])->willReturn($notification);
$notification->expects($this->once())->method('setDateTime')->willReturn($notification);
$this->notificationManager->expects($this->at(0))
$this->notificationManager->expects($this->once())
->method('createNotification')
->willReturn($notification);
$this->notificationManager->expects($this->at(1))
$this->notificationManager->expects($this->once())
->method('notify')
->with($notification);
@@ -490,10 +464,10 @@ class NotificationHelperTest extends \Test\TestCase {
$notification->expects($this->once())->method('setSubject')->with('board-shared', ['MyBoardTitle', 'admin'])->willReturn($notification);
$notification->expects($this->once())->method('setDateTime')->willReturn($notification);
$this->notificationManager->expects($this->at(0))
$this->notificationManager->expects($this->once())
->method('createNotification')
->willReturn($notification);
$this->notificationManager->expects($this->at(1))
$this->notificationManager->expects($this->once())
->method('notify')
->with($notification);
@@ -540,19 +514,12 @@ class NotificationHelperTest extends \Test\TestCase {
$notification2->expects($this->once())->method('setSubject')->with('card-comment-mentioned', ['MyCard', 1, 'admin'])->willReturn($notification2);
$notification2->expects($this->once())->method('setDateTime')->willReturn($notification2);
$this->notificationManager->expects($this->at(0))
$this->notificationManager->expects($this->exactly(2))
->method('createNotification')
->willReturn($notification1);
$this->notificationManager->expects($this->at(1))
->willReturnOnConsecutiveCalls($notification1, $notification2);
$this->notificationManager->expects($this->exactly(2))
->method('notify')
->with($notification1);
$this->notificationManager->expects($this->at(2))
->method('createNotification')
->willReturn($notification2);
$this->notificationManager->expects($this->at(3))
->method('notify')
->with($notification2);
->withConsecutive([$notification1], [$notification2]);
$this->notificationHelper->sendMention($comment);
}
+65 -27
View File
@@ -110,8 +110,16 @@ class AttachmentServiceTest extends TestCase {
$this->cache = $this->createMock(ICache::class);
$this->cacheFactory->expects($this->any())->method('createDistributed')->willReturn($this->cache);
$this->appContainer->expects($this->at(0))->method('query')->with(FileService::class)->willReturn($this->attachmentServiceImpl);
$this->appContainer->expects($this->at(1))->method('query')->with(FilesAppService::class)->willReturn($this->filesAppServiceImpl);
$this->appContainer->expects($this->exactly(2))
->method('query')
->withConsecutive(
[FileService::class],
[FilesAppService::class]
)
->willReturnOnConsecutiveCalls(
$this->attachmentServiceImpl,
$this->filesAppServiceImpl
);
$this->application->expects($this->any())
->method('getContainer')
@@ -129,9 +137,18 @@ class AttachmentServiceTest extends TestCase {
$fileServiceMock = $this->createMock(FileService::class);
$fileAppServiceMock = $this->createMock(FilesAppService::class);
$appContainer->expects($this->at(0))->method('query')->with(FileService::class)->willReturn($fileServiceMock);
$appContainer->expects($this->at(1))->method('query')->with(FilesAppService::class)->willReturn($fileAppServiceMock);
$appContainer->expects($this->at(2))->method('query')->with(MyAttachmentService::class)->willReturn(new MyAttachmentService());
$appContainer->expects($this->exactly(3))
->method('query')
->withConsecutive(
[FileService::class],
[FilesAppService::class],
[MyAttachmentService::class]
)
->willReturnOnConsecutiveCalls(
$fileServiceMock,
$fileAppServiceMock,
new MyAttachmentService()
);
$application->expects($this->any())
->method('getContainer')
@@ -148,12 +165,24 @@ class AttachmentServiceTest extends TestCase {
$appContainer = $this->createMock(IAppContainer::class);
$fileServiceMock = $this->createMock(FileService::class);
$fileAppServiceMock = $this->createMock(FilesAppService::class);
$appContainer->expects($this->at(0))->method('query')->with(FileService::class)->willReturn($fileServiceMock);
$appContainer->expects($this->at(1))->method('query')->with(FilesAppService::class)->willReturn($fileAppServiceMock);
$appContainer->expects($this->at(2))->method('query')->with(MyAttachmentService::class)->willReturn(new MyAttachmentService());
$appContainer->expects($this->exactly(3))
->method('query')
->withConsecutive(
[FileService::class],
[FilesAppService::class],
[MyAttachmentService::class]
)
->willReturnOnConsecutiveCalls(
$fileServiceMock,
$fileAppServiceMock,
new MyAttachmentService()
);
$application->expects($this->any())
->method('getContainer')
->willReturn($appContainer);
$attachmentService = new AttachmentService($this->attachmentMapper, $this->cardMapper, $this->changeHelper, $this->permissionService, $application, $this->cacheFactory, $this->userId, $this->l10n, $this->activityManager);
$attachmentService->registerAttachmentService('custom', MyAttachmentService::class);
$attachmentService->getService('deck_file_invalid');
@@ -176,35 +205,40 @@ class AttachmentServiceTest extends TestCase {
public function testFindAll() {
$this->mockPermission(Acl::PERMISSION_READ);
$attachments = [
$this->createAttachment('deck_file','file1'),
$this->createAttachment('deck_file','file2'),
$this->createAttachment('deck_file_invalid','file3'),
$this->createAttachment('deck_file', 'file1'),
$this->createAttachment('deck_file', 'file2'),
$this->createAttachment('deck_file_invalid', 'file3'),
];
$this->attachmentMapper->expects($this->once())
->method('findAll')
->with(123)
->willReturn($attachments);
$this->attachmentServiceImpl->expects($this->at(0))
$this->attachmentServiceImpl->expects($this->exactly(2))
->method('extendData')
->with($attachments[0]);
$this->attachmentServiceImpl->expects($this->at(1))
->method('extendData')
->with($attachments[1]);
->withConsecutive(
[$attachments[0]],
[$attachments[1]],
)
->willReturnOnConsecutiveCalls(
$attachments[0],
$attachments[1],
);
$this->assertEquals($attachments, $this->attachmentService->findAll(123, false));
}
public function testFindAllWithDeleted() {
$this->mockPermission(Acl::PERMISSION_READ);
$attachments = [
$this->createAttachment('deck_file','file1'),
$this->createAttachment('deck_file','file2'),
$this->createAttachment('deck_file_invalid','file3'),
$this->createAttachment('deck_file', 'file1'),
$this->createAttachment('deck_file', 'file2'),
$this->createAttachment('deck_file_invalid', 'file3'),
];
$attachmentsDeleted = [
$this->createAttachment('deck_file','file4'),
$this->createAttachment('deck_file','file5'),
$this->createAttachment('deck_file_invalid','file6'),
$this->createAttachment('deck_file', 'file4'),
$this->createAttachment('deck_file', 'file5'),
$this->createAttachment('deck_file_invalid', 'file6'),
];
$this->attachmentMapper->expects($this->once())
->method('findAll')
@@ -215,12 +249,15 @@ class AttachmentServiceTest extends TestCase {
->with(123, false)
->willReturn($attachmentsDeleted);
$this->attachmentServiceImpl->expects($this->at(0))
$this->attachmentServiceImpl->expects($this->exactly(4))
->method('extendData')
->with($attachments[0]);
$this->attachmentServiceImpl->expects($this->at(1))
->method('extendData')
->with($attachments[1]);
->withConsecutive(
[$attachments[0]],
[$attachments[1]],
[$attachmentsDeleted[0]],
[$attachmentsDeleted[1]]
);
$this->assertEquals(array_merge($attachments, $attachmentsDeleted), $this->attachmentService->findAll(123, true));
}
@@ -396,5 +433,6 @@ class AttachmentServiceTest extends TestCase {
->method('allowUndo')
->willReturn(false);
$actual = $this->attachmentService->restore(1, 1);
$this->assertEquals($expected, $actual);
}
}
+41 -17
View File
@@ -31,6 +31,7 @@ use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\ChangeHelper;
use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Db\StackMapper;
@@ -58,6 +59,8 @@ class BoardServiceTest extends TestCase {
private $boardMapper;
/** @var StackMapper */
private $stackMapper;
/** @var CardMapper */
private $cardMapper;
/** @var PermissionService */
private $permissionService;
/** @var NotificationHelper */
@@ -85,6 +88,7 @@ class BoardServiceTest extends TestCase {
$this->boardMapper = $this->createMock(BoardMapper::class);
$this->stackMapper = $this->createMock(StackMapper::class);
$this->config = $this->createMock(IConfig::class);
$this->cardMapper = $this->createMock(CardMapper::class);
$this->labelMapper = $this->createMock(LabelMapper::class);
$this->permissionService = $this->createMock(PermissionService::class);
$this->notificationHelper = $this->createMock(NotificationHelper::class);
@@ -106,6 +110,7 @@ class BoardServiceTest extends TestCase {
$this->permissionService,
$this->notificationHelper,
$this->assignedUsersMapper,
$this->cardMapper,
$this->userManager,
$this->groupManager,
$this->activityManager,
@@ -149,7 +154,7 @@ class BoardServiceTest extends TestCase {
->method('find')
->with(1)
->willReturn($b1);
$this->permissionService->expects($this->once())
$this->permissionService->expects($this->any())
->method('findUsers')
->willReturn([
'admin' => 'admin',
@@ -253,6 +258,11 @@ class BoardServiceTest extends TestCase {
->method('insert')
->with($acl)
->willReturn($acl);
$this->permissionService->expects($this->any())
->method('findUsers')
->willReturn([
'admin' => 'admin',
]);
$this->assertEquals($acl, $this->service->addAcl(
123, 'user', 'admin', true, true, true
));
@@ -295,30 +305,39 @@ class BoardServiceTest extends TestCase {
$existingAcl->setPermissionEdit($currentUserAcl[0]);
$existingAcl->setPermissionShare($currentUserAcl[1]);
$existingAcl->setPermissionManage($currentUserAcl[2]);
$this->permissionService->expects($this->at(0))
->method('checkPermission')
->with($this->boardMapper, 123, Acl::PERMISSION_SHARE, null);
if ($currentUserAcl[2]) {
$this->permissionService->expects($this->at(1))
$this->permissionService->expects($this->exactly(2))
->method('checkPermission')
->with($this->boardMapper, 123, Acl::PERMISSION_MANAGE, null);
->withConsecutive(
[$this->boardMapper, 123, Acl::PERMISSION_SHARE, null],
[$this->boardMapper, 123, Acl::PERMISSION_MANAGE, null]
);
} else {
$this->aclMapper->expects($this->once())
->method('findAll')
->willReturn([$existingAcl]);
$this->permissionService->expects($this->at(1))
$this->permissionService->expects($this->exactly(2))
->method('checkPermission')
->with($this->boardMapper, 123, Acl::PERMISSION_MANAGE, null)
->willThrowException(new NoPermissionException('No permission'));
$this->permissionService->expects($this->at(2))
->withConsecutive(
[$this->boardMapper, 123, Acl::PERMISSION_SHARE, null],
[$this->boardMapper, 123, Acl::PERMISSION_MANAGE, null]
)
->will(
$this->onConsecutiveCalls(
true,
$this->throwException(new NoPermissionException('No permission'))
)
);
$this->permissionService->expects($this->exactly(3))
->method('userCan')
->willReturn($currentUserAcl[0]);
$this->permissionService->expects($this->at(3))
->method('userCan')
->willReturn($currentUserAcl[1]);
$this->permissionService->expects($this->at(4))
->method('userCan')
->willReturn($currentUserAcl[2]);
->willReturnOnConsecutiveCalls(
$currentUserAcl[0],
$currentUserAcl[1],
$currentUserAcl[2]
);
}
$user = $this->createMock(IUser::class);
@@ -333,6 +352,11 @@ class BoardServiceTest extends TestCase {
$acl->resolveRelation('participant', function ($participant) use (&$user) {
return null;
});
$this->permissionService->expects($this->any())
->method('findUsers')
->willReturn([
'admin' => 'admin',
]);
$this->notificationHelper->expects($this->once())
->method('sendBoardShared');
$expected = clone $acl;

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