Compare commits

..

1 Commits

Author SHA1 Message Date
Julius Härtl
011488d63b ci: Add query count for integration tests
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2023-02-16 23:33:31 +01:00
72 changed files with 1405 additions and 1346 deletions

View File

@@ -148,7 +148,7 @@ jobs:
tar -zcvf ${{ env.APP_NAME }}.tar.gz ${{ env.APP_NAME }} tar -zcvf ${{ env.APP_NAME }}.tar.gz ${{ env.APP_NAME }}
- name: Attach tarball to github release - name: Attach tarball to github release
uses: svenstaro/upload-release-action@7319e4733ec7a184d739a6f412c40ffc339b69c7 # v2 uses: svenstaro/upload-release-action@cc92c9093e5f785e23a3d654fe2671640b851b5f # v2
id: attach_to_release id: attach_to_release
with: with:
repo_token: ${{ secrets.GITHUB_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -23,7 +23,7 @@ jobs:
# containers: [1, 2, 3] # containers: [1, 2, 3]
php-versions: [ '8.0' ] php-versions: [ '8.0' ]
databases: [ 'sqlite' ] databases: [ 'sqlite' ]
server-versions: [ 'stable26' ] server-versions: [ 'master' ]
steps: steps:
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}

View File

@@ -28,7 +28,7 @@ jobs:
matrix: matrix:
php-versions: ['8.1'] php-versions: ['8.1']
databases: ['sqlite', 'mysql', 'pgsql'] databases: ['sqlite', 'mysql', 'pgsql']
server-versions: ['stable26'] server-versions: ['master']
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }} name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
@@ -74,12 +74,11 @@ jobs:
uses: shivammathur/setup-php@2.24.0 uses: shivammathur/setup-php@2.24.0
with: with:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql, apcu tools: phpunit
ini-values: extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql,
apc.enable_cli=on
coverage: none coverage: none
- name: Set up dependencies - name: Set up PHPUnit
working-directory: apps/${{ env.APP_NAME }} working-directory: apps/${{ env.APP_NAME }}
run: composer i --no-dev run: composer i --no-dev
@@ -92,9 +91,6 @@ jobs:
fi fi
mkdir data mkdir data
./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin ./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
./occ config:system:set hashing_default_password --value=true --type=boolean
./occ config:system:set memcache.local --value="\\OC\\Memcache\\APCu"
./occ config:system:set memcache.distributed --value="\\OC\\Memcache\\APCu"
cat config/config.php cat config/config.php
./occ user:list ./occ user:list
./occ app:enable --force ${{ env.APP_NAME }} ./occ app:enable --force ${{ env.APP_NAME }}
@@ -107,7 +103,7 @@ jobs:
- name: Query count - name: Query count
if: ${{ matrix.databases == 'mysql' }} if: ${{ matrix.databases == 'mysql' }}
uses: actions/github-script@v6 uses: actions/github-script@v5
with: with:
github-token: ${{secrets.GITHUB_TOKEN}} github-token: ${{secrets.GITHUB_TOKEN}}
script: | script: |
@@ -151,4 +147,4 @@ jobs:
repo: context.repo.repo, repo: context.repo.repo,
body: comment body: comment
}) })
} }

View File

@@ -26,9 +26,9 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: ['8.0', '8.1', '8.2'] php-versions: ['8.0', '8.1']
databases: ['sqlite', 'mysql', 'pgsql'] databases: ['sqlite', 'mysql', 'pgsql']
server-versions: ['stable26'] server-versions: ['master']
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }} name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}

View File

@@ -1,47 +1,6 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## 1.9.0-beta.2
### Fixed
- fix: Avoid mutating the due date when calculating days @juliushaertl [#4488](https://github.com/nextcloud/deck/pull/4488)
- fix: Pass user id along to properly check permissions in background jobs @juliushaertl [#4485](https://github.com/nextcloud/deck/pull/4485)
- fix: Use passed userid when getting attachment folder @juliushaertl [#4487](https://github.com/nextcloud/deck/pull/4487)
- fix: Use proper z-index for text menubar @juliushaertl [#4490](https://github.com/nextcloud/deck/pull/4490)
- fix(dashboard): Fix undefined array index @marcelklehr [#4492](https://github.com/nextcloud/deck/pull/4492)
- fix: Always return sorted index array to make sure a json array is the result @juliushaertl [#4493](https://github.com/nextcloud/deck/pull/4493)
- chore(CI): Adjust testing matrix for Nextcloud 26 on stable26 @nickvergessen [#4498](https://github.com/nextcloud/deck/pull/4498)
## 1.9.0-beta.1
### Added
- Export Board @david-loe [#3065](https://github.com/nextcloud/deck/pull/3065)
- basic notify_push usage with session handling @alangecker [#3876](https://github.com/nextcloud/deck/pull/3876)
- feat(Description): Use text as editor if available @juliushaertl [#4399](https://github.com/nextcloud/deck/pull/4399)
- Improve reference provider and add reference widgets @julien-nc [#4422](https://github.com/nextcloud/deck/pull/4422)
- live updates 🎉 @alangecker [#4273](https://github.com/nextcloud/deck/pull/4273)
- Tag creation from card view @juliushaertl [#4344](https://github.com/nextcloud/deck/pull/4344)
### Fixed
- Fix component renaming so that acl works on shares again @small1 [#4315](https://github.com/nextcloud/deck/pull/4315)
- fix(Sidebar): Only close sidebar on v-click-outside for specific targets @juliushaertl [#4350](https://github.com/nextcloud/deck/pull/4350)
- add basic e2e tests for stack title @shoetten [#4206](https://github.com/nextcloud/deck/pull/4206)
- App metadata: add links to user and developer documentation @p-bo [#4356](https://github.com/nextcloud/deck/pull/4356)
- Update signature of Entity::markFieldUpdated @nickvergessen [#4398](https://github.com/nextcloud/deck/pull/4398)
- Remove updated nightly information @xf- [#4419](https://github.com/nextcloud/deck/pull/4419)
- perf: Register notifier and resource listener lazy @juliushaertl [#4439](https://github.com/nextcloud/deck/pull/4439)
- perf: Lazy load dashboard components @juliushaertl [#4440](https://github.com/nextcloud/deck/pull/4440)
- Optimise upcomming overview creation @Raudius [#3793](https://github.com/nextcloud/deck/pull/3793)
- Performance boost @juliushaertl [#4452](https://github.com/nextcloud/deck/pull/4452)
### Other
- Dependency updates
## 1.8.0-beta.1 ## 1.8.0-beta.1
### Enhancements ### Enhancements

View File

@@ -16,7 +16,7 @@
- 🚀 Get your project organized - 🚀 Get your project organized
</description> </description>
<version>1.9.0-beta.2</version> <version>1.9.0-beta.1</version>
<licence>agpl</licence> <licence>agpl</licence>
<author>Julius Härtl</author> <author>Julius Härtl</author>
<documentation> <documentation>

View File

@@ -19,7 +19,7 @@
"symfony/event-dispatcher": "^4.0", "symfony/event-dispatcher": "^4.0",
"vimeo/psalm": "^5.4", "vimeo/psalm": "^5.4",
"php-parallel-lint/php-parallel-lint": "^1.2", "php-parallel-lint/php-parallel-lint": "^1.2",
"nextcloud/ocp": "dev-stable26" "nextcloud/ocp": "dev-master"
}, },
"config": { "config": {
"optimize-autoloader": true, "optimize-autoloader": true,

65
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "7f234626b3fd062832a6387b9434427c", "content-hash": "22d201a4569de6d4fafbc13277ae91a6",
"packages": [ "packages": [
{ {
"name": "cogpowered/finediff", "name": "cogpowered/finediff",
@@ -952,16 +952,16 @@
}, },
{ {
"name": "fidry/cpu-core-counter", "name": "fidry/cpu-core-counter",
"version": "0.5.1", "version": "0.4.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/theofidry/cpu-core-counter.git", "url": "https://github.com/theofidry/cpu-core-counter.git",
"reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623" "reference": "79261cc280aded96d098e1b0e0ba0c4881b432c2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/b58e5a3933e541dc286cc91fc4f3898bbc6f1623", "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/79261cc280aded96d098e1b0e0ba0c4881b432c2",
"reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623", "reference": "79261cc280aded96d098e1b0e0ba0c4881b432c2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1001,7 +1001,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/theofidry/cpu-core-counter/issues", "issues": "https://github.com/theofidry/cpu-core-counter/issues",
"source": "https://github.com/theofidry/cpu-core-counter/tree/0.5.1" "source": "https://github.com/theofidry/cpu-core-counter/tree/0.4.1"
}, },
"funding": [ "funding": [
{ {
@@ -1009,7 +1009,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-12-24T12:35:10+00:00" "time": "2022-12-16T22:01:02+00:00"
}, },
{ {
"name": "friendsofphp/php-cs-fixer", "name": "friendsofphp/php-cs-fixer",
@@ -1253,16 +1253,16 @@
}, },
{ {
"name": "nextcloud/ocp", "name": "nextcloud/ocp",
"version": "dev-stable26", "version": "dev-master",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nextcloud-deps/ocp.git", "url": "https://github.com/nextcloud-deps/ocp.git",
"reference": "d1142002189596564d2575b35cbf13f17ff1b4a2" "reference": "5636b942e35ee391b1103150261d83d3d753d657"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/d1142002189596564d2575b35cbf13f17ff1b4a2", "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/5636b942e35ee391b1103150261d83d3d753d657",
"reference": "d1142002189596564d2575b35cbf13f17ff1b4a2", "reference": "5636b942e35ee391b1103150261d83d3d753d657",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1272,6 +1272,7 @@
"psr/event-dispatcher": "^1.0", "psr/event-dispatcher": "^1.0",
"psr/log": "^1.1" "psr/log": "^1.1"
}, },
"default-branch": true,
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@@ -1291,9 +1292,9 @@
"description": "Composer package containing Nextcloud's public API (classes, interfaces)", "description": "Composer package containing Nextcloud's public API (classes, interfaces)",
"support": { "support": {
"issues": "https://github.com/nextcloud-deps/ocp/issues", "issues": "https://github.com/nextcloud-deps/ocp/issues",
"source": "https://github.com/nextcloud-deps/ocp/tree/stable26" "source": "https://github.com/nextcloud-deps/ocp/tree/master"
}, },
"time": "2023-03-03T14:19:31+00:00" "time": "2023-02-08T00:37:37+00:00"
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
@@ -3799,25 +3800,26 @@
}, },
{ {
"name": "spatie/array-to-xml", "name": "spatie/array-to-xml",
"version": "3.1.5", "version": "2.17.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/spatie/array-to-xml.git", "url": "https://github.com/spatie/array-to-xml.git",
"reference": "13f76acef5362d15c71ae1ac6350cc3df5e25e43" "reference": "5cbec9c6ab17e320c58a259f0cebe88bde4a7c46"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/spatie/array-to-xml/zipball/13f76acef5362d15c71ae1ac6350cc3df5e25e43", "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/5cbec9c6ab17e320c58a259f0cebe88bde4a7c46",
"reference": "13f76acef5362d15c71ae1ac6350cc3df5e25e43", "reference": "5cbec9c6ab17e320c58a259f0cebe88bde4a7c46",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-dom": "*", "ext-dom": "*",
"php": "^8.0" "php": "^7.4|^8.0"
}, },
"require-dev": { "require-dev": {
"mockery/mockery": "^1.2", "mockery/mockery": "^1.2",
"pestphp/pest": "^1.21", "pestphp/pest": "^1.21",
"phpunit/phpunit": "^9.0",
"spatie/pest-plugin-snapshots": "^1.1" "spatie/pest-plugin-snapshots": "^1.1"
}, },
"type": "library", "type": "library",
@@ -3846,7 +3848,7 @@
"xml" "xml"
], ],
"support": { "support": {
"source": "https://github.com/spatie/array-to-xml/tree/3.1.5" "source": "https://github.com/spatie/array-to-xml/tree/2.17.1"
}, },
"funding": [ "funding": [
{ {
@@ -3858,7 +3860,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-12-24T13:43:51+00:00" "time": "2022-12-26T08:22:07+00:00"
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
@@ -5301,16 +5303,16 @@
}, },
{ {
"name": "vimeo/psalm", "name": "vimeo/psalm",
"version": "5.7.7", "version": "5.6.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/vimeo/psalm.git", "url": "https://github.com/vimeo/psalm.git",
"reference": "e028ba46ba0d7f9a78bc3201c251e137383e145f" "reference": "e784128902dfe01d489c4123d69918a9f3c1eac5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/vimeo/psalm/zipball/e028ba46ba0d7f9a78bc3201c251e137383e145f", "url": "https://api.github.com/repos/vimeo/psalm/zipball/e784128902dfe01d489c4123d69918a9f3c1eac5",
"reference": "e028ba46ba0d7f9a78bc3201c251e137383e145f", "reference": "e784128902dfe01d489c4123d69918a9f3c1eac5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -5329,12 +5331,12 @@
"ext-tokenizer": "*", "ext-tokenizer": "*",
"felixfbecker/advanced-json-rpc": "^3.1", "felixfbecker/advanced-json-rpc": "^3.1",
"felixfbecker/language-server-protocol": "^1.5.2", "felixfbecker/language-server-protocol": "^1.5.2",
"fidry/cpu-core-counter": "^0.4.1 || ^0.5.1", "fidry/cpu-core-counter": "^0.4.0",
"netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
"nikic/php-parser": "^4.13", "nikic/php-parser": "^4.13",
"php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0", "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0",
"sebastian/diff": "^4.0 || ^5.0", "sebastian/diff": "^4.0 || ^5.0",
"spatie/array-to-xml": "^2.17.0 || ^3.0", "spatie/array-to-xml": "^2.17.0",
"symfony/console": "^4.1.6 || ^5.0 || ^6.0", "symfony/console": "^4.1.6 || ^5.0 || ^6.0",
"symfony/filesystem": "^5.4 || ^6.0" "symfony/filesystem": "^5.4 || ^6.0"
}, },
@@ -5343,13 +5345,13 @@
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.4", "bamarni/composer-bin-plugin": "^1.4",
"brianium/paratest": "^6.9", "brianium/paratest": "^6.0",
"ext-curl": "*", "ext-curl": "*",
"mockery/mockery": "^1.5", "mockery/mockery": "^1.5",
"nunomaduro/mock-final-classes": "^1.1", "nunomaduro/mock-final-classes": "^1.1",
"php-parallel-lint/php-parallel-lint": "^1.2", "php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpdoc-parser": "^1.6", "phpstan/phpdoc-parser": "^1.6",
"phpunit/phpunit": "^9.6", "phpunit/phpunit": "^9.5",
"psalm/plugin-mockery": "^1.1", "psalm/plugin-mockery": "^1.1",
"psalm/plugin-phpunit": "^0.18", "psalm/plugin-phpunit": "^0.18",
"slevomat/coding-standard": "^8.4", "slevomat/coding-standard": "^8.4",
@@ -5395,14 +5397,13 @@
"keywords": [ "keywords": [
"code", "code",
"inspection", "inspection",
"php", "php"
"static analysis"
], ],
"support": { "support": {
"issues": "https://github.com/vimeo/psalm/issues", "issues": "https://github.com/vimeo/psalm/issues",
"source": "https://github.com/vimeo/psalm/tree/5.7.7" "source": "https://github.com/vimeo/psalm/tree/5.6.0"
}, },
"time": "2023-02-25T01:05:07+00:00" "time": "2023-01-23T20:32:47+00:00"
}, },
{ {
"name": "webmozart/assert", "name": "webmozart/assert",

View File

@@ -79,12 +79,8 @@ OC.L10N.register(
"The board \"%s\" has been shared with you by %s." : "Таблото \"%s\" е споделено с вас от%s.", "The board \"%s\" has been shared with you by %s." : "Таблото \"%s\" е споделено с вас от%s.",
"{user} has shared {deck-board} with you." : "{user} сподели {deck-board} с Вас.", "{user} has shared {deck-board} with you." : "{user} сподели {deck-board} с Вас.",
"Deck board" : "Deck табло", "Deck board" : "Deck табло",
"Owned by %1$s" : "Притежаван от %1$s",
"Deck boards, cards and comments" : "Табла, карти и коментари",
"From %1$s, in %2$s/%3$s, owned by %4$s" : "От %1$s, в %2$s/%3$s, притежание на %4$s",
"Card comments" : "Коментари на карти", "Card comments" : "Коментари на карти",
"%s on %s" : "%s на %s", "%s on %s" : "%s на %s",
"Deck boards and cards" : "Табла и карти",
"No data was provided to create an attachment." : "Не бяха предоставени данни за създаване на прикачен файл.", "No data was provided to create an attachment." : "Не бяха предоставени данни за създаване на прикачен файл.",
"Finished" : "Готово", "Finished" : "Готово",
"To review" : "За преглед", "To review" : "За преглед",
@@ -295,7 +291,6 @@ OC.L10N.register(
"No due" : "Не се дължи", "No due" : "Не се дължи",
"Search for {searchQuery} in all boards" : "Търсене на {searchQuery} във всички табла", "Search for {searchQuery} in all boards" : "Търсене на {searchQuery} във всички табла",
"No results found" : "Няма намерени резултати", "No results found" : "Няма намерени резултати",
"Deck board {name}\n* Last modified on {lastMod}" : "Табло {name}\n* Последна промяна на {lastMod}",
"{stack} in {board}" : "{stack} в {board}", "{stack} in {board}" : "{stack} в {board}",
"Click to expand description" : "Кликване за разширяване на описанието", "Click to expand description" : "Кликване за разширяване на описанието",
"* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Създаден на {created}\n* Последна промяна на {lastMod} \n* {nbAttachments} прикачени файлове \n* {nbComments} коментара", "* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Създаден на {created}\n* Последна промяна на {lastMod} \n* {nbAttachments} прикачени файлове \n* {nbComments} коментара",

View File

@@ -77,12 +77,8 @@
"The board \"%s\" has been shared with you by %s." : "Таблото \"%s\" е споделено с вас от%s.", "The board \"%s\" has been shared with you by %s." : "Таблото \"%s\" е споделено с вас от%s.",
"{user} has shared {deck-board} with you." : "{user} сподели {deck-board} с Вас.", "{user} has shared {deck-board} with you." : "{user} сподели {deck-board} с Вас.",
"Deck board" : "Deck табло", "Deck board" : "Deck табло",
"Owned by %1$s" : "Притежаван от %1$s",
"Deck boards, cards and comments" : "Табла, карти и коментари",
"From %1$s, in %2$s/%3$s, owned by %4$s" : "От %1$s, в %2$s/%3$s, притежание на %4$s",
"Card comments" : "Коментари на карти", "Card comments" : "Коментари на карти",
"%s on %s" : "%s на %s", "%s on %s" : "%s на %s",
"Deck boards and cards" : "Табла и карти",
"No data was provided to create an attachment." : "Не бяха предоставени данни за създаване на прикачен файл.", "No data was provided to create an attachment." : "Не бяха предоставени данни за създаване на прикачен файл.",
"Finished" : "Готово", "Finished" : "Готово",
"To review" : "За преглед", "To review" : "За преглед",
@@ -293,7 +289,6 @@
"No due" : "Не се дължи", "No due" : "Не се дължи",
"Search for {searchQuery} in all boards" : "Търсене на {searchQuery} във всички табла", "Search for {searchQuery} in all boards" : "Търсене на {searchQuery} във всички табла",
"No results found" : "Няма намерени резултати", "No results found" : "Няма намерени резултати",
"Deck board {name}\n* Last modified on {lastMod}" : "Табло {name}\n* Последна промяна на {lastMod}",
"{stack} in {board}" : "{stack} в {board}", "{stack} in {board}" : "{stack} в {board}",
"Click to expand description" : "Кликване за разширяване на описанието", "Click to expand description" : "Кликване за разширяване на описанието",
"* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Създаден на {created}\n* Последна промяна на {lastMod} \n* {nbAttachments} прикачени файлове \n* {nbComments} коментара", "* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Създаден на {created}\n* Последна промяна на {lastMod} \n* {nbAttachments} прикачени файлове \n* {nbComments} коментара",

View File

@@ -79,11 +79,8 @@ OC.L10N.register(
"The board \"%s\" has been shared with you by %s." : "Ο πίνακας \"%s\" είναι σε κοινή χρήση μαζί σας από %s.", "The board \"%s\" has been shared with you by %s." : "Ο πίνακας \"%s\" είναι σε κοινή χρήση μαζί σας από %s.",
"{user} has shared {deck-board} with you." : "Ο/Η διαμοιράστηκε μαζί σας το {deck-board}", "{user} has shared {deck-board} with you." : "Ο/Η διαμοιράστηκε μαζί σας το {deck-board}",
"Deck board" : "Πίνακας του Deck", "Deck board" : "Πίνακας του Deck",
"Owned by %1$s" : "Ανήκει στον/στην %1$s",
"Deck boards, cards and comments" : "Πίνακες, κάρτες και σχόλια Deck",
"Card comments" : "Σχόλια καρτέλας", "Card comments" : "Σχόλια καρτέλας",
"%s on %s" : "%s στο %s", "%s on %s" : "%s στο %s",
"Deck boards and cards" : "Πίνακες και κάρτες Deck",
"No data was provided to create an attachment." : "Δεν δόθηκαν στοιχεία για δημιουργία συνημμένου.", "No data was provided to create an attachment." : "Δεν δόθηκαν στοιχεία για δημιουργία συνημμένου.",
"Finished" : "Ολοκληρώθηκε", "Finished" : "Ολοκληρώθηκε",
"To review" : "Προς επισκόπηση", "To review" : "Προς επισκόπηση",
@@ -139,7 +136,6 @@ OC.L10N.register(
"Archived cards" : "Αρχειοθετημένες καρτέλες", "Archived cards" : "Αρχειοθετημένες καρτέλες",
"Add list" : "Προσθήκη λίστας", "Add list" : "Προσθήκη λίστας",
"List name" : "Όνομα λίστας", "List name" : "Όνομα λίστας",
"Active filters" : "Ενεργά φίλτρα",
"Apply filter" : "Εφαρμογή φίλτρου", "Apply filter" : "Εφαρμογή φίλτρου",
"Filter by tag" : "Φίλτρο ανά ετικέτα", "Filter by tag" : "Φίλτρο ανά ετικέτα",
"Filter by assigned user" : "Φίλτρο ανά χρήστη", "Filter by assigned user" : "Φίλτρο ανά χρήστη",
@@ -156,7 +152,6 @@ OC.L10N.register(
"Toggle compact mode" : "Εναλλαγή λειτουργίας μικρού μεγέθους", "Toggle compact mode" : "Εναλλαγή λειτουργίας μικρού μεγέθους",
"Open details" : "Άνοιγμα λεπτομερειών", "Open details" : "Άνοιγμα λεπτομερειών",
"Details" : "Λεπτομέρειες", "Details" : "Λεπτομέρειες",
"Currently present people" : "Παρόντες αυτή τη στιγμή",
"Loading board" : "Φόρτωση πίνακα", "Loading board" : "Φόρτωση πίνακα",
"No lists available" : "Δεν υπάρχουν διαθέσιμες λίστες", "No lists available" : "Δεν υπάρχουν διαθέσιμες λίστες",
"Create a new list to add cards to this board" : "Δημιουργήστε νέα λίστα για να προσθέσετε καρτέλες σε αυτό τον πίνακα.", "Create a new list to add cards to this board" : "Δημιουργήστε νέα λίστα για να προσθέσετε καρτέλες σε αυτό τον πίνακα.",
@@ -180,17 +175,10 @@ OC.L10N.register(
"Owner" : "Κάτοχος", "Owner" : "Κάτοχος",
"Delete" : "Διαγραφή", "Delete" : "Διαγραφή",
"Failed to create share with {displayName}" : "Αποτυχία δημιουργίας κοινής χρήσης με το {displayName}", "Failed to create share with {displayName}" : "Αποτυχία δημιουργίας κοινής χρήσης με το {displayName}",
"Are you sure you want to transfer the board {title} to {user}?" : "Είστε σίγουροι ότι θέλετε να μεταφέρετε τον πίνακα {title} στον {user}? ",
"Transfer the board." : "Μεταφορά του πίνακα.",
"Transfer" : "Μεταφορά", "Transfer" : "Μεταφορά",
"The board has been transferred to {user}" : "Ο πίνακας έχει μεταφερθεί στον/στην {user}",
"Failed to transfer the board to {user}" : "Απέτυχε η μεταφορά του πίνακα στον χρήστη {user}",
"Edit list title" : "Επεξεργασία τίτλου λίστας",
"Archive all cards" : "Αρχειοθέτηση όλων των καρτελών.", "Archive all cards" : "Αρχειοθέτηση όλων των καρτελών.",
"Unarchive all cards" : "Κατάργηση αρχειοθέτησης όλων των καρτών",
"Delete list" : "Διαγραφή λίστας", "Delete list" : "Διαγραφή λίστας",
"Archive all cards in this list" : "Αρχειοθέτηση όλων των καρτελών σε αυτή τη λίστα.", "Archive all cards in this list" : "Αρχειοθέτηση όλων των καρτελών σε αυτή τη λίστα.",
"Unarchive all cards in this list" : "Κατάργηση αρχειοθέτησης όλων των καρτών σε αυτή τη λίστα",
"Add a new card" : "Προσθήκη νέας καρτέλας", "Add a new card" : "Προσθήκη νέας καρτέλας",
"Card name" : "Όνομα καρτέλας", "Card name" : "Όνομα καρτέλας",
"List deleted" : "Η λίστα διαγράφηκε", "List deleted" : "Η λίστα διαγράφηκε",
@@ -248,9 +236,7 @@ OC.L10N.register(
"Write a description …" : "Γράψτε μια περιγραφή…", "Write a description …" : "Γράψτε μια περιγραφή…",
"Choose attachment" : "Επιλογή συνημμένου", "Choose attachment" : "Επιλογή συνημμένου",
"(group)" : "(ομάδα)", "(group)" : "(ομάδα)",
"Todo items" : "Στοιχεία todo",
"{count} comments, {unread} unread" : "{count} σχόλια, {unread} μη αναγνωσμένα", "{count} comments, {unread} unread" : "{count} σχόλια, {unread} μη αναγνωσμένα",
"Edit card title" : "Επεξεργασία τίτλου κάρτας",
"Assign to me" : "Ανάθεση σε εμένα", "Assign to me" : "Ανάθεση σε εμένα",
"Unassign myself" : "Αποδέσμευσή μου", "Unassign myself" : "Αποδέσμευσή μου",
"Move card" : "Μετακίνηση καρτέλας", "Move card" : "Μετακίνηση καρτέλας",
@@ -265,7 +251,6 @@ OC.L10N.register(
"All boards" : "Όλοι οι πίνακες", "All boards" : "Όλοι οι πίνακες",
"Archived boards" : "Αρχειοθέτηση πινάκων ", "Archived boards" : "Αρχειοθέτηση πινάκων ",
"Shared with you" : "Διαμοιρασμένα μαζί σας", "Shared with you" : "Διαμοιρασμένα μαζί σας",
"Deck settings" : "Ρυθμίσεις Deck",
"Use bigger card view" : "Χρησιμοποιήστε μεγαλύτερη προβολή καρτέλας", "Use bigger card view" : "Χρησιμοποιήστε μεγαλύτερη προβολή καρτέλας",
"Show boards in calendar/tasks" : "Εμφάνιση πινάκων στο ημερολόγιο / εργασίες", "Show boards in calendar/tasks" : "Εμφάνιση πινάκων στο ημερολόγιο / εργασίες",
"Limit deck usage of groups" : "Περιορίστε τη χρήση της εφαρμογής deck σε ομάδες", "Limit deck usage of groups" : "Περιορίστε τη χρήση της εφαρμογής deck σε ομάδες",
@@ -275,7 +260,6 @@ OC.L10N.register(
"Clone board" : "Κλώνος πίνακα", "Clone board" : "Κλώνος πίνακα",
"Unarchive board" : "Κατάργηση αρχειοθέτησης πίνακα", "Unarchive board" : "Κατάργηση αρχειοθέτησης πίνακα",
"Archive board" : "Αρχειοθέτηση πίνακα", "Archive board" : "Αρχειοθέτηση πίνακα",
"Export board" : "Εξαγωγή πίνακα",
"Turn on due date reminders" : "Ενεργοποιήστε τις υπενθυμίσεις ημερομηνίας προθεσμίας", "Turn on due date reminders" : "Ενεργοποιήστε τις υπενθυμίσεις ημερομηνίας προθεσμίας",
"Turn off due date reminders" : "Απενεργοποιήστε τις υπενθυμίσεις ημερομηνίας προθεσμίας", "Turn off due date reminders" : "Απενεργοποιήστε τις υπενθυμίσεις ημερομηνίας προθεσμίας",
"Due date reminders" : "Υπενθυμίσεις ημερομηνίας προθεσμίας", "Due date reminders" : "Υπενθυμίσεις ημερομηνίας προθεσμίας",
@@ -287,21 +271,14 @@ OC.L10N.register(
"Only assigned cards" : "Μόνο καρτέλες που έχουν ανατεθεί", "Only assigned cards" : "Μόνο καρτέλες που έχουν ανατεθεί",
"No reminder" : "Δεν υπάρχει υπενθύμιση", "No reminder" : "Δεν υπάρχει υπενθύμιση",
"An error occurred" : "Παρουσιάστηκε σφάλμα", "An error occurred" : "Παρουσιάστηκε σφάλμα",
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον πίνακα {title}; Αυτό θα διαγράψει όλα τα δεδομένα του πίνακα συμπεριλαμβανομένων και των αρχειοθετημένων καρτών.",
"Delete the board?" : "Διαγραφή του πίνακα;", "Delete the board?" : "Διαγραφή του πίνακα;",
"Loading filtered view" : "Φόρτωση εμφάνισης με βάση το φίλτρο", "Loading filtered view" : "Φόρτωση εμφάνισης με βάση το φίλτρο",
"No due" : "Χωρίς λήξη", "No due" : "Χωρίς λήξη",
"Search for {searchQuery} in all boards" : "Αναζήτηση για {searchQuery} σε όλους τους πίνακες", "Search for {searchQuery} in all boards" : "Αναζήτηση για {searchQuery} σε όλους τους πίνακες",
"No results found" : "Δεν βρέθηκαν αποτελέσματα", "No results found" : "Δεν βρέθηκαν αποτελέσματα",
"Deck board {name}\n* Last modified on {lastMod}" : "Πίνακας Deck {name}\n* Τελευταία τροποποίηση στις {lastMod}",
"{stack} in {board}" : "{stack} στο {board}", "{stack} in {board}" : "{stack} στο {board}",
"Click to expand description" : "Κλικ για επέκταση περιγραφής",
"* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Δημιουργήθηκε στις {created}\n* Τροποποιήθηκε στις {lastMod}\n* {nbAttachments} συνημμένα\n* {nbComments} σχόλια",
"{nbCards} cards" : "{nbCards} κάρτες",
"Click to expand comment" : "Κλικ για επέκταση σχολίου",
"No upcoming cards" : "Δεν υπάρχουν επερχόμενες καρτέλες", "No upcoming cards" : "Δεν υπάρχουν επερχόμενες καρτέλες",
"upcoming cards" : "επερχόμενες καρτέλες", "upcoming cards" : "επερχόμενες καρτέλες",
"Due on {date}" : "Προθεσμία στις {date}",
"Link to a board" : "Σύνδεσμος στον πίνακα", "Link to a board" : "Σύνδεσμος στον πίνακα",
"Link to a card" : "Σύνδεσμος σε καρτέλα", "Link to a card" : "Σύνδεσμος σε καρτέλα",
"Create a card" : "Δημιουργία καρτέλας", "Create a card" : "Δημιουργία καρτέλας",
@@ -314,8 +291,6 @@ OC.L10N.register(
"Share {file} with a Deck card" : "Μοιραστείτε το {file} με μια καρτέλα Deck", "Share {file} with a Deck card" : "Μοιραστείτε το {file} με μια καρτέλα Deck",
"Share" : "Μοιραστείτε", "Share" : "Μοιραστείτε",
"Are you sure you want to transfer the board {title} for {user}?" : "Είστε σίγουροι ότι θέλετε να μεταφέρετε τον πίνακα {title} για {user}? ", "Are you sure you want to transfer the board {title} for {user}?" : "Είστε σίγουροι ότι θέλετε να μεταφέρετε τον πίνακα {title} για {user}? ",
"Transfer the board for {user} successfully" : "Επιτυχής μεταφορά του πίνακα για τον χρήστη {user}",
"Failed to transfer the board for {user}" : "Απέτυχε η μεταφορά του πίνακα για τον χρήστη {user}",
"Add a new list" : "Προσθήκη νέας λίστας", "Add a new list" : "Προσθήκη νέας λίστας",
"Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον πίνακα {title}; Θα διαγραφούν όλα τα δεδομένα." "Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον πίνακα {title}; Θα διαγραφούν όλα τα δεδομένα."
}, },

View File

@@ -77,11 +77,8 @@
"The board \"%s\" has been shared with you by %s." : "Ο πίνακας \"%s\" είναι σε κοινή χρήση μαζί σας από %s.", "The board \"%s\" has been shared with you by %s." : "Ο πίνακας \"%s\" είναι σε κοινή χρήση μαζί σας από %s.",
"{user} has shared {deck-board} with you." : "Ο/Η διαμοιράστηκε μαζί σας το {deck-board}", "{user} has shared {deck-board} with you." : "Ο/Η διαμοιράστηκε μαζί σας το {deck-board}",
"Deck board" : "Πίνακας του Deck", "Deck board" : "Πίνακας του Deck",
"Owned by %1$s" : "Ανήκει στον/στην %1$s",
"Deck boards, cards and comments" : "Πίνακες, κάρτες και σχόλια Deck",
"Card comments" : "Σχόλια καρτέλας", "Card comments" : "Σχόλια καρτέλας",
"%s on %s" : "%s στο %s", "%s on %s" : "%s στο %s",
"Deck boards and cards" : "Πίνακες και κάρτες Deck",
"No data was provided to create an attachment." : "Δεν δόθηκαν στοιχεία για δημιουργία συνημμένου.", "No data was provided to create an attachment." : "Δεν δόθηκαν στοιχεία για δημιουργία συνημμένου.",
"Finished" : "Ολοκληρώθηκε", "Finished" : "Ολοκληρώθηκε",
"To review" : "Προς επισκόπηση", "To review" : "Προς επισκόπηση",
@@ -137,7 +134,6 @@
"Archived cards" : "Αρχειοθετημένες καρτέλες", "Archived cards" : "Αρχειοθετημένες καρτέλες",
"Add list" : "Προσθήκη λίστας", "Add list" : "Προσθήκη λίστας",
"List name" : "Όνομα λίστας", "List name" : "Όνομα λίστας",
"Active filters" : "Ενεργά φίλτρα",
"Apply filter" : "Εφαρμογή φίλτρου", "Apply filter" : "Εφαρμογή φίλτρου",
"Filter by tag" : "Φίλτρο ανά ετικέτα", "Filter by tag" : "Φίλτρο ανά ετικέτα",
"Filter by assigned user" : "Φίλτρο ανά χρήστη", "Filter by assigned user" : "Φίλτρο ανά χρήστη",
@@ -154,7 +150,6 @@
"Toggle compact mode" : "Εναλλαγή λειτουργίας μικρού μεγέθους", "Toggle compact mode" : "Εναλλαγή λειτουργίας μικρού μεγέθους",
"Open details" : "Άνοιγμα λεπτομερειών", "Open details" : "Άνοιγμα λεπτομερειών",
"Details" : "Λεπτομέρειες", "Details" : "Λεπτομέρειες",
"Currently present people" : "Παρόντες αυτή τη στιγμή",
"Loading board" : "Φόρτωση πίνακα", "Loading board" : "Φόρτωση πίνακα",
"No lists available" : "Δεν υπάρχουν διαθέσιμες λίστες", "No lists available" : "Δεν υπάρχουν διαθέσιμες λίστες",
"Create a new list to add cards to this board" : "Δημιουργήστε νέα λίστα για να προσθέσετε καρτέλες σε αυτό τον πίνακα.", "Create a new list to add cards to this board" : "Δημιουργήστε νέα λίστα για να προσθέσετε καρτέλες σε αυτό τον πίνακα.",
@@ -178,17 +173,10 @@
"Owner" : "Κάτοχος", "Owner" : "Κάτοχος",
"Delete" : "Διαγραφή", "Delete" : "Διαγραφή",
"Failed to create share with {displayName}" : "Αποτυχία δημιουργίας κοινής χρήσης με το {displayName}", "Failed to create share with {displayName}" : "Αποτυχία δημιουργίας κοινής χρήσης με το {displayName}",
"Are you sure you want to transfer the board {title} to {user}?" : "Είστε σίγουροι ότι θέλετε να μεταφέρετε τον πίνακα {title} στον {user}? ",
"Transfer the board." : "Μεταφορά του πίνακα.",
"Transfer" : "Μεταφορά", "Transfer" : "Μεταφορά",
"The board has been transferred to {user}" : "Ο πίνακας έχει μεταφερθεί στον/στην {user}",
"Failed to transfer the board to {user}" : "Απέτυχε η μεταφορά του πίνακα στον χρήστη {user}",
"Edit list title" : "Επεξεργασία τίτλου λίστας",
"Archive all cards" : "Αρχειοθέτηση όλων των καρτελών.", "Archive all cards" : "Αρχειοθέτηση όλων των καρτελών.",
"Unarchive all cards" : "Κατάργηση αρχειοθέτησης όλων των καρτών",
"Delete list" : "Διαγραφή λίστας", "Delete list" : "Διαγραφή λίστας",
"Archive all cards in this list" : "Αρχειοθέτηση όλων των καρτελών σε αυτή τη λίστα.", "Archive all cards in this list" : "Αρχειοθέτηση όλων των καρτελών σε αυτή τη λίστα.",
"Unarchive all cards in this list" : "Κατάργηση αρχειοθέτησης όλων των καρτών σε αυτή τη λίστα",
"Add a new card" : "Προσθήκη νέας καρτέλας", "Add a new card" : "Προσθήκη νέας καρτέλας",
"Card name" : "Όνομα καρτέλας", "Card name" : "Όνομα καρτέλας",
"List deleted" : "Η λίστα διαγράφηκε", "List deleted" : "Η λίστα διαγράφηκε",
@@ -246,9 +234,7 @@
"Write a description …" : "Γράψτε μια περιγραφή…", "Write a description …" : "Γράψτε μια περιγραφή…",
"Choose attachment" : "Επιλογή συνημμένου", "Choose attachment" : "Επιλογή συνημμένου",
"(group)" : "(ομάδα)", "(group)" : "(ομάδα)",
"Todo items" : "Στοιχεία todo",
"{count} comments, {unread} unread" : "{count} σχόλια, {unread} μη αναγνωσμένα", "{count} comments, {unread} unread" : "{count} σχόλια, {unread} μη αναγνωσμένα",
"Edit card title" : "Επεξεργασία τίτλου κάρτας",
"Assign to me" : "Ανάθεση σε εμένα", "Assign to me" : "Ανάθεση σε εμένα",
"Unassign myself" : "Αποδέσμευσή μου", "Unassign myself" : "Αποδέσμευσή μου",
"Move card" : "Μετακίνηση καρτέλας", "Move card" : "Μετακίνηση καρτέλας",
@@ -263,7 +249,6 @@
"All boards" : "Όλοι οι πίνακες", "All boards" : "Όλοι οι πίνακες",
"Archived boards" : "Αρχειοθέτηση πινάκων ", "Archived boards" : "Αρχειοθέτηση πινάκων ",
"Shared with you" : "Διαμοιρασμένα μαζί σας", "Shared with you" : "Διαμοιρασμένα μαζί σας",
"Deck settings" : "Ρυθμίσεις Deck",
"Use bigger card view" : "Χρησιμοποιήστε μεγαλύτερη προβολή καρτέλας", "Use bigger card view" : "Χρησιμοποιήστε μεγαλύτερη προβολή καρτέλας",
"Show boards in calendar/tasks" : "Εμφάνιση πινάκων στο ημερολόγιο / εργασίες", "Show boards in calendar/tasks" : "Εμφάνιση πινάκων στο ημερολόγιο / εργασίες",
"Limit deck usage of groups" : "Περιορίστε τη χρήση της εφαρμογής deck σε ομάδες", "Limit deck usage of groups" : "Περιορίστε τη χρήση της εφαρμογής deck σε ομάδες",
@@ -273,7 +258,6 @@
"Clone board" : "Κλώνος πίνακα", "Clone board" : "Κλώνος πίνακα",
"Unarchive board" : "Κατάργηση αρχειοθέτησης πίνακα", "Unarchive board" : "Κατάργηση αρχειοθέτησης πίνακα",
"Archive board" : "Αρχειοθέτηση πίνακα", "Archive board" : "Αρχειοθέτηση πίνακα",
"Export board" : "Εξαγωγή πίνακα",
"Turn on due date reminders" : "Ενεργοποιήστε τις υπενθυμίσεις ημερομηνίας προθεσμίας", "Turn on due date reminders" : "Ενεργοποιήστε τις υπενθυμίσεις ημερομηνίας προθεσμίας",
"Turn off due date reminders" : "Απενεργοποιήστε τις υπενθυμίσεις ημερομηνίας προθεσμίας", "Turn off due date reminders" : "Απενεργοποιήστε τις υπενθυμίσεις ημερομηνίας προθεσμίας",
"Due date reminders" : "Υπενθυμίσεις ημερομηνίας προθεσμίας", "Due date reminders" : "Υπενθυμίσεις ημερομηνίας προθεσμίας",
@@ -285,21 +269,14 @@
"Only assigned cards" : "Μόνο καρτέλες που έχουν ανατεθεί", "Only assigned cards" : "Μόνο καρτέλες που έχουν ανατεθεί",
"No reminder" : "Δεν υπάρχει υπενθύμιση", "No reminder" : "Δεν υπάρχει υπενθύμιση",
"An error occurred" : "Παρουσιάστηκε σφάλμα", "An error occurred" : "Παρουσιάστηκε σφάλμα",
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον πίνακα {title}; Αυτό θα διαγράψει όλα τα δεδομένα του πίνακα συμπεριλαμβανομένων και των αρχειοθετημένων καρτών.",
"Delete the board?" : "Διαγραφή του πίνακα;", "Delete the board?" : "Διαγραφή του πίνακα;",
"Loading filtered view" : "Φόρτωση εμφάνισης με βάση το φίλτρο", "Loading filtered view" : "Φόρτωση εμφάνισης με βάση το φίλτρο",
"No due" : "Χωρίς λήξη", "No due" : "Χωρίς λήξη",
"Search for {searchQuery} in all boards" : "Αναζήτηση για {searchQuery} σε όλους τους πίνακες", "Search for {searchQuery} in all boards" : "Αναζήτηση για {searchQuery} σε όλους τους πίνακες",
"No results found" : "Δεν βρέθηκαν αποτελέσματα", "No results found" : "Δεν βρέθηκαν αποτελέσματα",
"Deck board {name}\n* Last modified on {lastMod}" : "Πίνακας Deck {name}\n* Τελευταία τροποποίηση στις {lastMod}",
"{stack} in {board}" : "{stack} στο {board}", "{stack} in {board}" : "{stack} στο {board}",
"Click to expand description" : "Κλικ για επέκταση περιγραφής",
"* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Δημιουργήθηκε στις {created}\n* Τροποποιήθηκε στις {lastMod}\n* {nbAttachments} συνημμένα\n* {nbComments} σχόλια",
"{nbCards} cards" : "{nbCards} κάρτες",
"Click to expand comment" : "Κλικ για επέκταση σχολίου",
"No upcoming cards" : "Δεν υπάρχουν επερχόμενες καρτέλες", "No upcoming cards" : "Δεν υπάρχουν επερχόμενες καρτέλες",
"upcoming cards" : "επερχόμενες καρτέλες", "upcoming cards" : "επερχόμενες καρτέλες",
"Due on {date}" : "Προθεσμία στις {date}",
"Link to a board" : "Σύνδεσμος στον πίνακα", "Link to a board" : "Σύνδεσμος στον πίνακα",
"Link to a card" : "Σύνδεσμος σε καρτέλα", "Link to a card" : "Σύνδεσμος σε καρτέλα",
"Create a card" : "Δημιουργία καρτέλας", "Create a card" : "Δημιουργία καρτέλας",
@@ -312,8 +289,6 @@
"Share {file} with a Deck card" : "Μοιραστείτε το {file} με μια καρτέλα Deck", "Share {file} with a Deck card" : "Μοιραστείτε το {file} με μια καρτέλα Deck",
"Share" : "Μοιραστείτε", "Share" : "Μοιραστείτε",
"Are you sure you want to transfer the board {title} for {user}?" : "Είστε σίγουροι ότι θέλετε να μεταφέρετε τον πίνακα {title} για {user}? ", "Are you sure you want to transfer the board {title} for {user}?" : "Είστε σίγουροι ότι θέλετε να μεταφέρετε τον πίνακα {title} για {user}? ",
"Transfer the board for {user} successfully" : "Επιτυχής μεταφορά του πίνακα για τον χρήστη {user}",
"Failed to transfer the board for {user}" : "Απέτυχε η μεταφορά του πίνακα για τον χρήστη {user}",
"Add a new list" : "Προσθήκη νέας λίστας", "Add a new list" : "Προσθήκη νέας λίστας",
"Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον πίνακα {title}; Θα διαγραφούν όλα τα δεδομένα." "Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον πίνακα {title}; Θα διαγραφούν όλα τα δεδομένα."
},"pluralForm" :"nplurals=2; plural=(n != 1);" },"pluralForm" :"nplurals=2; plural=(n != 1);"

View File

@@ -81,7 +81,6 @@ OC.L10N.register(
"Deck board" : "Tableau", "Deck board" : "Tableau",
"Owned by %1$s" : "Assignée à %1$s", "Owned by %1$s" : "Assignée à %1$s",
"Deck boards, cards and comments" : "Tableaux, cartes et commentaires", "Deck boards, cards and comments" : "Tableaux, cartes et commentaires",
"From %1$s, in %2$s/%3$s, owned by %4$s" : "De %1$s, dans %2$s / %3$s, appartenant à %4$s",
"Card comments" : "Commentaires de la carte", "Card comments" : "Commentaires de la carte",
"%s on %s" : "%s sur %s", "%s on %s" : "%s sur %s",
"Deck boards and cards" : "Tableaux et cartes", "Deck boards and cards" : "Tableaux et cartes",

View File

@@ -79,7 +79,6 @@
"Deck board" : "Tableau", "Deck board" : "Tableau",
"Owned by %1$s" : "Assignée à %1$s", "Owned by %1$s" : "Assignée à %1$s",
"Deck boards, cards and comments" : "Tableaux, cartes et commentaires", "Deck boards, cards and comments" : "Tableaux, cartes et commentaires",
"From %1$s, in %2$s/%3$s, owned by %4$s" : "De %1$s, dans %2$s / %3$s, appartenant à %4$s",
"Card comments" : "Commentaires de la carte", "Card comments" : "Commentaires de la carte",
"%s on %s" : "%s sur %s", "%s on %s" : "%s sur %s",
"Deck boards and cards" : "Tableaux et cartes", "Deck boards and cards" : "Tableaux et cartes",

View File

@@ -3,14 +3,6 @@ OC.L10N.register(
{ {
"Personal" : "Անձնական", "Personal" : "Անձնական",
"Done" : "Done", "Done" : "Done",
"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 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֊ի ընդլայնումն կանգնեցրեց նիշքի վերբեռնումը",
"Cancel" : "ընդհատել", "Cancel" : "ընդհատել",
"Close" : "Փակել", "Close" : "Փակել",
"Details" : "Մանրամասներ", "Details" : "Մանրամասներ",

View File

@@ -1,14 +1,6 @@
{ "translations": { { "translations": {
"Personal" : "Անձնական", "Personal" : "Անձնական",
"Done" : "Done", "Done" : "Done",
"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 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֊ի ընդլայնումն կանգնեցրեց նիշքի վերբեռնումը",
"Cancel" : "ընդհատել", "Cancel" : "ընդհատել",
"Close" : "Փակել", "Close" : "Փակել",
"Details" : "Մանրամասներ", "Details" : "Մանրամասներ",

View File

@@ -30,8 +30,6 @@ OC.L10N.register(
"Created" : "Үүсгэсэн", "Created" : "Үүсгэсэн",
"Today" : "өнөөдөр", "Today" : "өнөөдөр",
"Tomorrow" : "маргааш", "Tomorrow" : "маргааш",
"Next week" : "Дараа 7 хоног",
"Next month" : "Дараа сар",
"Save" : "Хадгалах", "Save" : "Хадгалах",
"Reply" : "хариулт", "Reply" : "хариулт",
"Update" : "Шинэчлэх", "Update" : "Шинэчлэх",

View File

@@ -28,8 +28,6 @@
"Created" : "Үүсгэсэн", "Created" : "Үүсгэсэн",
"Today" : "өнөөдөр", "Today" : "өнөөдөр",
"Tomorrow" : "маргааш", "Tomorrow" : "маргааш",
"Next week" : "Дараа 7 хоног",
"Next month" : "Дараа сар",
"Save" : "Хадгалах", "Save" : "Хадгалах",
"Reply" : "хариулт", "Reply" : "хариулт",
"Update" : "Шинэчлэх", "Update" : "Шинэчлэх",

View File

@@ -78,13 +78,9 @@ OC.L10N.register(
"{user} has mentioned you in a comment on {deck-card}." : "{user} har nevnt deg i en kommentar på {deck-card}.", "{user} has mentioned you in a comment on {deck-card}." : "{user} har nevnt deg i en kommentar på {deck-card}.",
"The board \"%s\" has been shared with you by %s." : "Brettet \"%s\" har blitt delt med deg av %s.", "The board \"%s\" has been shared with you by %s." : "Brettet \"%s\" har blitt delt med deg av %s.",
"{user} has shared {deck-board} with you." : "{user} har delt brettet {deck-board} med deg.", "{user} has shared {deck-board} with you." : "{user} har delt brettet {deck-board} med deg.",
"Deck board" : "Stokktavle", "Deck board" : "Deck tavle",
"Owned by %1$s" : "Eid av %1$s",
"Deck boards, cards and comments" : "Stokktavler, kort og kommentarer",
"From %1$s, in %2$s/%3$s, owned by %4$s" : "Fra %1$s, under %2$s/%3$s, eid av %4$s",
"Card comments" : "Kommentarer på kortet", "Card comments" : "Kommentarer på kortet",
"%s on %s" : "%s på %s", "%s on %s" : "%s på %s",
"Deck boards and cards" : "Stokktavler og kort",
"No data was provided to create an attachment." : "Ingen data for å opprette vedlegg.", "No data was provided to create an attachment." : "Ingen data for å opprette vedlegg.",
"Finished" : "Fullført", "Finished" : "Fullført",
"To review" : "Til gjennomlesning", "To review" : "Til gjennomlesning",
@@ -157,7 +153,6 @@ OC.L10N.register(
"Toggle compact mode" : "Endre kompakt modus", "Toggle compact mode" : "Endre kompakt modus",
"Open details" : "Åpne detaljer", "Open details" : "Åpne detaljer",
"Details" : "Detaljer", "Details" : "Detaljer",
"Currently present people" : "Tilstedeværende personer for øyeblikket",
"Loading board" : "Laster tavle", "Loading board" : "Laster tavle",
"No lists available" : "Ingen stabler tilgjengelig", "No lists available" : "Ingen stabler tilgjengelig",
"Create a new list to add cards to this board" : "Lag en ny stabel for å legge til kort til denne tavlen", "Create a new list to add cards to this board" : "Lag en ny stabel for å legge til kort til denne tavlen",
@@ -186,12 +181,9 @@ OC.L10N.register(
"Transfer" : "Overfør", "Transfer" : "Overfør",
"The board has been transferred to {user}" : "Tavlen har blitt overført til {user}", "The board has been transferred to {user}" : "Tavlen har blitt overført til {user}",
"Failed to transfer the board to {user}" : "Klarte ikke overføre tavlen til {user}", "Failed to transfer the board to {user}" : "Klarte ikke overføre tavlen til {user}",
"Edit list title" : "Rediger listetittel",
"Archive all cards" : "Arkiver alle kort", "Archive all cards" : "Arkiver alle kort",
"Unarchive all cards" : "Fjern alle kort fra arkiv",
"Delete list" : "Slett listen", "Delete list" : "Slett listen",
"Archive all cards in this list" : "Arkiver alle kort i en stabel", "Archive all cards in this list" : "Arkiver alle kort i en stabel",
"Unarchive all cards in this list" : "Fjern alle kortene i denne listen fra arkiv",
"Add a new card" : "Legg til nytt kort", "Add a new card" : "Legg til nytt kort",
"Card name" : "Navn på kort", "Card name" : "Navn på kort",
"List deleted" : "Stabel slettet", "List deleted" : "Stabel slettet",
@@ -268,7 +260,6 @@ OC.L10N.register(
"Shared with you" : "Delt med deg", "Shared with you" : "Delt med deg",
"Deck settings" : "Innstillinger for Stokk", "Deck settings" : "Innstillinger for Stokk",
"Use bigger card view" : "Bruk større visning på kort", "Use bigger card view" : "Bruk større visning på kort",
"Show card ID badge" : "Vis ID-merke til kort",
"Show boards in calendar/tasks" : "Vis tavler i kalender/oppgaver", "Show boards in calendar/tasks" : "Vis tavler i kalender/oppgaver",
"Limit deck usage of groups" : "Begrens stokk-bruk til grupper", "Limit deck usage of groups" : "Begrens stokk-bruk til grupper",
"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." : "Begrensning av tavler vil hindre tilgang til de brukere som ikke er medlem av en gruppe fra å lage egne tavler. Bruker kan arbeide på de tavler som er delt med dem.", "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." : "Begrensning av tavler vil hindre tilgang til de brukere som ikke er medlem av en gruppe fra å lage egne tavler. Bruker kan arbeide på de tavler som er delt med dem.",
@@ -277,7 +268,6 @@ OC.L10N.register(
"Clone board" : "Klon tavle", "Clone board" : "Klon tavle",
"Unarchive board" : "Aktiver tavle", "Unarchive board" : "Aktiver tavle",
"Archive board" : "Arkiver tavle", "Archive board" : "Arkiver tavle",
"Export board" : "Eksporter tavle",
"Turn on due date reminders" : "Skru på påminnelser for forfallsdato", "Turn on due date reminders" : "Skru på påminnelser for forfallsdato",
"Turn off due date reminders" : "Skru av påminnelser for forfallsdato", "Turn off due date reminders" : "Skru av påminnelser for forfallsdato",
"Due date reminders" : "Påminnelser for forfallsdato", "Due date reminders" : "Påminnelser for forfallsdato",
@@ -288,19 +278,17 @@ OC.L10N.register(
"Board {0} deleted" : "Tavle {0} slettet", "Board {0} deleted" : "Tavle {0} slettet",
"Only assigned cards" : "Kun tildelte kort", "Only assigned cards" : "Kun tildelte kort",
"No reminder" : "Ingen varsel", "No reminder" : "Ingen varsel",
"An error occurred" : "En feil oppsto", "An error occurred" : "En feil oppstod",
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Are du sikker på sletting av tavlen {title}? Handlingen vil slette all data i denne tavlen, inkludert arkiverte kort.", "Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Are du sikker på sletting av tavlen {title}? Handlingen vil slette all data i denne tavlen, inkludert arkiverte kort.",
"Delete the board?" : "Slett tavlen?", "Delete the board?" : "Slett tavlen?",
"Loading filtered view" : "Laster filtrert visning", "Loading filtered view" : "Laster filtrert visning",
"No due" : "Ingen forfall", "No due" : "Ingen forfall",
"Search for {searchQuery} in all boards" : "Søk etter {searchQuery} i alle tavler", "Search for {searchQuery} in all boards" : "Søk etter {searchQuery} i alle tavler",
"No results found" : "Ingen resultater funnet", "No results found" : "Ingen resultater funnet",
"Deck board {name}\n* Last modified on {lastMod}" : "Stokktavle {name}\n* Sist endret {lastMod}",
"{stack} in {board}" : "{stack} i {board}", "{stack} in {board}" : "{stack} i {board}",
"Click to expand description" : "Klikk for å utvide beskrivelsen", "Click to expand description" : "Klikk for å utvide beskrivelsen",
"* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Opprettet {created}\n* Sist endret {lastMod}\n* {nbAttachments} vedlegg\n* {nbComments} kommentarer", "* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Opprettet {created}\n* Sist endret {lastMod}\n* {nbAttachments} vedlegg\n* {nbComments} kommentarer",
"{nbCards} cards" : "{nbCards} kort", "{nbCards} cards" : "{nbCards} kort",
"Click to expand comment" : "Klikk for å utvide kommentaren",
"No upcoming cards" : "Ingen kommende kort", "No upcoming cards" : "Ingen kommende kort",
"upcoming cards" : "kommende kort", "upcoming cards" : "kommende kort",
"Due on {date}" : "Utløper {date}", "Due on {date}" : "Utløper {date}",

View File

@@ -76,13 +76,9 @@
"{user} has mentioned you in a comment on {deck-card}." : "{user} har nevnt deg i en kommentar på {deck-card}.", "{user} has mentioned you in a comment on {deck-card}." : "{user} har nevnt deg i en kommentar på {deck-card}.",
"The board \"%s\" has been shared with you by %s." : "Brettet \"%s\" har blitt delt med deg av %s.", "The board \"%s\" has been shared with you by %s." : "Brettet \"%s\" har blitt delt med deg av %s.",
"{user} has shared {deck-board} with you." : "{user} har delt brettet {deck-board} med deg.", "{user} has shared {deck-board} with you." : "{user} har delt brettet {deck-board} med deg.",
"Deck board" : "Stokktavle", "Deck board" : "Deck tavle",
"Owned by %1$s" : "Eid av %1$s",
"Deck boards, cards and comments" : "Stokktavler, kort og kommentarer",
"From %1$s, in %2$s/%3$s, owned by %4$s" : "Fra %1$s, under %2$s/%3$s, eid av %4$s",
"Card comments" : "Kommentarer på kortet", "Card comments" : "Kommentarer på kortet",
"%s on %s" : "%s på %s", "%s on %s" : "%s på %s",
"Deck boards and cards" : "Stokktavler og kort",
"No data was provided to create an attachment." : "Ingen data for å opprette vedlegg.", "No data was provided to create an attachment." : "Ingen data for å opprette vedlegg.",
"Finished" : "Fullført", "Finished" : "Fullført",
"To review" : "Til gjennomlesning", "To review" : "Til gjennomlesning",
@@ -155,7 +151,6 @@
"Toggle compact mode" : "Endre kompakt modus", "Toggle compact mode" : "Endre kompakt modus",
"Open details" : "Åpne detaljer", "Open details" : "Åpne detaljer",
"Details" : "Detaljer", "Details" : "Detaljer",
"Currently present people" : "Tilstedeværende personer for øyeblikket",
"Loading board" : "Laster tavle", "Loading board" : "Laster tavle",
"No lists available" : "Ingen stabler tilgjengelig", "No lists available" : "Ingen stabler tilgjengelig",
"Create a new list to add cards to this board" : "Lag en ny stabel for å legge til kort til denne tavlen", "Create a new list to add cards to this board" : "Lag en ny stabel for å legge til kort til denne tavlen",
@@ -184,12 +179,9 @@
"Transfer" : "Overfør", "Transfer" : "Overfør",
"The board has been transferred to {user}" : "Tavlen har blitt overført til {user}", "The board has been transferred to {user}" : "Tavlen har blitt overført til {user}",
"Failed to transfer the board to {user}" : "Klarte ikke overføre tavlen til {user}", "Failed to transfer the board to {user}" : "Klarte ikke overføre tavlen til {user}",
"Edit list title" : "Rediger listetittel",
"Archive all cards" : "Arkiver alle kort", "Archive all cards" : "Arkiver alle kort",
"Unarchive all cards" : "Fjern alle kort fra arkiv",
"Delete list" : "Slett listen", "Delete list" : "Slett listen",
"Archive all cards in this list" : "Arkiver alle kort i en stabel", "Archive all cards in this list" : "Arkiver alle kort i en stabel",
"Unarchive all cards in this list" : "Fjern alle kortene i denne listen fra arkiv",
"Add a new card" : "Legg til nytt kort", "Add a new card" : "Legg til nytt kort",
"Card name" : "Navn på kort", "Card name" : "Navn på kort",
"List deleted" : "Stabel slettet", "List deleted" : "Stabel slettet",
@@ -266,7 +258,6 @@
"Shared with you" : "Delt med deg", "Shared with you" : "Delt med deg",
"Deck settings" : "Innstillinger for Stokk", "Deck settings" : "Innstillinger for Stokk",
"Use bigger card view" : "Bruk større visning på kort", "Use bigger card view" : "Bruk større visning på kort",
"Show card ID badge" : "Vis ID-merke til kort",
"Show boards in calendar/tasks" : "Vis tavler i kalender/oppgaver", "Show boards in calendar/tasks" : "Vis tavler i kalender/oppgaver",
"Limit deck usage of groups" : "Begrens stokk-bruk til grupper", "Limit deck usage of groups" : "Begrens stokk-bruk til grupper",
"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." : "Begrensning av tavler vil hindre tilgang til de brukere som ikke er medlem av en gruppe fra å lage egne tavler. Bruker kan arbeide på de tavler som er delt med dem.", "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." : "Begrensning av tavler vil hindre tilgang til de brukere som ikke er medlem av en gruppe fra å lage egne tavler. Bruker kan arbeide på de tavler som er delt med dem.",
@@ -275,7 +266,6 @@
"Clone board" : "Klon tavle", "Clone board" : "Klon tavle",
"Unarchive board" : "Aktiver tavle", "Unarchive board" : "Aktiver tavle",
"Archive board" : "Arkiver tavle", "Archive board" : "Arkiver tavle",
"Export board" : "Eksporter tavle",
"Turn on due date reminders" : "Skru på påminnelser for forfallsdato", "Turn on due date reminders" : "Skru på påminnelser for forfallsdato",
"Turn off due date reminders" : "Skru av påminnelser for forfallsdato", "Turn off due date reminders" : "Skru av påminnelser for forfallsdato",
"Due date reminders" : "Påminnelser for forfallsdato", "Due date reminders" : "Påminnelser for forfallsdato",
@@ -286,19 +276,17 @@
"Board {0} deleted" : "Tavle {0} slettet", "Board {0} deleted" : "Tavle {0} slettet",
"Only assigned cards" : "Kun tildelte kort", "Only assigned cards" : "Kun tildelte kort",
"No reminder" : "Ingen varsel", "No reminder" : "Ingen varsel",
"An error occurred" : "En feil oppsto", "An error occurred" : "En feil oppstod",
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Are du sikker på sletting av tavlen {title}? Handlingen vil slette all data i denne tavlen, inkludert arkiverte kort.", "Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Are du sikker på sletting av tavlen {title}? Handlingen vil slette all data i denne tavlen, inkludert arkiverte kort.",
"Delete the board?" : "Slett tavlen?", "Delete the board?" : "Slett tavlen?",
"Loading filtered view" : "Laster filtrert visning", "Loading filtered view" : "Laster filtrert visning",
"No due" : "Ingen forfall", "No due" : "Ingen forfall",
"Search for {searchQuery} in all boards" : "Søk etter {searchQuery} i alle tavler", "Search for {searchQuery} in all boards" : "Søk etter {searchQuery} i alle tavler",
"No results found" : "Ingen resultater funnet", "No results found" : "Ingen resultater funnet",
"Deck board {name}\n* Last modified on {lastMod}" : "Stokktavle {name}\n* Sist endret {lastMod}",
"{stack} in {board}" : "{stack} i {board}", "{stack} in {board}" : "{stack} i {board}",
"Click to expand description" : "Klikk for å utvide beskrivelsen", "Click to expand description" : "Klikk for å utvide beskrivelsen",
"* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Opprettet {created}\n* Sist endret {lastMod}\n* {nbAttachments} vedlegg\n* {nbComments} kommentarer", "* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Opprettet {created}\n* Sist endret {lastMod}\n* {nbAttachments} vedlegg\n* {nbComments} kommentarer",
"{nbCards} cards" : "{nbCards} kort", "{nbCards} cards" : "{nbCards} kort",
"Click to expand comment" : "Klikk for å utvide kommentaren",
"No upcoming cards" : "Ingen kommende kort", "No upcoming cards" : "Ingen kommende kort",
"upcoming cards" : "kommende kort", "upcoming cards" : "kommende kort",
"Due on {date}" : "Utløper {date}", "Due on {date}" : "Utløper {date}",

View File

@@ -79,11 +79,8 @@ OC.L10N.register(
"The board \"%s\" has been shared with you by %s." : "Вам предоставлен доступ к рабочей доске «%s» пользователем %s.", "The board \"%s\" has been shared with you by %s." : "Вам предоставлен доступ к рабочей доске «%s» пользователем %s.",
"{user} has shared {deck-board} with you." : "{user} предоставил(а) вам доступ к {deck-board}.", "{user} has shared {deck-board} with you." : "{user} предоставил(а) вам доступ к {deck-board}.",
"Deck board" : "Доска", "Deck board" : "Доска",
"Owned by %1$s" : "Владелец: %1$s",
"Deck boards, cards and comments" : "Доски, карточки и комментарии",
"Card comments" : "Комментарии карточки", "Card comments" : "Комментарии карточки",
"%s on %s" : "%s на %s", "%s on %s" : "%s на %s",
"Deck boards and cards" : "Доски и карточки",
"No data was provided to create an attachment." : "Отсутствуют данные для создания вложения.", "No data was provided to create an attachment." : "Отсутствуют данные для создания вложения.",
"Finished" : "Завершено", "Finished" : "Завершено",
"To review" : "Проверить", "To review" : "Проверить",
@@ -184,7 +181,6 @@ OC.L10N.register(
"Transfer" : "Передача", "Transfer" : "Передача",
"The board has been transferred to {user}" : "Доска была передана пользователю {user}", "The board has been transferred to {user}" : "Доска была передана пользователю {user}",
"Failed to transfer the board to {user}" : "Не удалось передать доску пользователю {user}", "Failed to transfer the board to {user}" : "Не удалось передать доску пользователю {user}",
"Edit list title" : "Изменить название списка",
"Archive all cards" : "Переместить все карточки в архив", "Archive all cards" : "Переместить все карточки в архив",
"Unarchive all cards" : "Восстановить все карточки из архива", "Unarchive all cards" : "Восстановить все карточки из архива",
"Delete list" : "Удалить список", "Delete list" : "Удалить список",
@@ -275,7 +271,6 @@ OC.L10N.register(
"Clone board" : "Скопировать доску", "Clone board" : "Скопировать доску",
"Unarchive board" : "Восстановить доску из архива", "Unarchive board" : "Восстановить доску из архива",
"Archive board" : "Переместить доску в архив", "Archive board" : "Переместить доску в архив",
"Export board" : "Экспортировать доску",
"Turn on due date reminders" : "Включить напоминания о сроке выполнения", "Turn on due date reminders" : "Включить напоминания о сроке выполнения",
"Turn off due date reminders" : "Отключить напоминания о сроке выполнения", "Turn off due date reminders" : "Отключить напоминания о сроке выполнения",
"Due date reminders" : "Напоминания о сроке выполнения", "Due date reminders" : "Напоминания о сроке выполнения",
@@ -293,14 +288,11 @@ OC.L10N.register(
"No due" : "Без назначенной даты", "No due" : "Без назначенной даты",
"Search for {searchQuery} in all boards" : "Искать {searchQuery} на всех досках", "Search for {searchQuery} in all boards" : "Искать {searchQuery} на всех досках",
"No results found" : "Результаты отсутствуют", "No results found" : "Результаты отсутствуют",
"Deck board {name}\n* Last modified on {lastMod}" : "Доска «{name}»\n* Последнее изменение: {lastMod}",
"{stack} in {board}" : "«{stack}» с доски «{board}»", "{stack} in {board}" : "«{stack}» с доски «{board}»",
"Click to expand description" : "Нажмите, чтобы развернуть поле описания", "Click to expand description" : "Нажмите, чтобы развернуть поле описания",
"{nbCards} cards" : "карточек: {nbCards}", "{nbCards} cards" : "карточек: {nbCards}",
"Click to expand comment" : "Нажмите, чтобы развернуть комментарии",
"No upcoming cards" : "Отсутствуют карточки, ожидающие выполнения", "No upcoming cards" : "Отсутствуют карточки, ожидающие выполнения",
"upcoming cards" : "карточки, ожидающие выполнения", "upcoming cards" : "карточки, ожидающие выполнения",
"Due on {date}" : "Дата исполнения: {date}",
"Link to a board" : "Ссылка на доску", "Link to a board" : "Ссылка на доску",
"Link to a card" : "Ссылка на карточку", "Link to a card" : "Ссылка на карточку",
"Create a card" : "Создать карточку", "Create a card" : "Создать карточку",

View File

@@ -77,11 +77,8 @@
"The board \"%s\" has been shared with you by %s." : "Вам предоставлен доступ к рабочей доске «%s» пользователем %s.", "The board \"%s\" has been shared with you by %s." : "Вам предоставлен доступ к рабочей доске «%s» пользователем %s.",
"{user} has shared {deck-board} with you." : "{user} предоставил(а) вам доступ к {deck-board}.", "{user} has shared {deck-board} with you." : "{user} предоставил(а) вам доступ к {deck-board}.",
"Deck board" : "Доска", "Deck board" : "Доска",
"Owned by %1$s" : "Владелец: %1$s",
"Deck boards, cards and comments" : "Доски, карточки и комментарии",
"Card comments" : "Комментарии карточки", "Card comments" : "Комментарии карточки",
"%s on %s" : "%s на %s", "%s on %s" : "%s на %s",
"Deck boards and cards" : "Доски и карточки",
"No data was provided to create an attachment." : "Отсутствуют данные для создания вложения.", "No data was provided to create an attachment." : "Отсутствуют данные для создания вложения.",
"Finished" : "Завершено", "Finished" : "Завершено",
"To review" : "Проверить", "To review" : "Проверить",
@@ -182,7 +179,6 @@
"Transfer" : "Передача", "Transfer" : "Передача",
"The board has been transferred to {user}" : "Доска была передана пользователю {user}", "The board has been transferred to {user}" : "Доска была передана пользователю {user}",
"Failed to transfer the board to {user}" : "Не удалось передать доску пользователю {user}", "Failed to transfer the board to {user}" : "Не удалось передать доску пользователю {user}",
"Edit list title" : "Изменить название списка",
"Archive all cards" : "Переместить все карточки в архив", "Archive all cards" : "Переместить все карточки в архив",
"Unarchive all cards" : "Восстановить все карточки из архива", "Unarchive all cards" : "Восстановить все карточки из архива",
"Delete list" : "Удалить список", "Delete list" : "Удалить список",
@@ -273,7 +269,6 @@
"Clone board" : "Скопировать доску", "Clone board" : "Скопировать доску",
"Unarchive board" : "Восстановить доску из архива", "Unarchive board" : "Восстановить доску из архива",
"Archive board" : "Переместить доску в архив", "Archive board" : "Переместить доску в архив",
"Export board" : "Экспортировать доску",
"Turn on due date reminders" : "Включить напоминания о сроке выполнения", "Turn on due date reminders" : "Включить напоминания о сроке выполнения",
"Turn off due date reminders" : "Отключить напоминания о сроке выполнения", "Turn off due date reminders" : "Отключить напоминания о сроке выполнения",
"Due date reminders" : "Напоминания о сроке выполнения", "Due date reminders" : "Напоминания о сроке выполнения",
@@ -291,14 +286,11 @@
"No due" : "Без назначенной даты", "No due" : "Без назначенной даты",
"Search for {searchQuery} in all boards" : "Искать {searchQuery} на всех досках", "Search for {searchQuery} in all boards" : "Искать {searchQuery} на всех досках",
"No results found" : "Результаты отсутствуют", "No results found" : "Результаты отсутствуют",
"Deck board {name}\n* Last modified on {lastMod}" : "Доска «{name}»\n* Последнее изменение: {lastMod}",
"{stack} in {board}" : "«{stack}» с доски «{board}»", "{stack} in {board}" : "«{stack}» с доски «{board}»",
"Click to expand description" : "Нажмите, чтобы развернуть поле описания", "Click to expand description" : "Нажмите, чтобы развернуть поле описания",
"{nbCards} cards" : "карточек: {nbCards}", "{nbCards} cards" : "карточек: {nbCards}",
"Click to expand comment" : "Нажмите, чтобы развернуть комментарии",
"No upcoming cards" : "Отсутствуют карточки, ожидающие выполнения", "No upcoming cards" : "Отсутствуют карточки, ожидающие выполнения",
"upcoming cards" : "карточки, ожидающие выполнения", "upcoming cards" : "карточки, ожидающие выполнения",
"Due on {date}" : "Дата исполнения: {date}",
"Link to a board" : "Ссылка на доску", "Link to a board" : "Ссылка на доску",
"Link to a card" : "Ссылка на карточку", "Link to a card" : "Ссылка на карточку",
"Create a card" : "Создать карточку", "Create a card" : "Создать карточку",

View File

@@ -71,20 +71,11 @@ OC.L10N.register(
"Load more" : "Учитај још", "Load more" : "Учитај још",
"Personal" : "Лично", "Personal" : "Лично",
"The card \"%s\" on \"%s\" has been assigned to you by %s." : "Корисник %s Вам је доделио картицу „%s“ са табле „%s“.", "The card \"%s\" on \"%s\" has been assigned to you by %s." : "Корисник %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 \"%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“.", "%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“.", "The board \"%s\" has been shared with you by %s." : "Корисник „%s“ је поделио са Вама таблу „%s“.",
"{user} has shared {deck-board} with you." : "{user} је са вама поделио {deck-board}.",
"Deck board" : "Табла Шпила", "Deck board" : "Табла Шпила",
"Owned by %1$s" : "Власник је %1$s",
"Deck boards, cards and comments" : "Табле шпилова, картице и коментари",
"From %1$s, in %2$s/%3$s, owned by %4$s" : "Од %1$s, у %2$s/%3$s, власник је %4$s",
"Card comments" : "Коментари картица",
"%s on %s" : "%s на %s", "%s on %s" : "%s на %s",
"Deck boards and cards" : "Табле шпилова и картице",
"No data was provided to create an attachment." : "Нису дати подаци за креирање прилога.", "No data was provided to create an attachment." : "Нису дати подаци за креирање прилога.",
"Finished" : "Завршено", "Finished" : "Завршено",
"To review" : "Треба прегледати", "To review" : "Треба прегледати",
@@ -106,24 +97,16 @@ OC.L10N.register(
"Could not write file to disk" : "Не могу да пишем на диск", "Could not write file to disk" : "Не могу да пишем на диск",
"A PHP extension stopped the file upload" : "PHP екстензија је зауставила отпремање фајла", "A PHP extension stopped the file upload" : "PHP екстензија је зауставила отпремање фајла",
"No file uploaded or file size exceeds maximum of %s" : "Ниједан фајл није отпремљен или величина фајла премашује максимум од %s", "No file uploaded or file size exceeds maximum of %s" : "Ниједан фајл није отпремљен или величина фајла премашује максимум од %s",
"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" : "Картица није нађена", "Card not found" : "Картица није нађена",
"Path is already shared with this card" : "Путања се већ дели са овом картицом",
"Invalid date, date format must be YYYY-MM-DD" : "Неисправан датим, формат датума мора бити ГГГГ-ММ-ДД", "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" : "Шпил је организациони алат у канбан стилу који је намењен личном планирању и организацији пројекта у тимовима интегрисаним са Nextcloud.\n\n\n- 📥 Додајте своје задатке у картице и поређајте их по редоследу\n- 📄 Напишите додатне белешке употребом Markdown синтаксе\n- 🔖 Доделите ознаке у циљу још боље организације\n- 👥 Делите са својим тимом, пријатељима или породицом\n- 📎 Прикачите фајлове и уградите их у свој Markdown опис\n- 💬 Дискутујте са својим тимом користећи коментаре\n- ⚡ Пратите измене у току активности\n- 🚀 Организујте свој пројекат",
"Add board" : "Додај таблу", "Add board" : "Додај таблу",
"Select the board to link to a project" : "Одаберите таблу да је повежете са пројектом", "Select the board to link to a project" : "Одаберите таблу да је повежете са пројектом",
"Search by board title" : "Претражи по наслову табле", "Search by board title" : "Претражи по наслову табле",
"Select board" : "Одаберите таблу", "Select board" : "Одаберите таблу",
"Create a new card" : "Креирај нову картицу",
"Select a board" : "Изаберите таблу", "Select a board" : "Изаберите таблу",
"Select a list" : "Одабери списак", "Select a list" : "Одабери списак",
"Card title" : "Наслов картицњ",
"Cancel" : "Одустани", "Cancel" : "Одустани",
"Creating the new card …" : "Креира се нова картица ...",
"Card \"{card}\" was added to \"{board}\"" : "Картица „{card}” је додта на „{board}",
"Open card" : "Отвори картицу",
"Close" : "Затвори", "Close" : "Затвори",
"Create card" : "Направите картицу", "Create card" : "Направите картицу",
"Select a card" : "Изаберите картицу", "Select a card" : "Изаберите картицу",
@@ -140,7 +123,6 @@ OC.L10N.register(
"Archived cards" : "Архивиране картице", "Archived cards" : "Архивиране картице",
"Add list" : "Додај списак", "Add list" : "Додај списак",
"List name" : "Назив листе", "List name" : "Назив листе",
"Active filters" : "Активни филтери",
"Apply filter" : "Примени филтер", "Apply filter" : "Примени филтер",
"Filter by tag" : "Филтрирај по ознаци", "Filter by tag" : "Филтрирај по ознаци",
"Filter by assigned user" : "Филтрирај по додељеном кориснику", "Filter by assigned user" : "Филтрирај по додељеном кориснику",
@@ -157,7 +139,6 @@ OC.L10N.register(
"Toggle compact mode" : "Укључи/искључи компактни режим", "Toggle compact mode" : "Укључи/искључи компактни режим",
"Open details" : "Отвори детаље", "Open details" : "Отвори детаље",
"Details" : "Детаљи", "Details" : "Детаљи",
"Currently present people" : "Тренутно присутне особе",
"Loading board" : "Учитавам таблу", "Loading board" : "Учитавам таблу",
"No lists available" : "Нема доступних спискова", "No lists available" : "Нема доступних спискова",
"Create a new list to add cards to this board" : "Направите нови списак да додате картице на ову таблу", "Create a new list to add cards to this board" : "Направите нови списак да додате картице на ову таблу",
@@ -170,7 +151,6 @@ OC.L10N.register(
"Undo" : "Опозови", "Undo" : "Опозови",
"Deleted cards" : "Обрисане картице", "Deleted cards" : "Обрисане картице",
"Share board with a user, group or circle …" : "Подели таблу са корисником, групом или кругом…", "Share board with a user, group or circle …" : "Подели таблу са корисником, групом или кругом…",
"Searching for users, groups and circles …" : "Претрага корисника, група и кругова ...",
"No participants found" : "Нема нађених учесника", "No participants found" : "Нема нађених учесника",
"Board owner" : "Власник табле", "Board owner" : "Власник табле",
"(Group)" : "(група)", "(Group)" : "(група)",
@@ -181,17 +161,10 @@ OC.L10N.register(
"Owner" : "Власник", "Owner" : "Власник",
"Delete" : "Избриши", "Delete" : "Избриши",
"Failed to create share with {displayName}" : "Грешка у прављењу дељења са {displayName}", "Failed to create share with {displayName}" : "Грешка у прављењу дељења са {displayName}",
"Are you sure you want to transfer the board {title} to {user}?" : "Да ли сте сигурни да кориснику {user} пренесете таблу {title}?",
"Transfer the board." : "Пренос табле.",
"Transfer" : "Пренеси", "Transfer" : "Пренеси",
"The board has been transferred to {user}" : "Табла је пренета кориснику {user}",
"Failed to transfer the board to {user}" : "Није успео пренос табле кориснику {user}",
"Edit list title" : "Уреди наслов листе",
"Archive all cards" : "Архивирај све картице", "Archive all cards" : "Архивирај све картице",
"Unarchive all cards" : "Врати све картице из архиве",
"Delete list" : "Обриши списак", "Delete list" : "Обриши списак",
"Archive all cards in this list" : "Архивирај све картице са овог списка", "Archive all cards in this list" : "Архивирај све картице са овог списка",
"Unarchive all cards in this list" : "Враћа из архиве све картице у овој листи",
"Add a new card" : "Додај нову картицу", "Add a new card" : "Додај нову картицу",
"Card name" : "Име картице", "Card name" : "Име картице",
"List deleted" : "Листа обрисана", "List deleted" : "Листа обрисана",
@@ -200,13 +173,8 @@ OC.L10N.register(
"title and color value must be provided" : "морају се дати вредности за наслов и боју", "title and color value must be provided" : "морају се дати вредности за наслов и боју",
"Board name" : "Име табле", "Board name" : "Име табле",
"Members" : "Чланови", "Members" : "Чланови",
"Upload new files" : "Отпреми нове фајлове",
"Share from Files" : "Подели из Фајлова",
"Pending share" : "Дељење на чекању",
"Add this attachment" : "Додај овај прилог", "Add this attachment" : "Додај овај прилог",
"Show in Files" : "Прикажи у Фајловима",
"Download" : "Преузми", "Download" : "Преузми",
"Remove attachment" : "Уклони прилог",
"Delete Attachment" : "Обриши прилог", "Delete Attachment" : "Обриши прилог",
"Restore Attachment" : "Поврати прилог", "Restore Attachment" : "Поврати прилог",
"File to share" : "Фајл за дељење", "File to share" : "Фајл за дељење",
@@ -219,7 +187,6 @@ OC.L10N.register(
"Created" : "Направљен", "Created" : "Направљен",
"The title cannot be empty." : "Наслов не може бити празан.", "The title cannot be empty." : "Наслов не може бити празан.",
"No comments yet. Begin the discussion!" : "Нема још коментара. Започните дискусију!", "No comments yet. Begin the discussion!" : "Нема још коментара. Започните дискусију!",
"Failed to load comments" : "Није успело учитавање коментара",
"Assign a tag to this card…" : "Додели ознаку овој картици…", "Assign a tag to this card…" : "Додели ознаку овој картици…",
"Assign to users" : "Додели корисницима", "Assign to users" : "Додели корисницима",
"Assign to users/groups/circles" : "Додели корисницима/групама/круговима", "Assign to users/groups/circles" : "Додели корисницима/групама/круговима",
@@ -230,13 +197,10 @@ OC.L10N.register(
"Select Date" : "Одаберите датум", "Select Date" : "Одаберите датум",
"Today" : "Данас", "Today" : "Данас",
"Tomorrow" : "сутра", "Tomorrow" : "сутра",
"Next week" : "Наредне недеље",
"Next month" : "Наредног месеца",
"Save" : "Сачувај", "Save" : "Сачувај",
"The comment cannot be empty." : "Коментар не може да буде празан.", "The comment cannot be empty." : "Коментар не може да буде празан.",
"The comment cannot be longer than 1000 characters." : "Коментар не може да има преко 1000 карактера.", "The comment cannot be longer than 1000 characters." : "Коментар не може да има преко 1000 карактера.",
"In reply to" : "Као одговор на", "In reply to" : "Као одговор на",
"Cancel reply" : "Откажи одговор",
"Reply" : "Одговори", "Reply" : "Одговори",
"Update" : "Ажурирај", "Update" : "Ажурирај",
"Description" : "Опис", "Description" : "Опис",
@@ -246,12 +210,8 @@ OC.L10N.register(
"Edit description" : "Измени опис", "Edit description" : "Измени опис",
"View description" : "Погледај опис", "View description" : "Погледај опис",
"Add Attachment" : "Додај прилог", "Add Attachment" : "Додај прилог",
"Write a description …" : "Напишите опис ...",
"Choose attachment" : "Одабери прилог", "Choose attachment" : "Одабери прилог",
"(group)" : "(група)", "(group)" : "(група)",
"Todo items" : "Ставке обавеза",
"{count} comments, {unread} unread" : "{count} коментара, {unread} није прочитано",
"Edit card title" : "Уреди наслов картице",
"Assign to me" : "Додели мени", "Assign to me" : "Додели мени",
"Unassign myself" : "Склони са мене", "Unassign myself" : "Склони са мене",
"Move card" : "Премести картицу", "Move card" : "Премести картицу",
@@ -266,9 +226,6 @@ OC.L10N.register(
"All boards" : "Све табле", "All boards" : "Све табле",
"Archived boards" : "Архивиране табле", "Archived boards" : "Архивиране табле",
"Shared with you" : "Дељено са Вама", "Shared with you" : "Дељено са Вама",
"Deck settings" : "Поставке Шпила",
"Use bigger card view" : "Користи већи приказ картице",
"Show card ID badge" : "Прикажи беџ ID картице",
"Show boards in calendar/tasks" : "Прикажи табле у календару/задацима", "Show boards in calendar/tasks" : "Прикажи табле у календару/задацима",
"Limit deck usage of groups" : "Ограничи употребу шпила на групе", "Limit deck usage of groups" : "Ограничи употребу шпила на групе",
"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 апликације ће блокирати кориснике који нису део одабраних група да креирају своје табле. Корисници ће и даље моћи да раде на таблама које су подељене са њима.", "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 апликације ће блокирати кориснике који нису део одабраних група да креирају своје табле. Корисници ће и даље моћи да раде на таблама које су подељене са њима.",
@@ -277,47 +234,22 @@ OC.L10N.register(
"Clone board" : "Клонирај таблу", "Clone board" : "Клонирај таблу",
"Unarchive board" : "Врати таблу из архиве", "Unarchive board" : "Врати таблу из архиве",
"Archive board" : "Архивирај таблу", "Archive board" : "Архивирај таблу",
"Export board" : "Извези таблу",
"Turn on due date reminders" : "Укључи подсетнике о року",
"Turn off due date reminders" : "Искључи подсетнике о року",
"Due date reminders" : "Подсетници о року",
"All cards" : "Све картице",
"Assigned cards" : "Додељене картице",
"No notifications" : "Нема обавештења", "No notifications" : "Нема обавештења",
"Delete board" : "Избриши таблу", "Delete board" : "Избриши таблу",
"Board {0} deleted" : "Табла {0} обрисана", "Board {0} deleted" : "Табла {0} обрисана",
"Only assigned cards" : "Само додељене картице",
"No reminder" : "Нема подсетника",
"An error occurred" : "Догодила се грешка", "An error occurred" : "Догодила се грешка",
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Да ли сте сигурни да желите да обришете таблу {title}? Ово ће да обрише све податке на овој табли, укључијући и архивиране картице.",
"Delete the board?" : "Обрисати таблу?", "Delete the board?" : "Обрисати таблу?",
"Loading filtered view" : "Учитам филтрирани преглед", "Loading filtered view" : "Учитам филтрирани преглед",
"No due" : "Нема рокова", "No due" : "Нема рокова",
"Search for {searchQuery} in all boards" : "Тражи се {searchQuery} у свим таблама",
"No results found" : "Нема пронађених резултата", "No results found" : "Нема пронађених резултата",
"Deck board {name}\n* Last modified on {lastMod}" : "Шпил табла {name}\n* Последњи пут измењена дана {lastMod}",
"{stack} in {board}" : "{stack} у {board}",
"Click to expand description" : "Кликните да проширите опис",
"* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Креирано дана {created}\n* Последњи пут измењено дана {lastMod}\n* {nbAttachments} прилога\n* {nbComments} коментара",
"{nbCards} cards" : "{nbCards} картица",
"Click to expand comment" : "Кликните да проширите коментар",
"No upcoming cards" : "Нема предстојећих картица", "No upcoming cards" : "Нема предстојећих картица",
"upcoming cards" : "предстојеће картице", "upcoming cards" : "предстојеће картице",
"Due on {date}" : "Рок је {date}",
"Link to a board" : "Веза ка табли", "Link to a board" : "Веза ка табли",
"Link to a card" : "Веза ка картици", "Link to a card" : "Веза ка картици",
"Create a card" : "Креирај картицу",
"Message from {author} in {conversationName}" : "Порука од {author} у {conversationName}",
"Something went wrong" : "Нешто је пошло наопако", "Something went wrong" : "Нешто је пошло наопако",
"Failed to upload {name}" : "Није успело отпремање {name}",
"Maximum file size of {size} exceeded" : "Премашена максимална величина фајла од {size}", "Maximum file size of {size} exceeded" : "Премашена максимална величина фајла од {size}",
"Error creating the share" : "Грешка при прављењу дељења", "Error creating the share" : "Грешка при прављењу дељења",
"Share with a Deck card" : "Дели са Шпил картицом",
"Share {file} with a Deck card" : "Дели {file} са Шпил картицом",
"Share" : "Подели", "Share" : "Подели",
"Are you sure you want to transfer the board {title} for {user}?" : "Да ли сте сигурни да желите пренети таблу {title} за {user}?",
"Transfer the board for {user} successfully" : "Пренос табле за {user} је успео",
"Failed to transfer the board for {user}" : "Пренос табле за {user} није успео",
"Add a new list" : "Додај нови списак", "Add a new list" : "Додај нови списак",
"Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Да ли стварно желите да обришете таблу {title}? Овим ћете обрисати све податке са табле." "Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Да ли стварно желите да обришете таблу {title}? Овим ћете обрисати све податке са табле."
}, },

View File

@@ -69,20 +69,11 @@
"Load more" : "Учитај још", "Load more" : "Учитај још",
"Personal" : "Лично", "Personal" : "Лично",
"The card \"%s\" on \"%s\" has been assigned to you by %s." : "Корисник %s Вам је доделио картицу „%s“ са табле „%s“.", "The card \"%s\" on \"%s\" has been assigned to you by %s." : "Корисник %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 \"%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“.", "%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“.", "The board \"%s\" has been shared with you by %s." : "Корисник „%s“ је поделио са Вама таблу „%s“.",
"{user} has shared {deck-board} with you." : "{user} је са вама поделио {deck-board}.",
"Deck board" : "Табла Шпила", "Deck board" : "Табла Шпила",
"Owned by %1$s" : "Власник је %1$s",
"Deck boards, cards and comments" : "Табле шпилова, картице и коментари",
"From %1$s, in %2$s/%3$s, owned by %4$s" : "Од %1$s, у %2$s/%3$s, власник је %4$s",
"Card comments" : "Коментари картица",
"%s on %s" : "%s на %s", "%s on %s" : "%s на %s",
"Deck boards and cards" : "Табле шпилова и картице",
"No data was provided to create an attachment." : "Нису дати подаци за креирање прилога.", "No data was provided to create an attachment." : "Нису дати подаци за креирање прилога.",
"Finished" : "Завршено", "Finished" : "Завршено",
"To review" : "Треба прегледати", "To review" : "Треба прегледати",
@@ -104,24 +95,16 @@
"Could not write file to disk" : "Не могу да пишем на диск", "Could not write file to disk" : "Не могу да пишем на диск",
"A PHP extension stopped the file upload" : "PHP екстензија је зауставила отпремање фајла", "A PHP extension stopped the file upload" : "PHP екстензија је зауставила отпремање фајла",
"No file uploaded or file size exceeds maximum of %s" : "Ниједан фајл није отпремљен или величина фајла премашује максимум од %s", "No file uploaded or file size exceeds maximum of %s" : "Ниједан фајл није отпремљен или величина фајла премашује максимум од %s",
"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" : "Картица није нађена", "Card not found" : "Картица није нађена",
"Path is already shared with this card" : "Путања се већ дели са овом картицом",
"Invalid date, date format must be YYYY-MM-DD" : "Неисправан датим, формат датума мора бити ГГГГ-ММ-ДД", "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" : "Шпил је организациони алат у канбан стилу који је намењен личном планирању и организацији пројекта у тимовима интегрисаним са Nextcloud.\n\n\n- 📥 Додајте своје задатке у картице и поређајте их по редоследу\n- 📄 Напишите додатне белешке употребом Markdown синтаксе\n- 🔖 Доделите ознаке у циљу још боље организације\n- 👥 Делите са својим тимом, пријатељима или породицом\n- 📎 Прикачите фајлове и уградите их у свој Markdown опис\n- 💬 Дискутујте са својим тимом користећи коментаре\n- ⚡ Пратите измене у току активности\n- 🚀 Организујте свој пројекат",
"Add board" : "Додај таблу", "Add board" : "Додај таблу",
"Select the board to link to a project" : "Одаберите таблу да је повежете са пројектом", "Select the board to link to a project" : "Одаберите таблу да је повежете са пројектом",
"Search by board title" : "Претражи по наслову табле", "Search by board title" : "Претражи по наслову табле",
"Select board" : "Одаберите таблу", "Select board" : "Одаберите таблу",
"Create a new card" : "Креирај нову картицу",
"Select a board" : "Изаберите таблу", "Select a board" : "Изаберите таблу",
"Select a list" : "Одабери списак", "Select a list" : "Одабери списак",
"Card title" : "Наслов картицњ",
"Cancel" : "Одустани", "Cancel" : "Одустани",
"Creating the new card …" : "Креира се нова картица ...",
"Card \"{card}\" was added to \"{board}\"" : "Картица „{card}” је додта на „{board}",
"Open card" : "Отвори картицу",
"Close" : "Затвори", "Close" : "Затвори",
"Create card" : "Направите картицу", "Create card" : "Направите картицу",
"Select a card" : "Изаберите картицу", "Select a card" : "Изаберите картицу",
@@ -138,7 +121,6 @@
"Archived cards" : "Архивиране картице", "Archived cards" : "Архивиране картице",
"Add list" : "Додај списак", "Add list" : "Додај списак",
"List name" : "Назив листе", "List name" : "Назив листе",
"Active filters" : "Активни филтери",
"Apply filter" : "Примени филтер", "Apply filter" : "Примени филтер",
"Filter by tag" : "Филтрирај по ознаци", "Filter by tag" : "Филтрирај по ознаци",
"Filter by assigned user" : "Филтрирај по додељеном кориснику", "Filter by assigned user" : "Филтрирај по додељеном кориснику",
@@ -155,7 +137,6 @@
"Toggle compact mode" : "Укључи/искључи компактни режим", "Toggle compact mode" : "Укључи/искључи компактни режим",
"Open details" : "Отвори детаље", "Open details" : "Отвори детаље",
"Details" : "Детаљи", "Details" : "Детаљи",
"Currently present people" : "Тренутно присутне особе",
"Loading board" : "Учитавам таблу", "Loading board" : "Учитавам таблу",
"No lists available" : "Нема доступних спискова", "No lists available" : "Нема доступних спискова",
"Create a new list to add cards to this board" : "Направите нови списак да додате картице на ову таблу", "Create a new list to add cards to this board" : "Направите нови списак да додате картице на ову таблу",
@@ -168,7 +149,6 @@
"Undo" : "Опозови", "Undo" : "Опозови",
"Deleted cards" : "Обрисане картице", "Deleted cards" : "Обрисане картице",
"Share board with a user, group or circle …" : "Подели таблу са корисником, групом или кругом…", "Share board with a user, group or circle …" : "Подели таблу са корисником, групом или кругом…",
"Searching for users, groups and circles …" : "Претрага корисника, група и кругова ...",
"No participants found" : "Нема нађених учесника", "No participants found" : "Нема нађених учесника",
"Board owner" : "Власник табле", "Board owner" : "Власник табле",
"(Group)" : "(група)", "(Group)" : "(група)",
@@ -179,17 +159,10 @@
"Owner" : "Власник", "Owner" : "Власник",
"Delete" : "Избриши", "Delete" : "Избриши",
"Failed to create share with {displayName}" : "Грешка у прављењу дељења са {displayName}", "Failed to create share with {displayName}" : "Грешка у прављењу дељења са {displayName}",
"Are you sure you want to transfer the board {title} to {user}?" : "Да ли сте сигурни да кориснику {user} пренесете таблу {title}?",
"Transfer the board." : "Пренос табле.",
"Transfer" : "Пренеси", "Transfer" : "Пренеси",
"The board has been transferred to {user}" : "Табла је пренета кориснику {user}",
"Failed to transfer the board to {user}" : "Није успео пренос табле кориснику {user}",
"Edit list title" : "Уреди наслов листе",
"Archive all cards" : "Архивирај све картице", "Archive all cards" : "Архивирај све картице",
"Unarchive all cards" : "Врати све картице из архиве",
"Delete list" : "Обриши списак", "Delete list" : "Обриши списак",
"Archive all cards in this list" : "Архивирај све картице са овог списка", "Archive all cards in this list" : "Архивирај све картице са овог списка",
"Unarchive all cards in this list" : "Враћа из архиве све картице у овој листи",
"Add a new card" : "Додај нову картицу", "Add a new card" : "Додај нову картицу",
"Card name" : "Име картице", "Card name" : "Име картице",
"List deleted" : "Листа обрисана", "List deleted" : "Листа обрисана",
@@ -198,13 +171,8 @@
"title and color value must be provided" : "морају се дати вредности за наслов и боју", "title and color value must be provided" : "морају се дати вредности за наслов и боју",
"Board name" : "Име табле", "Board name" : "Име табле",
"Members" : "Чланови", "Members" : "Чланови",
"Upload new files" : "Отпреми нове фајлове",
"Share from Files" : "Подели из Фајлова",
"Pending share" : "Дељење на чекању",
"Add this attachment" : "Додај овај прилог", "Add this attachment" : "Додај овај прилог",
"Show in Files" : "Прикажи у Фајловима",
"Download" : "Преузми", "Download" : "Преузми",
"Remove attachment" : "Уклони прилог",
"Delete Attachment" : "Обриши прилог", "Delete Attachment" : "Обриши прилог",
"Restore Attachment" : "Поврати прилог", "Restore Attachment" : "Поврати прилог",
"File to share" : "Фајл за дељење", "File to share" : "Фајл за дељење",
@@ -217,7 +185,6 @@
"Created" : "Направљен", "Created" : "Направљен",
"The title cannot be empty." : "Наслов не може бити празан.", "The title cannot be empty." : "Наслов не може бити празан.",
"No comments yet. Begin the discussion!" : "Нема још коментара. Започните дискусију!", "No comments yet. Begin the discussion!" : "Нема још коментара. Започните дискусију!",
"Failed to load comments" : "Није успело учитавање коментара",
"Assign a tag to this card…" : "Додели ознаку овој картици…", "Assign a tag to this card…" : "Додели ознаку овој картици…",
"Assign to users" : "Додели корисницима", "Assign to users" : "Додели корисницима",
"Assign to users/groups/circles" : "Додели корисницима/групама/круговима", "Assign to users/groups/circles" : "Додели корисницима/групама/круговима",
@@ -228,13 +195,10 @@
"Select Date" : "Одаберите датум", "Select Date" : "Одаберите датум",
"Today" : "Данас", "Today" : "Данас",
"Tomorrow" : "сутра", "Tomorrow" : "сутра",
"Next week" : "Наредне недеље",
"Next month" : "Наредног месеца",
"Save" : "Сачувај", "Save" : "Сачувај",
"The comment cannot be empty." : "Коментар не може да буде празан.", "The comment cannot be empty." : "Коментар не може да буде празан.",
"The comment cannot be longer than 1000 characters." : "Коментар не може да има преко 1000 карактера.", "The comment cannot be longer than 1000 characters." : "Коментар не може да има преко 1000 карактера.",
"In reply to" : "Као одговор на", "In reply to" : "Као одговор на",
"Cancel reply" : "Откажи одговор",
"Reply" : "Одговори", "Reply" : "Одговори",
"Update" : "Ажурирај", "Update" : "Ажурирај",
"Description" : "Опис", "Description" : "Опис",
@@ -244,12 +208,8 @@
"Edit description" : "Измени опис", "Edit description" : "Измени опис",
"View description" : "Погледај опис", "View description" : "Погледај опис",
"Add Attachment" : "Додај прилог", "Add Attachment" : "Додај прилог",
"Write a description …" : "Напишите опис ...",
"Choose attachment" : "Одабери прилог", "Choose attachment" : "Одабери прилог",
"(group)" : "(група)", "(group)" : "(група)",
"Todo items" : "Ставке обавеза",
"{count} comments, {unread} unread" : "{count} коментара, {unread} није прочитано",
"Edit card title" : "Уреди наслов картице",
"Assign to me" : "Додели мени", "Assign to me" : "Додели мени",
"Unassign myself" : "Склони са мене", "Unassign myself" : "Склони са мене",
"Move card" : "Премести картицу", "Move card" : "Премести картицу",
@@ -264,9 +224,6 @@
"All boards" : "Све табле", "All boards" : "Све табле",
"Archived boards" : "Архивиране табле", "Archived boards" : "Архивиране табле",
"Shared with you" : "Дељено са Вама", "Shared with you" : "Дељено са Вама",
"Deck settings" : "Поставке Шпила",
"Use bigger card view" : "Користи већи приказ картице",
"Show card ID badge" : "Прикажи беџ ID картице",
"Show boards in calendar/tasks" : "Прикажи табле у календару/задацима", "Show boards in calendar/tasks" : "Прикажи табле у календару/задацима",
"Limit deck usage of groups" : "Ограничи употребу шпила на групе", "Limit deck usage of groups" : "Ограничи употребу шпила на групе",
"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 апликације ће блокирати кориснике који нису део одабраних група да креирају своје табле. Корисници ће и даље моћи да раде на таблама које су подељене са њима.", "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 апликације ће блокирати кориснике који нису део одабраних група да креирају своје табле. Корисници ће и даље моћи да раде на таблама које су подељене са њима.",
@@ -275,47 +232,22 @@
"Clone board" : "Клонирај таблу", "Clone board" : "Клонирај таблу",
"Unarchive board" : "Врати таблу из архиве", "Unarchive board" : "Врати таблу из архиве",
"Archive board" : "Архивирај таблу", "Archive board" : "Архивирај таблу",
"Export board" : "Извези таблу",
"Turn on due date reminders" : "Укључи подсетнике о року",
"Turn off due date reminders" : "Искључи подсетнике о року",
"Due date reminders" : "Подсетници о року",
"All cards" : "Све картице",
"Assigned cards" : "Додељене картице",
"No notifications" : "Нема обавештења", "No notifications" : "Нема обавештења",
"Delete board" : "Избриши таблу", "Delete board" : "Избриши таблу",
"Board {0} deleted" : "Табла {0} обрисана", "Board {0} deleted" : "Табла {0} обрисана",
"Only assigned cards" : "Само додељене картице",
"No reminder" : "Нема подсетника",
"An error occurred" : "Догодила се грешка", "An error occurred" : "Догодила се грешка",
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Да ли сте сигурни да желите да обришете таблу {title}? Ово ће да обрише све податке на овој табли, укључијући и архивиране картице.",
"Delete the board?" : "Обрисати таблу?", "Delete the board?" : "Обрисати таблу?",
"Loading filtered view" : "Учитам филтрирани преглед", "Loading filtered view" : "Учитам филтрирани преглед",
"No due" : "Нема рокова", "No due" : "Нема рокова",
"Search for {searchQuery} in all boards" : "Тражи се {searchQuery} у свим таблама",
"No results found" : "Нема пронађених резултата", "No results found" : "Нема пронађених резултата",
"Deck board {name}\n* Last modified on {lastMod}" : "Шпил табла {name}\n* Последњи пут измењена дана {lastMod}",
"{stack} in {board}" : "{stack} у {board}",
"Click to expand description" : "Кликните да проширите опис",
"* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments" : "* Креирано дана {created}\n* Последњи пут измењено дана {lastMod}\n* {nbAttachments} прилога\n* {nbComments} коментара",
"{nbCards} cards" : "{nbCards} картица",
"Click to expand comment" : "Кликните да проширите коментар",
"No upcoming cards" : "Нема предстојећих картица", "No upcoming cards" : "Нема предстојећих картица",
"upcoming cards" : "предстојеће картице", "upcoming cards" : "предстојеће картице",
"Due on {date}" : "Рок је {date}",
"Link to a board" : "Веза ка табли", "Link to a board" : "Веза ка табли",
"Link to a card" : "Веза ка картици", "Link to a card" : "Веза ка картици",
"Create a card" : "Креирај картицу",
"Message from {author} in {conversationName}" : "Порука од {author} у {conversationName}",
"Something went wrong" : "Нешто је пошло наопако", "Something went wrong" : "Нешто је пошло наопако",
"Failed to upload {name}" : "Није успело отпремање {name}",
"Maximum file size of {size} exceeded" : "Премашена максимална величина фајла од {size}", "Maximum file size of {size} exceeded" : "Премашена максимална величина фајла од {size}",
"Error creating the share" : "Грешка при прављењу дељења", "Error creating the share" : "Грешка при прављењу дељења",
"Share with a Deck card" : "Дели са Шпил картицом",
"Share {file} with a Deck card" : "Дели {file} са Шпил картицом",
"Share" : "Подели", "Share" : "Подели",
"Are you sure you want to transfer the board {title} for {user}?" : "Да ли сте сигурни да желите пренети таблу {title} за {user}?",
"Transfer the board for {user} successfully" : "Пренос табле за {user} је успео",
"Failed to transfer the board for {user}" : "Пренос табле за {user} није успео",
"Add a new list" : "Додај нови списак", "Add a new list" : "Додај нови списак",
"Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Да ли стварно желите да обришете таблу {title}? Овим ћете обрисати све податке са табле." "Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Да ли стварно желите да обришете таблу {title}? Овим ћете обрисати све податке са табле."
},"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" },"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"

View File

@@ -185,7 +185,6 @@ OC.L10N.register(
"An error occurred" : "Виникла помилка", "An error occurred" : "Виникла помилка",
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Ви впевнені, що хочете вилучити дошку {title}? Це призведе до видалення всіх даних цієї дошки, включаючи архівні картки.", "Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Ви впевнені, що хочете вилучити дошку {title}? Це призведе до видалення всіх даних цієї дошки, включаючи архівні картки.",
"Delete the board?" : "Вилучити дошку?", "Delete the board?" : "Вилучити дошку?",
"Due on {date}" : "До {date}",
"Link to a board" : "Прив'язати до дошки", "Link to a board" : "Прив'язати до дошки",
"Link to a card" : "Прив'язати до картки", "Link to a card" : "Прив'язати до картки",
"Message from {author} in {conversationName}" : "Повідомлення від {author} у {conversationName}", "Message from {author} in {conversationName}" : "Повідомлення від {author} у {conversationName}",

View File

@@ -183,7 +183,6 @@
"An error occurred" : "Виникла помилка", "An error occurred" : "Виникла помилка",
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Ви впевнені, що хочете вилучити дошку {title}? Це призведе до видалення всіх даних цієї дошки, включаючи архівні картки.", "Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Ви впевнені, що хочете вилучити дошку {title}? Це призведе до видалення всіх даних цієї дошки, включаючи архівні картки.",
"Delete the board?" : "Вилучити дошку?", "Delete the board?" : "Вилучити дошку?",
"Due on {date}" : "До {date}",
"Link to a board" : "Прив'язати до дошки", "Link to a board" : "Прив'язати до дошки",
"Link to a card" : "Прив'язати до картки", "Link to a card" : "Прив'язати до картки",
"Message from {author} in {conversationName}" : "Повідомлення від {author} у {conversationName}", "Message from {author} in {conversationName}" : "Повідомлення від {author} у {conversationName}",

View File

@@ -36,7 +36,6 @@ use OCA\Deck\Db\CardMapper;
use OCA\Deck\Event\AclCreatedEvent; use OCA\Deck\Event\AclCreatedEvent;
use OCA\Deck\Event\AclDeletedEvent; use OCA\Deck\Event\AclDeletedEvent;
use OCA\Deck\Event\AclUpdatedEvent; use OCA\Deck\Event\AclUpdatedEvent;
use OCA\Deck\Event\BoardUpdatedEvent;
use OCA\Deck\Event\CardCreatedEvent; use OCA\Deck\Event\CardCreatedEvent;
use OCA\Deck\Event\CardDeletedEvent; use OCA\Deck\Event\CardDeletedEvent;
use OCA\Deck\Event\CardUpdatedEvent; use OCA\Deck\Event\CardUpdatedEvent;
@@ -155,13 +154,6 @@ class Application extends App implements IBootstrap {
// Event listening for realtime updates via notify_push // Event listening for realtime updates via notify_push
$context->registerEventListener(SessionCreatedEvent::class, LiveUpdateListener::class); $context->registerEventListener(SessionCreatedEvent::class, LiveUpdateListener::class);
$context->registerEventListener(SessionClosedEvent::class, LiveUpdateListener::class); $context->registerEventListener(SessionClosedEvent::class, LiveUpdateListener::class);
$context->registerEventListener(BoardUpdatedEvent::class, LiveUpdateListener::class);
$context->registerEventListener(CardCreatedEvent::class, LiveUpdateListener::class);
$context->registerEventListener(CardUpdatedEvent::class, LiveUpdateListener::class);
$context->registerEventListener(CardDeletedEvent::class, LiveUpdateListener::class);
$context->registerEventListener(AclCreatedEvent::class, LiveUpdateListener::class);
$context->registerEventListener(AclUpdatedEvent::class, LiveUpdateListener::class);
$context->registerEventListener(AclDeletedEvent::class, LiveUpdateListener::class);
$context->registerNotifierService(Notifier::class); $context->registerNotifierService(Notifier::class);
$context->registerEventListener(LoadAdditionalScriptsEvent::class, ResourceAdditionalScriptsListener::class); $context->registerEventListener(LoadAdditionalScriptsEvent::class, ResourceAdditionalScriptsListener::class);

View File

@@ -65,7 +65,7 @@ class BoardApiController extends ApiController {
public function index($details = null) { public function index($details = null) {
$modified = $this->request->getHeader('If-Modified-Since'); $modified = $this->request->getHeader('If-Modified-Since');
if ($modified === null || $modified === '') { if ($modified === null || $modified === '') {
$boards = $this->boardService->findAll(0, $details === true); $boards = $this->boardService->findAll(0, $details);
} else { } else {
$date = Util::parseHTTPDate($modified); $date = Util::parseHTTPDate($modified);
if (!$date) { if (!$date) {

View File

@@ -59,7 +59,7 @@ class DeckCalendarBackend {
} }
public function getBoards(): array { public function getBoards(): array {
return $this->boardService->findAll(-1, false, false); return $this->boardService->findAll(-1, null, false);
} }
public function getBoard(int $id): Board { public function getBoard(int $id): Board {

View File

@@ -117,7 +117,7 @@ class DeckWidget implements IAPIWidget, IButtonWidget, IIconWidget {
$nowTimestamp = (new Datetime())->getTimestamp(); $nowTimestamp = (new Datetime())->getTimestamp();
$sinceTimestamp = $since !== null ? (new Datetime($since))->getTimestamp() : null; $sinceTimestamp = $since !== null ? (new Datetime($since))->getTimestamp() : null;
$upcomingCards = array_filter($upcomingCards, static function (array $card) use ($nowTimestamp, $sinceTimestamp) { $upcomingCards = array_filter($upcomingCards, static function (array $card) use ($nowTimestamp, $sinceTimestamp) {
if (isset($card['duedate'])) { if ($card['duedate']) {
$ts = (new Datetime($card['duedate']))->getTimestamp(); $ts = (new Datetime($card['duedate']))->getTimestamp();
return $ts > $nowTimestamp && ($sinceTimestamp === null || $ts > $sinceTimestamp); return $ts > $nowTimestamp && ($sinceTimestamp === null || $ts > $sinceTimestamp);
} }

View File

@@ -52,20 +52,6 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
public function findIn(array $boardIds, ?int $limit = null, ?int $offset = null): array {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage')
->from('deck_board_acl')
->where($qb->expr()->in('board_id', $qb->createParameter('boardIds')))
->setMaxResults($limit)
->setFirstResult($offset);
return iterator_to_array($this->chunkQuery($boardIds, function (array $ids) use ($qb) {
$qb->setParameter('boardIds', $ids, IQueryBuilder::PARAM_INT_ARRAY);
return $this->findEntities($qb);
}));
}
/** /**
* @param numeric $userId * @param numeric $userId
* @param numeric $id * @param numeric $id

View File

@@ -28,13 +28,15 @@ namespace OCA\Deck\Db;
use OCA\Deck\NotFoundException; use OCA\Deck\NotFoundException;
use OCA\Deck\Service\CirclesService; use OCA\Deck\Service\CirclesService;
use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection; use OCP\IDBConnection;
use OCP\IGroupManager; use OCP\IGroupManager;
use OCP\IUserManager; use OCP\IUserManager;
use PDO;
/** @template-extends DeckMapper<Assignment> */ /** @template-extends QBMapper<Assignment> */
class AssignmentMapper extends DeckMapper implements IPermissionMapper { class AssignmentMapper extends QBMapper implements IPermissionMapper {
/** @var CardMapper */ /** @var CardMapper */
private $cardMapper; private $cardMapper;
@@ -58,7 +60,7 @@ class AssignmentMapper extends DeckMapper implements IPermissionMapper {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
->from('deck_assigned_users') ->from('deck_assigned_users')
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT))); ->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, PDO::PARAM_INT)));
$users = $this->findEntities($qb); $users = $this->findEntities($qb);
foreach ($users as $user) { foreach ($users as $user) {
$this->mapParticipant($user); $this->mapParticipant($user);
@@ -66,29 +68,12 @@ class AssignmentMapper extends DeckMapper implements IPermissionMapper {
return $users; return $users;
} }
public function findIn(array $cardIds): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('deck_assigned_users')
->where($qb->expr()->in('card_id', $qb->createParameter('cardIds')));
$users = iterator_to_array($this->chunkQuery($cardIds, function (array $ids) use ($qb) {
$qb->setParameter('cardIds', $ids, IQueryBuilder::PARAM_INT_ARRAY);
return $this->findEntities($qb);
}));
foreach ($users as $user) {
$this->mapParticipant($user);
}
return $users;
}
public function findByParticipant(string $participant, $type = Assignment::TYPE_USER): array { public function findByParticipant(string $participant, $type = Assignment::TYPE_USER): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
->from('deck_assigned_users') ->from('deck_assigned_users')
->where($qb->expr()->eq('participant', $qb->createNamedParameter($participant, IQueryBuilder::PARAM_STR))) ->where($qb->expr()->eq('participant', $qb->createNamedParameter($participant, PDO::PARAM_STR)))
->andWhere($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT))); ->andWhere($qb->expr()->eq('type', $qb->createNamedParameter($type, PDO::PARAM_INT)));
return $this->findEntities($qb); return $this->findEntities($qb);
} }
@@ -147,8 +132,8 @@ class AssignmentMapper extends DeckMapper implements IPermissionMapper {
private function getOrigin(Assignment $assignment) { private function getOrigin(Assignment $assignment) {
if ($assignment->getType() === Assignment::TYPE_USER) { if ($assignment->getType() === Assignment::TYPE_USER) {
$origin = $this->userManager->userExists($assignment->getParticipant()); $origin = $this->userManager->get($assignment->getParticipant());
return $origin ? new User($assignment->getParticipant(), $this->userManager) : null; return $origin ? new User($origin) : null;
} }
if ($assignment->getType() === Assignment::TYPE_GROUP) { if ($assignment->getType() === Assignment::TYPE_GROUP) {
$origin = $this->groupManager->get($assignment->getParticipant()); $origin = $this->groupManager->get($assignment->getParticipant());

View File

@@ -90,6 +90,9 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
$this->boardCache[$id] = $this->findEntity($qb); $this->boardCache[$id] = $this->findEntity($qb);
} }
// FIXME is this necessary? it was NOT done with the old mapper
// $this->mapOwner($board);
// Add labels // Add labels
if ($withLabels && $this->boardCache[$id]->getLabels() === null) { if ($withLabels && $this->boardCache[$id]->getLabels() === null) {
$labels = $this->labelMapper->findAll($id); $labels = $this->labelMapper->findAll($id);
@@ -156,21 +159,7 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
$userBoards = $this->findAllByUser($userId, null, null, $since, $includeArchived, $before, $term); $userBoards = $this->findAllByUser($userId, null, null, $since, $includeArchived, $before, $term);
$groupBoards = $this->findAllByGroups($userId, $groups, null, null, $since, $includeArchived, $before, $term); $groupBoards = $this->findAllByGroups($userId, $groups, null, null, $since, $includeArchived, $before, $term);
$circleBoards = $this->findAllByCircles($userId, null, null, $since, $includeArchived, $before, $term); $circleBoards = $this->findAllByCircles($userId, null, null, $since, $includeArchived, $before, $term);
$allBoards = array_values(array_unique(array_merge($userBoards, $groupBoards, $circleBoards))); $allBoards = array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
// Could be moved outside
$acls = $this->aclMapper->findIn(array_map(function ($board) {
return $board->getId();
}, $allBoards));
/* @var Board $entry */
foreach ($allBoards as $entry) {
$boardAcls = array_values(array_filter($acls, function ($acl) use ($entry) {
return $acl->getBoardId() === $entry->getId();
}));
$entry->setAcl($boardAcls);
}
foreach ($allBoards as $board) { foreach ($allBoards as $board) {
$this->boardCache[$board->getId()] = $board; $this->boardCache[$board->getId()] = $board;
} }
@@ -270,7 +259,11 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
$entry->setShared(1); $entry->setShared(1);
} }
$entries = array_merge($entries, $sharedEntries); $entries = array_merge($entries, $sharedEntries);
/* @var Board $entry */
foreach ($entries as $entry) {
$acl = $this->aclMapper->findAll($entry->id);
$entry->setAcl($acl);
}
return $entries; return $entries;
} }
@@ -343,6 +336,11 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
foreach ($entries as $entry) { foreach ($entries as $entry) {
$entry->setShared(2); $entry->setShared(2);
} }
/* @var Board $entry */
foreach ($entries as $entry) {
$acl = $this->aclMapper->findAll($entry->id);
$entry->setAcl($acl);
}
return $entries; return $entries;
} }
@@ -399,6 +397,11 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
foreach ($entries as $entry) { foreach ($entries as $entry) {
$entry->setShared(2); $entry->setShared(2);
} }
/* @var Board $entry */
foreach ($entries as $entry) {
$acl = $this->aclMapper->findAll($entry->id);
$entry->setAcl($acl);
}
return $entries; return $entries;
} }
@@ -452,11 +455,13 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
} }
public function mapAcl(Acl &$acl) { public function mapAcl(Acl &$acl) {
$userManager = $this->userManager;
$groupManager = $this->groupManager; $groupManager = $this->groupManager;
$acl->resolveRelation('participant', function ($participant) use (&$acl, &$userManager, &$groupManager) { $acl->resolveRelation('participant', function ($participant) use (&$acl, &$userManager, &$groupManager) {
if ($acl->getType() === Acl::PERMISSION_TYPE_USER) { if ($acl->getType() === Acl::PERMISSION_TYPE_USER) {
if ($this->userManager->userExists($acl->getParticipant())) { $user = $userManager->get($participant);
return new User($acl->getParticipant(), $this->userManager); if ($user !== null) {
return new User($user);
} }
$this->logger->debug('User ' . $acl->getId() . ' not found when mapping acl ' . $acl->getParticipant()); $this->logger->debug('User ' . $acl->getId() . ' not found when mapping acl ' . $acl->getParticipant());
return null; return null;
@@ -494,8 +499,9 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
public function mapOwner(Board &$board) { public function mapOwner(Board &$board) {
$userManager = $this->userManager; $userManager = $this->userManager;
$board->resolveRelation('owner', function ($owner) use (&$userManager) { $board->resolveRelation('owner', function ($owner) use (&$userManager) {
if ($this->userManager->userExists($owner)) { $user = $userManager->get($owner);
return new User($owner, $userManager); if ($user !== null) {
return new User($user);
} }
return null; return null;
}); });

View File

@@ -159,17 +159,16 @@ class Card extends RelationalEntity {
} }
public function getDaysUntilDue(): ?int { public function getDaysUntilDue(): ?int {
if ($this->getDuedate() === null) { $today = new DateTime();
$match_date = $this->getDuedate();
if ($match_date === null) {
return null; return null;
} }
$today = new DateTime();
$today->setTime(0, 0); $today->setTime(0, 0);
$match_date->setTime(0, 0);
$matchDate = DateTime::createFromInterface($this->getDuedate()); $diff = $today->diff($match_date);
$matchDate->setTime(0, 0);
$diff = $today->diff($matchDate);
return (int) $diff->format('%R%a'); // Extract days count in interval return (int) $diff->format('%R%a'); // Extract days count in interval
} }

View File

@@ -254,13 +254,13 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
public function findAllWithDue(array $boardIds) { public function findAllWithDue($boardId) {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('c.*') $qb->select('c.*')
->from('deck_cards', 'c') ->from('deck_cards', 'c')
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id') ->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id') ->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
->where($qb->expr()->in('s.board_id', $qb->createNamedParameter($boardIds, IQueryBuilder::PARAM_INT_ARRAY))) ->where($qb->expr()->eq('s.board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->isNotNull('c.duedate')) ->andWhere($qb->expr()->isNotNull('c.duedate'))
->andWhere($qb->expr()->eq('c.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))) ->andWhere($qb->expr()->eq('c.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))) ->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
@@ -270,14 +270,14 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
public function findToMeOrNotAssignedCards(array $boardIds, string $username) { public function findToMeOrNotAssignedCards($boardId, $username) {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('c.*') $qb->select('c.*')
->from('deck_cards', 'c') ->from('deck_cards', 'c')
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id') ->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id') ->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
->leftJoin('c', 'deck_assigned_users', 'u', 'c.id = u.card_id') ->leftJoin('c', 'deck_assigned_users', 'u', 'c.id = u.card_id')
->where($qb->expr()->in('s.board_id', $qb->createNamedParameter($boardIds, IQueryBuilder::PARAM_INT_ARRAY))) ->where($qb->expr()->eq('s.board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->orX( ->andWhere($qb->expr()->orX(
$qb->expr()->eq('u.participant', $qb->createNamedParameter($username, IQueryBuilder::PARAM_STR)), $qb->expr()->eq('u.participant', $qb->createNamedParameter($username, IQueryBuilder::PARAM_STR)),
$qb->expr()->isNull('u.participant')) $qb->expr()->isNull('u.participant'))
@@ -607,8 +607,9 @@ class CardMapper extends QBMapper implements IPermissionMapper {
public function mapOwner(Card &$card) { public function mapOwner(Card &$card) {
$userManager = $this->userManager; $userManager = $this->userManager;
$card->resolveRelation('owner', function ($owner) use (&$userManager) { $card->resolveRelation('owner', function ($owner) use (&$userManager) {
if ($userManager->userExists($owner)) { $user = $userManager->get($owner);
return new User($owner, $this->userManager); if ($user !== null) {
return new User($user);
} }
return null; return null;
}); });

View File

@@ -23,7 +23,6 @@
namespace OCA\Deck\Db; namespace OCA\Deck\Db;
use Generator;
use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper; use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
@@ -36,7 +35,7 @@ abstract class DeckMapper extends QBMapper {
/** /**
* @param $id * @param $id
* @return T * @return \OCP\AppFramework\Db\Entity if not found
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws \OCP\AppFramework\Db\DoesNotExistException * @throws \OCP\AppFramework\Db\DoesNotExistException
*/ */
@@ -48,21 +47,4 @@ abstract class DeckMapper extends QBMapper {
return $this->findEntity($qb); return $this->findEntity($qb);
} }
/**
* Helper function to split passed array into chunks of 1000 elements and
* call a given callback for fetching query results
*
* Can be useful to limit to 1000 results per query for oracle compatiblity
* but still iterate over all results
*/
public function chunkQuery(array $ids, callable $callback): Generator {
$limit = 1000;
while (!empty($ids)) {
$slice = array_splice($ids, 0, $limit);
foreach ($callback($slice) as $item) {
yield $item;
}
}
}
} }

View File

@@ -79,19 +79,6 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
public function findAssignedLabelsForCards($cardIds, $limit = null, $offset = null): array {
$qb = $this->db->getQueryBuilder();
$qb->select('l.*', 'card_id')
->from($this->getTableName(), 'l')
->innerJoin('l', 'deck_assigned_labels', 'al', 'l.id = al.label_id')
->where($qb->expr()->in('card_id', $qb->createNamedParameter($cardIds, IQueryBuilder::PARAM_INT_ARRAY)))
->orderBy('l.id')
->setMaxResults($limit)
->setFirstResult($offset);
return $this->findEntities($qb);
}
/** /**
* @param numeric $boardId * @param numeric $boardId
* @param int|null $limit * @param int|null $limit

View File

@@ -33,7 +33,7 @@ class RelationalObject implements JsonSerializable {
* RelationalObject constructor. * RelationalObject constructor.
* *
* @param $primaryKey string * @param $primaryKey string
* @param callable|mixed $object * @param $object
*/ */
public function __construct($primaryKey, $object) { public function __construct($primaryKey, $object) {
$this->primaryKey = $primaryKey; $this->primaryKey = $primaryKey;
@@ -47,24 +47,16 @@ class RelationalObject implements JsonSerializable {
); );
} }
public function getObject() {
if (is_callable($this->object)) {
$this->object = call_user_func($this->object, $this);
}
return $this->object;
}
/** /**
* This method should be overwritten if object doesn't implement \JsonSerializable * This method should be overwritten if object doesn't implement \JsonSerializable
* *
* @throws \Exception * @throws \Exception
*/ */
public function getObjectSerialization() { public function getObjectSerialization() {
if ($this->getObject() instanceof JsonSerializable) { if ($this->object instanceof JsonSerializable) {
return $this->getObject()->jsonSerialize(); return $this->object->jsonSerialize();
} else { } else {
throw new \Exception('jsonSerialize is not implemented on ' . get_class($this->getObject())); throw new \Exception('jsonSerialize is not implemented on ' . get_class($this));
} }
} }

View File

@@ -26,27 +26,16 @@ namespace OCA\Deck\Db;
use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\Cache\CappedMemoryCache;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection; use OCP\IDBConnection;
use OCP\ICache;
use OCP\ICacheFactory;
/** @template-extends DeckMapper<Stack> */ /** @template-extends DeckMapper<Stack> */
class StackMapper extends DeckMapper implements IPermissionMapper { class StackMapper extends DeckMapper implements IPermissionMapper {
private CappedMemoryCache $stackCache; private $cardMapper;
private CardMapper $cardMapper;
private ICache $cache;
public function __construct( public function __construct(IDBConnection $db, CardMapper $cardMapper) {
IDBConnection $db,
CardMapper $cardMapper,
ICacheFactory $cacheFactory
) {
parent::__construct($db, 'deck_stacks', Stack::class); parent::__construct($db, 'deck_stacks', Stack::class);
$this->cardMapper = $cardMapper; $this->cardMapper = $cardMapper;
$this->stackCache = new CappedMemoryCache();
$this->cache = $cacheFactory->createDistributed('deck-stackMapper');
} }
@@ -58,17 +47,12 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function find($id): Stack { public function find($id): Stack {
if (isset($this->stackCache[(string)$id])) {
return $this->stackCache[(string)$id];
}
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
->from($this->getTableName()) ->from($this->getTableName())
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))); ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
$this->stackCache[(string)$id] = $this->findEntity($qb); return $this->findEntity($qb);
return $this->stackCache[(string)$id];
} }
/** /**
@@ -129,16 +113,9 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
public function update(Entity $entity): Entity {
$result = parent::update($entity);
$this->stackCache[(string)$entity->getId()] = $result;
return $result;
}
public function delete(Entity $entity): Entity { public function delete(Entity $entity): Entity {
// delete cards on stack // delete cards on stack
$this->cardMapper->deleteByStack($entity->getId()); $this->cardMapper->deleteByStack($entity->getId());
unset($this->stackCache[(string)$entity->getId()]);
return parent::delete($entity); return parent::delete($entity);
} }
@@ -165,19 +142,12 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function findBoardId($id): ?int { public function findBoardId($id): ?int {
$result = $this->cache->get('findBoardId:' . $id);
if ($result !== null) {
return $result !== false ? $result : null;
}
try { try {
$entity = $this->find($id); $entity = $this->find($id);
$result = $entity->getBoardId(); return $entity->getBoardId();
} catch (DoesNotExistException $e) { } catch (DoesNotExistException $e) {
$result = false;
} catch (MultipleObjectsReturnedException $e) { } catch (MultipleObjectsReturnedException $e) {
} }
$this->cache->set('findBoardId:' . $id, $result); return null;
return $result !== false ? $result : null;
} }
} }

View File

@@ -23,30 +23,27 @@
namespace OCA\Deck\Db; namespace OCA\Deck\Db;
use OCP\IUserManager; use OCP\IUser;
class User extends RelationalObject { class User extends RelationalObject {
private IUserManager $userManager; public function __construct(IUser $user) {
public function __construct($uid, IUserManager $userManager) { $primaryKey = $user->getUID();
$this->userManager = $userManager; parent::__construct($primaryKey, $user);
parent::__construct($uid, function ($object) {
return $this->userManager->get($object->getPrimaryKey());
});
} }
public function getObjectSerialization() { public function getObjectSerialization() {
return [ return [
'uid' => $this->getObject()->getUID(), 'uid' => $this->object->getUID(),
'displayname' => $this->getObject()->getDisplayName(), 'displayname' => $this->object->getDisplayName(),
'type' => Acl::PERMISSION_TYPE_USER 'type' => 0
]; ];
} }
public function getUID() { public function getUID() {
return $this->getPrimaryKey(); return $this->object->getUID();
} }
public function getDisplayName() { public function getDisplayName() {
return $this->userManager->getDisplayName($this->getPrimaryKey()); return $this->object->getDisplayName();
} }
} }

View File

@@ -31,7 +31,7 @@ use OCP\EventDispatcher\Event;
abstract class AAclEvent extends Event { abstract class AAclEvent extends Event {
private $acl; private $acl;
public function __construct(Acl $acl) { public function __construct(Acl $acl) {
parent::__construct(); parent::__construct();
@@ -41,8 +41,4 @@ abstract class AAclEvent extends Event {
public function getAcl(): Acl { public function getAcl(): Acl {
return $this->acl; return $this->acl;
} }
public function getBoardId(): int {
return $this->acl->getBoardId();
}
} }

View File

@@ -1,43 +0,0 @@
<?php
/*
* @copyright Copyright (c) 2022 chandi Langecker <git@chandi.it>
*
* @author chandi Langecker <git@chandi.it>
*
* @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 OCP\EventDispatcher\Event;
class BoardUpdatedEvent extends Event {
private $boardId;
public function __construct(int $boardId) {
parent::__construct();
$this->boardId = $boardId;
}
public function getBoardId(): int {
return $this->boardId;
}
}

View File

@@ -26,17 +26,5 @@ declare(strict_types=1);
namespace OCA\Deck\Event; namespace OCA\Deck\Event;
use OCA\Deck\Db\Card;
class CardUpdatedEvent extends ACardEvent { class CardUpdatedEvent extends ACardEvent {
private $cardBefore;
public function __construct(Card $card, Card $before = null) {
parent::__construct($card);
$this->cardBefore = $before;
}
public function getCardBefore() {
return $this->cardBefore;
}
} }

View File

@@ -26,12 +26,7 @@ declare(strict_types=1);
namespace OCA\Deck\Listeners; namespace OCA\Deck\Listeners;
use OCA\Deck\Db\StackMapper;
use OCA\Deck\NotifyPushEvents; use OCA\Deck\NotifyPushEvents;
use OCA\Deck\Event\AAclEvent;
use OCA\Deck\Event\ACardEvent;
use OCA\Deck\Event\BoardUpdatedEvent;
use OCA\Deck\Event\CardUpdatedEvent;
use OCA\Deck\Event\SessionClosedEvent; use OCA\Deck\Event\SessionClosedEvent;
use OCA\Deck\Event\SessionCreatedEvent; use OCA\Deck\Event\SessionCreatedEvent;
use OCA\Deck\Service\SessionService; use OCA\Deck\Service\SessionService;
@@ -42,20 +37,18 @@ use OCP\IRequest;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/** @template-implements IEventListener<Event|SessionCreatedEvent|SessionClosedEvent|AAclEvent|ACardEvent|CardUpdatedEvent|BoardUpdatedEvent> */ /** @template-implements IEventListener<Event|SessionCreatedEvent|SessionClosedEvent> */
class LiveUpdateListener implements IEventListener { class LiveUpdateListener implements IEventListener {
private LoggerInterface $logger; private LoggerInterface $logger;
private SessionService $sessionService; private SessionService $sessionService;
private IRequest $request; private IRequest $request;
private StackMapper $stackMapper;
private $queue; private $queue;
public function __construct( public function __construct(
ContainerInterface $container, ContainerInterface $container,
IRequest $request, IRequest $request,
LoggerInterface $logger, LoggerInterface $logger,
SessionService $sessionService, SessionService $sessionService
StackMapper $stackMapper
) { ) {
try { try {
$this->queue = $container->get(IQueue::class); $this->queue = $container->get(IQueue::class);
@@ -66,7 +59,6 @@ class LiveUpdateListener implements IEventListener {
$this->logger = $logger; $this->logger = $logger;
$this->sessionService = $sessionService; $this->sessionService = $sessionService;
$this->request = $request; $this->request = $request;
$this->stackMapper = $stackMapper;
} }
public function handle(Event $event): void { public function handle(Event $event): void {
@@ -76,37 +68,17 @@ class LiveUpdateListener implements IEventListener {
} }
try { try {
// the web frontend is adding the Session-ID as a header // the web frontend is adding the Session-ID as a header on every request
// TODO: verify the token! this currently allows to spoof a token from someone // TODO: verify the token! this currently allows to spoof a token from someone
// else, preventing this person from getting updates // else, preventing this person from getting any live updates
$causingSessionToken = $this->request->getHeader('x-nc-deck-session'); $causingSessionToken = $this->request->getHeader('x-nc-deck-session');
if ( if (
$event instanceof SessionCreatedEvent || $event instanceof SessionCreatedEvent ||
$event instanceof SessionClosedEvent || $event instanceof SessionClosedEvent
$event instanceof BoardUpdatedEvent ||
$event instanceof AAclEvent
) { ) {
$this->sessionService->notifyAllSessions($this->queue, $event->getBoardId(), NotifyPushEvents::DeckBoardUpdate, [ $this->sessionService->notifyAllSessions($this->queue, $event->getBoardId(), NotifyPushEvents::DeckBoardUpdate, [
'id' => $event->getBoardId() 'id' => $event->getBoardId()
], $causingSessionToken); ], $causingSessionToken);
} elseif ($event instanceof ACardEvent) {
$boardId = $this->stackMapper->findBoardId($event->getCard()->getStackId());
$this->sessionService->notifyAllSessions($this->queue, $boardId, NotifyPushEvents::DeckCardUpdate, [
'boardId' => $boardId,
'cardId' => $event->getCard()->getId()
], $causingSessionToken);
// if card got moved to a diferent board, we should notify
// also sessions active on the previous board
if ($event instanceof CardUpdatedEvent && $event->getCardBefore()) {
$previousBoardId = $this->stackMapper->findBoardId($event->getCardBefore()->getStackId());
if ($boardId !== $previousBoardId) {
$this->sessionService->notifyAllSessions($this->queue, $previousBoardId, NotifyPushEvents::DeckCardUpdate, [
'boardId' => $boardId,
'cardId' => $event->getCard()->getId()
], $causingSessionToken);
}
}
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$this->logger->error('Error when handling live update event', ['exception' => $e]); $this->logger->error('Error when handling live update event', ['exception' => $e]);

View File

@@ -26,5 +26,4 @@ namespace OCA\Deck;
class NotifyPushEvents { class NotifyPushEvents {
public const DeckBoardUpdate = 'deck_board_update'; public const DeckBoardUpdate = 'deck_board_update';
public const DeckCardUpdate = 'deck_card_update';
} }

View File

@@ -56,7 +56,6 @@ use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\LabelMapper; use OCA\Deck\Db\LabelMapper;
use OCP\IUserManager; use OCP\IUserManager;
use OCA\Deck\BadRequestException; use OCA\Deck\BadRequestException;
use OCA\Deck\Event\BoardUpdatedEvent;
use OCA\Deck\Validators\BoardServiceValidator; use OCA\Deck\Validators\BoardServiceValidator;
use OCP\IURLGenerator; use OCP\IURLGenerator;
use OCP\Server; use OCP\Server;
@@ -80,8 +79,7 @@ class BoardService {
private IEventDispatcher $eventDispatcher; private IEventDispatcher $eventDispatcher;
private ChangeHelper $changeHelper; private ChangeHelper $changeHelper;
private CardMapper $cardMapper; private CardMapper $cardMapper;
private ?array $boardsCacheFull = null; private ?array $boardsCache = null;
private ?array $boardsCachePartial = null;
private IURLGenerator $urlGenerator; private IURLGenerator $urlGenerator;
private IDBConnection $connection; private IDBConnection $connection;
private BoardServiceValidator $boardServiceValidator; private BoardServiceValidator $boardServiceValidator;
@@ -149,45 +147,96 @@ class BoardService {
} }
/** /**
* @return Board[] * @return array
*/ */
public function findAll(int $since = -1, bool $fullDetails = false, bool $includeArchived = true): array { public function findAll($since = -1, $details = null, $includeArchived = true) {
if ($this->boardsCacheFull && $fullDetails) { if ($this->boardsCache) {
return $this->boardsCacheFull; return $this->boardsCache;
} }
if ($this->boardsCachePartial && !$fullDetails) {
return $this->boardsCachePartial;
}
$complete = $this->getUserBoards($since, $includeArchived); $complete = $this->getUserBoards($since, $includeArchived);
return $this->enrichBoards($complete, $fullDetails); $result = [];
/** @var Board $item */
foreach ($complete as &$item) {
$this->boardMapper->mapOwner($item);
if ($item->getAcl() !== null) {
foreach ($item->getAcl() as &$acl) {
$this->boardMapper->mapAcl($acl);
}
}
if ($details !== null) {
$this->enrichWithStacks($item);
$this->enrichWithLabels($item);
$this->enrichWithUsers($item);
}
$permissions = $this->permissionService->matchPermissions($item);
$item->setPermissions([
'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ] ?? false,
'PERMISSION_EDIT' => $permissions[Acl::PERMISSION_EDIT] ?? false,
'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE] ?? false,
'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE] ?? false
]);
$this->enrichWithBoardSettings($item);
$result[$item->getId()] = $item;
}
$this->boardsCache = $result;
return array_values($result);
} }
/** /**
* @param $boardId
* @return Board
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function find(int $boardId, bool $fullDetails = true): Board { public function find($boardId) {
$this->boardServiceValidator->check(compact('boardId')); $this->boardServiceValidator->check(compact('boardId'));
if ($this->boardsCache && isset($this->boardsCache[$boardId])) {
if (isset($this->boardsCacheFull[$boardId]) && $fullDetails) { return $this->boardsCache[$boardId];
return $this->boardsCacheFull[$boardId];
} }
if (is_numeric($boardId) === false) {
if (isset($this->boardsCachePartial[$boardId]) && !$fullDetails) { throw new BadRequestException('board id must be a number');
return $this->boardsCachePartial[$boardId];
} }
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
/** @var Board $board */ /** @var Board $board */
$board = $this->boardMapper->find($boardId, true, true); $board = $this->boardMapper->find($boardId, true, true);
[$board] = $this->enrichBoards([$board], $fullDetails); $this->boardMapper->mapOwner($board);
if ($board->getAcl() !== null) {
foreach ($board->getAcl() as $acl) {
if ($acl !== null) {
$this->boardMapper->mapAcl($acl);
}
}
}
$permissions = $this->permissionService->matchPermissions($board);
$board->setPermissions([
'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ] ?? false,
'PERMISSION_EDIT' => $permissions[Acl::PERMISSION_EDIT] ?? false,
'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE] ?? false,
'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE] ?? false
]);
$this->enrichWithUsers($board);
$this->enrichWithBoardSettings($board);
$this->enrichWithActiveSessions($board);
$this->boardsCache[$board->getId()] = $board;
return $board; return $board;
} }
/**
* @return array
*/
private function getBoardPrerequisites() {
$groups = $this->groupManager->getUserGroupIds(
$this->userManager->get($this->userId)
);
return [
'user' => $this->userId,
'groups' => $groups
];
}
/** /**
* @param $mapper * @param $mapper
* @param $id * @param $id
@@ -380,7 +429,6 @@ class BoardService {
$this->boardMapper->mapOwner($board); $this->boardMapper->mapOwner($board);
$this->activityManager->triggerUpdateEvents(ActivityManager::DECK_OBJECT_BOARD, $changes, ActivityManager::SUBJECT_BOARD_UPDATE); $this->activityManager->triggerUpdateEvents(ActivityManager::DECK_OBJECT_BOARD, $changes, ActivityManager::SUBJECT_BOARD_UPDATE);
$this->changeHelper->boardChanged($board->getId()); $this->changeHelper->boardChanged($board->getId());
$this->eventDispatcher->dispatchTyped(new BoardUpdatedEvent($board->getId()));
return $board; return $board;
} }
@@ -408,7 +456,7 @@ class BoardService {
public function enrichWithActiveSessions(Board $board) { public function enrichWithActiveSessions(Board $board) {
$sessions = $this->sessionMapper->findAllActive($board->getId()); $sessions = $this->sessionMapper->findAllActive($board->getId());
$board->setActiveSessions(array_values( $board->setActiveSessions(array_values(
array_unique( array_unique(
array_map(function (Session $session) { array_map(function (Session $session) {
@@ -643,45 +691,6 @@ class BoardService {
return $board; return $board;
} }
/** @param Board[] $boards */
private function enrichBoards(array $boards, bool $fullDetails = true): array {
$result = [];
foreach ($boards as $board) {
// FIXME The enrichment in here could make use of combined queries
$this->boardMapper->mapOwner($board);
if ($board->getAcl() !== null) {
foreach ($board->getAcl() as &$acl) {
$this->boardMapper->mapAcl($acl);
}
}
$permissions = $this->permissionService->matchPermissions($board);
$board->setPermissions([
'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ] ?? false,
'PERMISSION_EDIT' => $permissions[Acl::PERMISSION_EDIT] ?? false,
'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE] ?? false,
'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE] ?? false
]);
if ($fullDetails) {
$this->enrichWithStacks($board);
$this->enrichWithLabels($board);
$this->enrichWithUsers($board);
$this->enrichWithBoardSettings($board);
$this->enrichWithActiveSessions($board);
}
// Cache for further usage
if ($fullDetails) {
$this->boardsCacheFull[$board->getId()] = $board;
} else {
$this->boardsCachePartial[$board->getId()] = $board;
}
}
return $boards;
}
private function enrichWithStacks($board, $since = -1) { private function enrichWithStacks($board, $since = -1) {
$stacks = $this->stackMapper->findAll($board->getId(), null, null, $since); $stacks = $this->stackMapper->findAll($board->getId(), null, null, $since);
@@ -704,7 +713,7 @@ class BoardService {
private function enrichWithUsers($board, $since = -1) { private function enrichWithUsers($board, $since = -1) {
$boardUsers = $this->permissionService->findUsers($board->getId()); $boardUsers = $this->permissionService->findUsers($board->getId());
if ($boardUsers === null || \count($boardUsers) === 0) { if (\count($boardUsers) === 0) {
return; return;
} }
$board->setUsers(array_values($boardUsers)); $board->setUsers(array_values($boardUsers));
@@ -714,6 +723,10 @@ class BoardService {
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . '#' . $endpoint; return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . '#' . $endpoint;
} }
private function clearBoardsCache() {
$this->boardsCache = null;
}
/** /**
* Clean a given board data from the Cache * Clean a given board data from the Cache
*/ */
@@ -722,8 +735,7 @@ class BoardService {
$boardOwnerId = $board->getOwner(); $boardOwnerId = $board->getOwner();
$this->boardMapper->flushCache($boardId, $boardOwnerId); $this->boardMapper->flushCache($boardId, $boardOwnerId);
unset($this->boardsCacheFull[$boardId]); unset($this->boardsCache[$boardId]);
unset($this->boardsCachePartial[$boardId]);
} }
private function enrichWithCards($board) { private function enrichWithCards($board) {

View File

@@ -28,13 +28,11 @@ namespace OCA\Deck\Service;
use OCA\Deck\Activity\ActivityManager; use OCA\Deck\Activity\ActivityManager;
use OCA\Deck\Activity\ChangeSet; use OCA\Deck\Activity\ChangeSet;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\AssignmentMapper; use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\Card; use OCA\Deck\Db\Card;
use OCA\Deck\Db\CardMapper; use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\Acl; use OCA\Deck\Db\Acl;
use OCA\Deck\Db\ChangeHelper; use OCA\Deck\Db\ChangeHelper;
use OCA\Deck\Db\Label;
use OCA\Deck\Db\StackMapper; use OCA\Deck\Db\StackMapper;
use OCA\Deck\Event\CardCreatedEvent; use OCA\Deck\Event\CardCreatedEvent;
use OCA\Deck\Event\CardDeletedEvent; use OCA\Deck\Event\CardDeletedEvent;
@@ -116,52 +114,32 @@ class CardService {
$this->cardServiceValidator = $cardServiceValidator; $this->cardServiceValidator = $cardServiceValidator;
} }
public function enrichCards($cards) { public function enrich($card) {
$cardId = $card->getId();
$this->cardMapper->mapOwner($card);
$card->setAssignedUsers($this->assignedUsersMapper->findAll($cardId));
$card->setLabels($this->labelMapper->findAssignedLabelsForCard($cardId));
$card->setAttachmentCount($this->attachmentService->count($cardId));
$user = $this->userManager->get($this->currentUser); $user = $this->userManager->get($this->currentUser);
$lastRead = $this->commentsManager->getReadMark('deckCard', (string)$card->getId(), $user);
$countUnreadComments = $this->commentsManager->getNumberOfCommentsForObject('deckCard', (string)$card->getId(), $lastRead);
$countComments = $this->commentsManager->getNumberOfCommentsForObject('deckCard', (string)$card->getId());
$card->setCommentsUnread($countUnreadComments);
$card->setCommentsCount($countComments);
$cardIds = array_map(function (Card $card) use ($user) { $stack = $this->stackMapper->find($card->getStackId());
// Everything done in here might be heavy as it is executed for every card $board = $this->boardService->find($stack->getBoardId());
$cardId = $card->getId(); $card->setRelatedStack($stack);
$this->cardMapper->mapOwner($card); $card->setRelatedBoard($board);
$card->setAttachmentCount($this->attachmentService->count($cardId));
// TODO We should find a better way just to get the comment count so we can save 1-3 queries per card here
$countComments = $this->commentsManager->getNumberOfCommentsForObject('deckCard', (string)$card->getId());
$lastRead = $countComments > 0 ? $this->commentsManager->getReadMark('deckCard', (string)$card->getId(), $user) : null;
$countUnreadComments = $lastRead ? $this->commentsManager->getNumberOfCommentsForObject('deckCard', (string)$card->getId(), $lastRead) : 0;
$card->setCommentsUnread($countUnreadComments);
$card->setCommentsCount($countComments);
$stack = $this->stackMapper->find($card->getStackId());
$board = $this->boardService->find($stack->getBoardId(), false);
$card->setRelatedStack($stack);
$card->setRelatedBoard($board);
return $card->getId();
}, $cards);
$assignedLabels = $this->labelMapper->findAssignedLabelsForCards($cardIds);
$assignedUsers = $this->assignedUsersMapper->findIn($cardIds);
foreach ($cards as $card) {
$cardLabels = array_values(array_filter($assignedLabels, function (Label $label) use ($card) {
return $label->getCardId() === $card->getId();
}));
$cardAssignedUsers = array_values(array_filter($assignedUsers, function (Assignment $assignment) use ($card) {
return $assignment->getCardId() === $card->getId();
}));
$card->setLabels($cardLabels);
$card->setAssignedUsers($cardAssignedUsers);
}
return $cards;
} }
public function fetchDeleted($boardId) { public function fetchDeleted($boardId) {
$this->cardServiceValidator->check(compact('boardId')); $this->cardServiceValidator->check(compact('boardId'));
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
$cards = $this->cardMapper->findDeleted($boardId); $cards = $this->cardMapper->findDeleted($boardId);
$this->enrichCards($cards); foreach ($cards as $card) {
$this->enrich($card);
}
return $cards; return $cards;
} }
@@ -175,17 +153,16 @@ class CardService {
public function find(int $cardId) { public function find(int $cardId) {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
$card = $this->cardMapper->find($cardId); $card = $this->cardMapper->find($cardId);
[$card] = $this->enrichCards([$card]); $assignedUsers = $this->assignedUsersMapper->findAll($card->getId());
// Attachments are only enriched on individual card fetching
$attachments = $this->attachmentService->findAll($cardId, true); $attachments = $this->attachmentService->findAll($cardId, true);
if ($this->request->getParam('apiVersion') === '1.0') { if ($this->request->getParam('apiVersion') === '1.0') {
$attachments = array_filter($attachments, function ($attachment) { $attachments = array_filter($attachments, function ($attachment) {
return $attachment->getType() === 'deck_file'; return $attachment->getType() === 'deck_file';
}); });
} }
$card->setAssignedUsers($assignedUsers);
$card->setAttachments($attachments); $card->setAttachments($attachments);
$this->enrich($card);
return $card; return $card;
} }
@@ -197,7 +174,9 @@ class CardService {
return []; return [];
} }
$cards = $this->cardMapper->findCalendarEntries($boardId); $cards = $this->cardMapper->findCalendarEntries($boardId);
$this->enrichCards($cards); foreach ($cards as $card) {
$this->enrich($card);
}
return $cards; return $cards;
} }
@@ -353,7 +332,7 @@ class CardService {
} }
$this->changeHelper->cardChanged($card->getId(), true); $this->changeHelper->cardChanged($card->getId(), true);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card, $changes->getBefore())); $this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
return $card; return $card;
} }
@@ -443,8 +422,6 @@ class CardService {
$result[$card->getOrder()] = $card; $result[$card->getOrder()] = $card;
} }
$this->changeHelper->cardChanged($id, false); $this->changeHelper->cardChanged($id, false);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
return array_values($result); return array_values($result);
} }

View File

@@ -224,7 +224,7 @@ class ConfigService {
} }
public function getAttachmentFolder(string $userId = null): string { public function getAttachmentFolder(string $userId = null): string {
if ($userId === null && $this->getUserId() === null) { if ($this->getUserId() === null) {
throw new NoPermissionException('Must be logged in get the attachment folder'); throw new NoPermissionException('Must be logged in get the attachment folder');
} }

View File

@@ -62,8 +62,9 @@ class DefaultBoardService {
*/ */
public function checkFirstRun($userId): bool { public function checkFirstRun($userId): bool {
$firstRun = $this->config->getUserValue($userId, Application::APP_ID, 'firstRun', 'yes'); $firstRun = $this->config->getUserValue($userId, Application::APP_ID, 'firstRun', 'yes');
$userBoards = $this->boardMapper->findAllByUser($userId);
if ($firstRun === 'yes') { if ($firstRun === 'yes' && count($userBoards) === 0) {
try { try {
$this->config->setUserValue($userId, Application::APP_ID, 'firstRun', 'no'); $this->config->setUserValue($userId, Application::APP_ID, 'firstRun', 'no');
} catch (PreConditionNotMetException $e) { } catch (PreConditionNotMetException $e) {

View File

@@ -28,7 +28,7 @@ declare(strict_types=1);
namespace OCA\Deck\Service; namespace OCA\Deck\Service;
use OCA\Deck\Db\AssignmentMapper; use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\Board; use OCA\Deck\Db\Card;
use OCA\Deck\Db\CardMapper; use OCA\Deck\Db\CardMapper;
use OCA\Deck\Model\CardDetails; use OCA\Deck\Model\CardDetails;
use OCP\Comments\ICommentsManager; use OCP\Comments\ICommentsManager;
@@ -37,7 +37,6 @@ use OCA\Deck\Db\LabelMapper;
use OCP\IUserManager; use OCP\IUserManager;
class OverviewService { class OverviewService {
private CardService $cardService;
private BoardMapper $boardMapper; private BoardMapper $boardMapper;
private LabelMapper $labelMapper; private LabelMapper $labelMapper;
private CardMapper $cardMapper; private CardMapper $cardMapper;
@@ -47,7 +46,6 @@ class OverviewService {
private AttachmentService $attachmentService; private AttachmentService $attachmentService;
public function __construct( public function __construct(
CardService $cardService,
BoardMapper $boardMapper, BoardMapper $boardMapper,
LabelMapper $labelMapper, LabelMapper $labelMapper,
CardMapper $cardMapper, CardMapper $cardMapper,
@@ -56,7 +54,6 @@ class OverviewService {
ICommentsManager $commentsManager, ICommentsManager $commentsManager,
AttachmentService $attachmentService AttachmentService $attachmentService
) { ) {
$this->cardService = $cardService;
$this->boardMapper = $boardMapper; $this->boardMapper = $boardMapper;
$this->labelMapper = $labelMapper; $this->labelMapper = $labelMapper;
$this->cardMapper = $cardMapper; $this->cardMapper = $cardMapper;
@@ -66,43 +63,66 @@ class OverviewService {
$this->attachmentService = $attachmentService; $this->attachmentService = $attachmentService;
} }
public function enrich(Card $card, string $userId): void {
$cardId = $card->getId();
$this->cardMapper->mapOwner($card);
$card->setAssignedUsers($this->assignedUsersMapper->findAll($cardId));
$card->setLabels($this->labelMapper->findAssignedLabelsForCard($cardId));
$card->setAttachmentCount($this->attachmentService->count($cardId));
$user = $this->userManager->get($userId);
if ($user !== null) {
$lastRead = $this->commentsManager->getReadMark('deckCard', (string)$card->getId(), $user);
$count = $this->commentsManager->getNumberOfCommentsForObject('deckCard', (string)$card->getId(), $lastRead);
$card->setCommentsUnread($count);
}
}
public function findAllWithDue(string $userId): array {
$userBoards = $this->boardMapper->findAllForUser($userId);
$allDueCards = [];
foreach ($userBoards as $userBoard) {
$allDueCards[] = array_map(function ($card) use ($userBoard, $userId) {
$this->enrich($card, $userId);
return (new CardDetails($card, $userBoard))->jsonSerialize();
}, $this->cardMapper->findAllWithDue($userBoard->getId()));
}
return array_merge(...$allDueCards);
}
public function findUpcomingCards(string $userId): array { public function findUpcomingCards(string $userId): array {
$userBoards = $this->boardMapper->findAllForUser($userId); $userBoards = $this->boardMapper->findAllForUser($userId);
$boardOwnerIds = array_filter(array_map(function (Board $board) {
return count($board->getAcl()) === 0 ? $board->getId() : null;
}, $userBoards));
$boardSharedIds = array_filter(array_map(function (Board $board) {
return count($board->getAcl()) > 0 ? $board->getId() : null;
}, $userBoards));
$foundCards = array_merge(
// private board: get cards with due date
$this->cardMapper->findAllWithDue($boardOwnerIds),
// shared board: get all my assigned or unassigned cards
$this->cardMapper->findToMeOrNotAssignedCards($boardSharedIds, $userId)
);
$this->cardService->enrichCards($foundCards);
$overview = []; $overview = [];
foreach ($foundCards as $card) { foreach ($userBoards as $userBoard) {
$diffDays = $card->getDaysUntilDue(); if (count($userBoard->getAcl()) === 0) {
// private board: get cards with due date
$key = 'later'; $cards = $this->cardMapper->findAllWithDue($userBoard->getId());
if ($diffDays === null) { } else {
$key = 'nodue'; // shared board: get all my assigned or unassigned cards
} elseif ($diffDays < 0) { $cards = $this->cardMapper->findToMeOrNotAssignedCards($userBoard->getId(), $userId);
$key = 'overdue';
} elseif ($diffDays === 0) {
$key = 'today';
} elseif ($diffDays === 1) {
$key = 'tomorrow';
} elseif ($diffDays <= 7) {
$key = 'nextSevenDays';
} }
$card = (new CardDetails($card, $card->getRelatedBoard())); foreach ($cards as $card) {
$overview[$key][] = $card->jsonSerialize(); $this->enrich($card, $userId);
$diffDays = $card->getDaysUntilDue();
$key = 'later';
if ($diffDays === null) {
$key = 'nodue';
} elseif ($diffDays < 0) {
$key = 'overdue';
} elseif ($diffDays === 0) {
$key = 'today';
} elseif ($diffDays === 1) {
$key = 'tomorrow';
} elseif ($diffDays <= 7) {
$key = 'nextSevenDays';
}
$card = (new CardDetails($card, $userBoard));
$overview[$key][] = $card->jsonSerialize();
}
} }
return $overview; return $overview;
} }

View File

@@ -97,26 +97,21 @@ class PermissionService {
* @param $boardId * @param $boardId
* @return bool|array * @return bool|array
*/ */
public function getPermissions($boardId, ?string $userId = null) { public function getPermissions($boardId) {
if ($userId === null) { if ($cached = $this->permissionCache->get($boardId)) {
$userId = $this->userId;
}
$cacheKey = $boardId . '-' . $userId;
if ($cached = $this->permissionCache->get($cacheKey)) {
return $cached; return $cached;
} }
$owner = $this->userIsBoardOwner($boardId, $userId); $owner = $this->userIsBoardOwner($boardId);
$acls = $this->aclMapper->findAll($boardId); $acls = $this->aclMapper->findAll($boardId);
$permissions = [ $permissions = [
Acl::PERMISSION_READ => $owner || $this->userCan($acls, Acl::PERMISSION_READ, $userId), Acl::PERMISSION_READ => $owner || $this->userCan($acls, Acl::PERMISSION_READ),
Acl::PERMISSION_EDIT => $owner || $this->userCan($acls, Acl::PERMISSION_EDIT, $userId), Acl::PERMISSION_EDIT => $owner || $this->userCan($acls, Acl::PERMISSION_EDIT),
Acl::PERMISSION_MANAGE => $owner || $this->userCan($acls, Acl::PERMISSION_MANAGE, $userId), Acl::PERMISSION_MANAGE => $owner || $this->userCan($acls, Acl::PERMISSION_MANAGE),
Acl::PERMISSION_SHARE => ($owner || $this->userCan($acls, Acl::PERMISSION_SHARE, $userId)) Acl::PERMISSION_SHARE => ($owner || $this->userCan($acls, Acl::PERMISSION_SHARE))
&& (!$this->shareManager->sharingDisabledForUser($userId)) && (!$this->shareManager->sharingDisabledForUser($this->userId))
]; ];
$this->permissionCache->set($cacheKey, $permissions); $this->permissionCache->set($boardId, $permissions);
return $permissions; return $permissions;
} }
@@ -148,7 +143,7 @@ class PermissionService {
* @return bool * @return bool
* @throws NoPermissionException * @throws NoPermissionException
*/ */
public function checkPermission($mapper, $id, $permission, $userId = null): bool { public function checkPermission($mapper, $id, $permission, $userId = null) {
$boardId = $id; $boardId = $id;
if ($mapper instanceof IPermissionMapper && !($mapper instanceof BoardMapper)) { if ($mapper instanceof IPermissionMapper && !($mapper instanceof BoardMapper)) {
$boardId = $mapper->findBoardId($id); $boardId = $mapper->findBoardId($id);
@@ -158,11 +153,23 @@ class PermissionService {
throw new NoPermissionException('Permission denied'); throw new NoPermissionException('Permission denied');
} }
$permissions = $this->getPermissions($boardId, $userId); if ($permission === Acl::PERMISSION_SHARE && $this->shareManager->sharingDisabledForUser($this->userId)) {
if ($permissions[$permission] === true) { throw new NoPermissionException('Permission denied');
}
if ($this->userIsBoardOwner($boardId, $userId)) {
return true; return true;
} }
try {
$acls = $this->getBoard($boardId)->getAcl() ?? [];
$result = $this->userCan($acls, $permission, $userId);
if ($result) {
return true;
}
} catch (DoesNotExistException | MultipleObjectsReturnedException $e) {
}
// Throw NoPermission to not leak information about existing entries // Throw NoPermission to not leak information about existing entries
throw new NoPermissionException('Permission denied'); throw new NoPermissionException('Permission denied');
} }
@@ -253,20 +260,22 @@ class PermissionService {
} }
$users = []; $users = [];
if (!$this->userManager->userExists($board->getOwner())) { $owner = $this->userManager->get($board->getOwner());
if ($owner === null) {
$this->logger->info('No owner found for board ' . $board->getId()); $this->logger->info('No owner found for board ' . $board->getId());
} else { } else {
$users[$board->getOwner()] = new User($board->getOwner(), $this->userManager); $users[$owner->getUID()] = new User($owner);
} }
$acls = $this->aclMapper->findAll($boardId); $acls = $this->aclMapper->findAll($boardId);
/** @var Acl $acl */ /** @var Acl $acl */
foreach ($acls as $acl) { foreach ($acls as $acl) {
if ($acl->getType() === Acl::PERMISSION_TYPE_USER) { if ($acl->getType() === Acl::PERMISSION_TYPE_USER) {
if (!$this->userManager->userExists($acl->getParticipant())) { $user = $this->userManager->get($acl->getParticipant());
if ($user === null) {
$this->logger->info('No user found for acl rule ' . $acl->getId()); $this->logger->info('No user found for acl rule ' . $acl->getId());
continue; continue;
} }
$users[$acl->getParticipant()] = new User($acl->getParticipant(), $this->userManager); $users[$user->getUID()] = new User($user);
} }
if ($acl->getType() === Acl::PERMISSION_TYPE_GROUP) { if ($acl->getType() === Acl::PERMISSION_TYPE_GROUP) {
$group = $this->groupManager->get($acl->getParticipant()); $group = $this->groupManager->get($acl->getParticipant());
@@ -275,7 +284,7 @@ class PermissionService {
continue; continue;
} }
foreach ($group->getUsers() as $user) { foreach ($group->getUsers() as $user) {
$users[$user->getUID()] = new User($user->getUID(), $this->userManager); $users[$user->getUID()] = new User($user);
} }
} }
@@ -296,7 +305,7 @@ class PermissionService {
if ($user === null) { if ($user === null) {
$this->logger->info('No user found for circle member ' . $member->getUserId()); $this->logger->info('No user found for circle member ' . $member->getUserId());
} else { } else {
$users[$member->getUserId()] = new User($member->getUserId(), $this->userManager); $users[$member->getUserId()] = new User($user);
} }
} }
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@@ -82,7 +82,11 @@ class SearchService {
}, $boards); }, $boards);
$matchedCards = $this->cardMapper->search($boardIds, $this->filterStringParser->parse($term), $limit, $cursor); $matchedCards = $this->cardMapper->search($boardIds, $this->filterStringParser->parse($term), $limit, $cursor);
return $this->cardService->enrichCards($matchedCards); $self = $this;
return array_map(function (Card $card) use ($self) {
$self->cardService->enrich($card);
return $card;
}, $matchedCards);
} }
public function searchBoards(string $term, ?int $limit, ?int $cursor): array { public function searchBoards(string $term, ?int $limit, ?int $cursor): array {
@@ -113,8 +117,7 @@ class SearchService {
$comment = $this->commentsManager->get($cardRow['comment_id']); $comment = $this->commentsManager->get($cardRow['comment_id']);
unset($cardRow['comment_id']); unset($cardRow['comment_id']);
$card = Card::fromRow($cardRow); $card = Card::fromRow($cardRow);
// TODO: Only perform one enrich call here $self->cardService->enrich($card);
$self->cardService->enrichCards([$card]);
$user = $this->userManager->get($comment->getActorId()); $user = $this->userManager->get($comment->getActorId());
$displayName = $user ? $user->getDisplayName() : ''; $displayName = $user ? $user->getDisplayName() : '';
return new CommentSearchResultEntry($comment->getId(), $comment->getMessage(), $displayName, $card, $this->urlGenerator, $this->l10n); return new CommentSearchResultEntry($comment->getId(), $comment->getMessage(), $displayName, $card, $this->urlGenerator, $this->l10n);

View File

@@ -36,12 +36,10 @@ use OCA\Deck\Db\ChangeHelper;
use OCA\Deck\Db\LabelMapper; use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Db\Stack; use OCA\Deck\Db\Stack;
use OCA\Deck\Db\StackMapper; use OCA\Deck\Db\StackMapper;
use OCA\Deck\Event\BoardUpdatedEvent;
use OCA\Deck\Model\CardDetails; use OCA\Deck\Model\CardDetails;
use OCA\Deck\NoPermissionException; use OCA\Deck\NoPermissionException;
use OCA\Deck\StatusException; use OCA\Deck\StatusException;
use OCA\Deck\Validators\StackServiceValidator; use OCA\Deck\Validators\StackServiceValidator;
use OCP\EventDispatcher\IEventDispatcher;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
class StackService { class StackService {
@@ -57,7 +55,6 @@ class StackService {
private ActivityManager $activityManager; private ActivityManager $activityManager;
private ChangeHelper $changeHelper; private ChangeHelper $changeHelper;
private LoggerInterface $logger; private LoggerInterface $logger;
private IEventDispatcher $eventDispatcher;
private StackServiceValidator $stackServiceValidator; private StackServiceValidator $stackServiceValidator;
public function __construct( public function __construct(
@@ -73,7 +70,6 @@ class StackService {
ActivityManager $activityManager, ActivityManager $activityManager,
ChangeHelper $changeHelper, ChangeHelper $changeHelper,
LoggerInterface $logger, LoggerInterface $logger,
IEventDispatcher $eventDispatcher,
StackServiceValidator $stackServiceValidator StackServiceValidator $stackServiceValidator
) { ) {
$this->stackMapper = $stackMapper; $this->stackMapper = $stackMapper;
@@ -88,7 +84,6 @@ class StackService {
$this->activityManager = $activityManager; $this->activityManager = $activityManager;
$this->changeHelper = $changeHelper; $this->changeHelper = $changeHelper;
$this->logger = $logger; $this->logger = $logger;
$this->eventDispatcher = $eventDispatcher;
$this->stackServiceValidator = $stackServiceValidator; $this->stackServiceValidator = $stackServiceValidator;
} }
@@ -99,9 +94,9 @@ class StackService {
return; return;
} }
$this->cardService->enrichCards($cards);
$cards = array_map( $cards = array_map(
function (Card $card): CardDetails { function (Card $card): CardDetails {
$this->cardService->enrich($card);
return new CardDetails($card); return new CardDetails($card);
}, },
$cards $cards
@@ -242,7 +237,6 @@ class StackService {
ActivityManager::DECK_OBJECT_BOARD, $stack, ActivityManager::SUBJECT_STACK_CREATE ActivityManager::DECK_OBJECT_BOARD, $stack, ActivityManager::SUBJECT_STACK_CREATE
); );
$this->changeHelper->boardChanged($boardId); $this->changeHelper->boardChanged($boardId);
$this->eventDispatcher->dispatchTyped(new BoardUpdatedEvent($boardId));
return $stack; return $stack;
} }
@@ -271,7 +265,6 @@ class StackService {
ActivityManager::DECK_OBJECT_BOARD, $stack, ActivityManager::SUBJECT_STACK_DELETE ActivityManager::DECK_OBJECT_BOARD, $stack, ActivityManager::SUBJECT_STACK_DELETE
); );
$this->changeHelper->boardChanged($stack->getBoardId()); $this->changeHelper->boardChanged($stack->getBoardId());
$this->eventDispatcher->dispatchTyped(new BoardUpdatedEvent($stack->getBoardId()));
$this->enrichStackWithCards($stack); $this->enrichStackWithCards($stack);
return $stack; return $stack;
@@ -313,7 +306,6 @@ class StackService {
ActivityManager::DECK_OBJECT_BOARD, $changes, ActivityManager::SUBJECT_STACK_UPDATE ActivityManager::DECK_OBJECT_BOARD, $changes, ActivityManager::SUBJECT_STACK_UPDATE
); );
$this->changeHelper->boardChanged($stack->getBoardId()); $this->changeHelper->boardChanged($stack->getBoardId());
$this->eventDispatcher->dispatchTyped(new BoardUpdatedEvent($stack->getBoardId()));
return $stack; return $stack;
} }
@@ -353,7 +345,6 @@ class StackService {
$result[$stack->getOrder()] = $stack; $result[$stack->getOrder()] = $stack;
} }
$this->changeHelper->boardChanged($stackToSort->getBoardId()); $this->changeHelper->boardChanged($stackToSort->getBoardId());
$this->eventDispatcher->dispatchTyped(new BoardUpdatedEvent($stackToSort->getBoardId()));
return $result; return $result;
} }

1251
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,109 +1,109 @@
{ {
"name": "deck", "name": "deck",
"description": "", "description": "",
"version": "1.9.0-beta.2", "version": "1.9.0-beta.1",
"authors": [ "authors": [
{ {
"name": "Julius Härtl", "name": "Julius Härtl",
"email": "jus@bitgrid.net", "email": "jus@bitgrid.net",
"role": "Developer" "role": "Developer"
}, },
{ {
"name": "Michael Weimann", "name": "Michael Weimann",
"email": "mail@michael-weimann.eu", "email": "mail@michael-weimann.eu",
"role": "Developer" "role": "Developer"
} }
], ],
"license": "agpl", "license": "agpl",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "NODE_ENV=production webpack --progress --config webpack.js", "build": "NODE_ENV=production webpack --progress --config webpack.js",
"dev": "NODE_ENV=development webpack --progress --config webpack.js", "dev": "NODE_ENV=development webpack --progress --config webpack.js",
"watch": "NODE_ENV=development webpack --progress --watch --config webpack.js", "watch": "NODE_ENV=development webpack --progress --watch --config webpack.js",
"lint": "eslint --ext .js,.vue src", "lint": "eslint --ext .js,.vue src",
"lint:fix": "eslint --ext .js,.vue src --fix", "lint:fix": "eslint --ext .js,.vue src --fix",
"lint:cypress": "eslint --ext .js cypress", "lint:cypress": "eslint --ext .js cypress",
"stylelint": "stylelint src", "stylelint": "stylelint src",
"stylelint:fix": "stylelint src --fix", "stylelint:fix": "stylelint src --fix",
"test": "jest", "test": "jest",
"test:coverage": "jest --coverage" "test:coverage": "jest --coverage"
}, },
"dependencies": { "dependencies": {
"@babel/polyfill": "^7.12.1", "@babel/polyfill": "^7.12.1",
"@babel/runtime": "^7.21.0", "@babel/runtime": "^7.20.13",
"@nextcloud/auth": "^2.0.0", "@nextcloud/auth": "^2.0.0",
"@nextcloud/axios": "^2.3.0", "@nextcloud/axios": "^2.3.0",
"@nextcloud/dialogs": "^4.0.1", "@nextcloud/dialogs": "^3.2.0",
"@nextcloud/event-bus": "^3.0.2", "@nextcloud/event-bus": "^3.0.2",
"@nextcloud/files": "^2.1.0", "@nextcloud/files": "^2.1.0",
"@nextcloud/initial-state": "^2.0.0", "@nextcloud/initial-state": "^2.0.0",
"@nextcloud/l10n": "^2.1.0", "@nextcloud/l10n": "^2.0.1",
"@nextcloud/moment": "^1.2.1", "@nextcloud/moment": "^1.2.1",
"@nextcloud/notify_push": "^1.1.3", "@nextcloud/notify_push": "^1.1.3",
"@nextcloud/router": "^2.0.1", "@nextcloud/router": "^2.0.1",
"@nextcloud/vue": "^7.7.1", "@nextcloud/vue": "^7.5.0",
"@nextcloud/vue-dashboard": "^2.0.1", "@nextcloud/vue-dashboard": "^2.0.1",
"@nextcloud/vue-richtext": "^2.0.4", "@nextcloud/vue-richtext": "^2.0.4",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
"dompurify": "^3.0.0", "dompurify": "^2.4.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
"markdown-it-link-attributes": "^4.0.1", "markdown-it-link-attributes": "^4.0.1",
"markdown-it-task-checkbox": "^1.0.6", "markdown-it-task-checkbox": "^1.0.6",
"moment": "^2.29.4", "moment": "^2.29.4",
"nextcloud-vue-collections": "^0.11.0", "nextcloud-vue-collections": "^0.10.0",
"p-queue": "^7.3.4", "p-queue": "^7.3.4",
"url-search-params-polyfill": "^8.1.1", "url-search-params-polyfill": "^8.1.1",
"vue": "^2.7.14", "vue": "^2.7.14",
"vue-at": "^2.5.1", "vue-at": "^2.5.1",
"vue-click-outside": "^1.1.0", "vue-click-outside": "^1.1.0",
"vue-easymde": "^2.0.0", "vue-easymde": "^2.0.0",
"vue-infinite-loading": "^2.4.5", "vue-infinite-loading": "^2.4.5",
"vue-material-design-icons": "^5.2.0", "vue-material-design-icons": "^5.2.0",
"vue-router": "^3.6.5", "vue-router": "^3.6.5",
"vue-smooth-dnd": "^0.8.1", "vue-smooth-dnd": "^0.8.1",
"vuex": "^3.6.2", "vuex": "^3.6.2",
"vuex-router-sync": "^5.0.0" "vuex-router-sync": "^5.0.0"
}, },
"browserslist": [ "browserslist": [
"extends @nextcloud/browserslist-config" "extends @nextcloud/browserslist-config"
], ],
"engines": { "engines": {
"node": "^16.0.0", "node": "^16.0.0",
"npm": "^7.0.0 || ^8.0.0" "npm": "^7.0.0 || ^8.0.0"
}, },
"devDependencies": { "devDependencies": {
"@nextcloud/babel-config": "^1.0.0", "@nextcloud/babel-config": "^1.0.0",
"@nextcloud/browserslist-config": "^2.3.0", "@nextcloud/browserslist-config": "^2.3.0",
"@nextcloud/cypress": "^1.0.0-beta.2", "@nextcloud/cypress": "^1.0.0-beta.2",
"@nextcloud/eslint-config": "^8.2.1", "@nextcloud/eslint-config": "^8.2.1",
"@nextcloud/stylelint-config": "^2.3.0", "@nextcloud/stylelint-config": "^2.3.0",
"@nextcloud/webpack-vue-config": "^5.4.0", "@nextcloud/webpack-vue-config": "^5.4.0",
"@relative-ci/agent": "^4.1.3", "@relative-ci/agent": "^4.1.3",
"@vue/test-utils": "^1.3.4", "@vue/test-utils": "^1.3.4",
"@vue/vue2-jest": "^29.2.2", "@vue/vue2-jest": "^29.2.2",
"cypress": "^12.7.0", "cypress": "^12.5.1",
"eslint-plugin-cypress": "^2.12.1", "eslint-plugin-cypress": "^2.12.1",
"eslint-webpack-plugin": "^4.0.0", "eslint-webpack-plugin": "^4.0.0",
"jest": "^29.4.3", "jest": "^29.4.3",
"jest-serializer-vue": "^3.1.0", "jest-serializer-vue": "^3.1.0",
"stylelint-webpack-plugin": "^4.1.0", "stylelint-webpack-plugin": "^4.0.0",
"vue-template-compiler": "^2.7.14" "vue-template-compiler": "^2.7.14"
}, },
"jest": { "jest": {
"moduleFileExtensions": [ "moduleFileExtensions": [
"js", "js",
"vue" "vue"
], ],
"moduleNameMapper": { "moduleNameMapper": {
"^@/(.*)$": "<rootDir>/src/$1" "^@/(.*)$": "<rootDir>/src/$1"
}, },
"transform": { "transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest", "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest" ".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
}, },
"snapshotSerializers": [ "snapshotSerializers": [
"<rootDir>/node_modules/jest-serializer-vue" "<rootDir>/node_modules/jest-serializer-vue"
] ]
} }
} }

View File

@@ -95,7 +95,8 @@ export default {
.avatar-wrapper { .avatar-wrapper {
background-color: #b9b9b9; background-color: #b9b9b9;
border-radius: 50%; border-radius: 50%;
border: 1px solid var(--color-border-dark); border-width: 2px;
border-style: solid;
width: var(--size); width: var(--size);
height: var(--size); height: var(--size);
text-align: center; text-align: center;

View File

@@ -150,13 +150,13 @@
import ClickOutside from 'vue-click-outside' import ClickOutside from 'vue-click-outside'
import { mapGetters, mapState } from 'vuex' import { mapGetters, mapState } from 'vuex'
import { Container, Draggable } from 'vue-smooth-dnd' import { Container, Draggable } from 'vue-smooth-dnd'
import ArchiveIcon from 'vue-material-design-icons/Archive.vue'
import { NcActions, NcActionButton, NcModal } from '@nextcloud/vue' import { NcActions, NcActionButton, NcModal } from '@nextcloud/vue'
import { showError, showUndo } from '@nextcloud/dialogs' import { showError, showUndo } from '@nextcloud/dialogs'
import CardItem from '../cards/CardItem.vue' import CardItem from '../cards/CardItem.vue'
import '@nextcloud/dialogs/dist/index.css' import '@nextcloud/dialogs/styles/toast.scss'
import ArchiveIcon from 'vue-material-design-icons/Archive.vue'
export default { export default {
name: 'Stack', name: 'Stack',

View File

@@ -417,7 +417,6 @@ h5 {
.app-sidebar__tab .description__text .text-menubar { .app-sidebar__tab .description__text .text-menubar {
top: -10px !important; top: -10px !important;
z-index: 100;
} }
.modal__card .description__text .text-menubar { .modal__card .description__text .text-menubar {

View File

@@ -89,14 +89,13 @@
<script> <script>
import { NcModal, NcActions, NcActionButton, NcMultiselect } from '@nextcloud/vue' import { NcModal, NcActions, NcActionButton, NcMultiselect } from '@nextcloud/vue'
import { mapGetters, mapState } from 'vuex' import { mapGetters, mapState } from 'vuex'
import ArchiveIcon from 'vue-material-design-icons/Archive.vue'
import CardBulletedIcon from 'vue-material-design-icons/CardBulleted.vue'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth' import { getCurrentUser } from '@nextcloud/auth'
import { showUndo } from '@nextcloud/dialogs' import { showUndo } from '@nextcloud/dialogs'
import '@nextcloud/dialogs/styles/toast.scss'
import '@nextcloud/dialogs/dist/index.css' import ArchiveIcon from 'vue-material-design-icons/Archive.vue'
import CardBulletedIcon from 'vue-material-design-icons/CardBulleted.vue'
export default { export default {
name: 'CardMenu', name: 'CardMenu',

View File

@@ -52,18 +52,6 @@ hasPush = listen('deck_board_update', (name, body) => {
store.dispatch('refreshBoard', currentBoardId) store.dispatch('refreshBoard', currentBoardId)
}) })
listen('deck_card_update', (name, body) => {
// ignore update events which we have triggered ourselves
if (isOurSessionToken(body._causingSessionToken)) return
// only handle update events for the currently open board
const currentBoardId = store.state.currentBoard?.id
if (body.boardId !== currentBoardId) return
store.dispatch('loadStacks', currentBoardId)
})
/** /**
* is the notify_push app active and can * is the notify_push app active and can
* provide us with real time updates? * provide us with real time updates?

View File

@@ -273,17 +273,6 @@ export default {
addNewCard(state, card) { addNewCard(state, card) {
state.cards.push(card) state.cards.push(card)
}, },
setCards(state, cards) {
const deletedCards = state.cards.filter(_card => {
return cards.findIndex(c => _card.id === c.id) === -1
})
for (const card of deletedCards) {
this.commit('deleteCard', card)
}
for (const card of cards) {
this.commit('addCard', card)
}
},
}, },
actions: { actions: {
async addCard({ commit }, card) { async addCard({ commit }, card) {

View File

@@ -333,15 +333,10 @@ export default new Vuex.Store({
commit('setAssignableUsers', board.users) commit('setAssignableUsers', board.users)
}, },
async refreshBoard({ commit, dispatch }, boardId) { async refreshBoard({ commit }, boardId) {
const board = await apiClient.loadById(boardId) const board = await apiClient.loadById(boardId)
const etagHasChanged = board.ETag !== this.state.currentBoard.ETag
commit('setCurrentBoard', board) commit('setCurrentBoard', board)
commit('setAssignableUsers', board.users) commit('setAssignableUsers', board.users)
if (etagHasChanged) {
dispatch('loadStacks', boardId)
}
}, },
toggleShowArchived({ commit }) { toggleShowArchived({ commit }) {

View File

@@ -84,16 +84,14 @@ export default {
call = 'loadArchivedStacks' call = 'loadArchivedStacks'
} }
const stacks = await apiClient[call](boardId) const stacks = await apiClient[call](boardId)
const cards = []
for (const i in stacks) { for (const i in stacks) {
const stack = stacks[i] const stack = stacks[i]
for (const j in stack.cards) { for (const j in stack.cards) {
cards.push(stack.cards[j]) commit('addCard', stack.cards[j])
} }
delete stack.cards delete stack.cards
commit('addStack', stack) commit('addStack', stack)
} }
commit('setCards', cards)
}, },
createStack({ commit }, stack) { createStack({ commit }, stack) {
stack.boardId = this.state.currentBoard.id stack.boardId = this.state.currentBoard.id

View File

@@ -24,7 +24,6 @@
namespace OCA\Deck\Db; namespace OCA\Deck\Db;
use OCP\IUser; use OCP\IUser;
use OCP\IUserManager;
class UserTest extends \Test\TestCase { class UserTest extends \Test\TestCase {
public function testGroupObjectSerialize() { public function testGroupObjectSerialize() {
@@ -36,11 +35,7 @@ class UserTest extends \Test\TestCase {
$user->expects($this->any()) $user->expects($this->any())
->method('getDisplayName') ->method('getDisplayName')
->willReturn('myuser displayname'); ->willReturn('myuser displayname');
$userManager = $this->createMock(IUserManager::class); $userRelationalObject = new User($user);
$userManager->expects($this->any())
->method('get')
->willReturn($user);
$userRelationalObject = new User('myuser', $userManager);
$expected = [ $expected = [
'uid' => 'myuser', 'uid' => 'myuser',
'displayname' => 'myuser displayname', 'displayname' => 'myuser displayname',
@@ -58,11 +53,7 @@ class UserTest extends \Test\TestCase {
$user->expects($this->any()) $user->expects($this->any())
->method('getDisplayName') ->method('getDisplayName')
->willReturn('myuser displayname'); ->willReturn('myuser displayname');
$userManager = $this->createMock(IUserManager::class); $userRelationalObject = new User($user);
$userManager->expects($this->any())
->method('get')
->willReturn($user);
$userRelationalObject = new User('myuser', $userManager);
$expected = [ $expected = [
'uid' => 'myuser', 'uid' => 'myuser',
'displayname' => 'myuser displayname', 'displayname' => 'myuser displayname',

View File

@@ -37,7 +37,6 @@ use OCP\IConfig;
use OCP\IGroup; use OCP\IGroup;
use OCP\IGroupManager; use OCP\IGroupManager;
use OCP\IUser; use OCP\IUser;
use OCP\IUserManager;
use OCP\Notification\IManager; use OCP\Notification\IManager;
use OCP\Notification\INotification; use OCP\Notification\INotification;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
@@ -220,9 +219,8 @@ class NotificationHelperTest extends \Test\TestCase {
'title' => 'MyCardTitle', 'title' => 'MyCardTitle',
'duedate' => '2020-12-24' 'duedate' => '2020-12-24'
]); ]);
$userManager = $this->createMock(IUserManager::class);
$card->setAssignedUsers([ $card->setAssignedUsers([
new User($users[0]->getUID(), $userManager) new User($users[0])
]); ]);
$this->cardMapper->expects($this->once()) $this->cardMapper->expects($this->once())
->method('findBoardId') ->method('findBoardId')
@@ -310,9 +308,8 @@ class NotificationHelperTest extends \Test\TestCase {
'title' => 'MyCardTitle', 'title' => 'MyCardTitle',
'duedate' => '2020-12-24' 'duedate' => '2020-12-24'
]); ]);
$userManager = $this->createMock(IUserManager::class);
$card->setAssignedUsers([ $card->setAssignedUsers([
new User($users[0]->getUID(), $userManager) new User($users[0])
]); ]);
$this->cardMapper->expects($this->once()) $this->cardMapper->expects($this->once())
->method('findBoardId') ->method('findBoardId')

View File

@@ -219,7 +219,6 @@ class BoardServiceTest extends TestCase {
public function testUpdate() { public function testUpdate() {
$board = new Board(); $board = new Board();
$board->setId(123);
$board->setTitle('MyBoard'); $board->setTitle('MyBoard');
$board->setOwner('admin'); $board->setOwner('admin');
$board->setColor('00ff00'); $board->setColor('00ff00');

View File

@@ -24,7 +24,6 @@
namespace OCA\Deck\Service; namespace OCA\Deck\Service;
use OCA\Deck\Activity\ActivityManager; use OCA\Deck\Activity\ActivityManager;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\AssignmentMapper; use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\Board; use OCA\Deck\Db\Board;
use OCA\Deck\Db\Card; use OCA\Deck\Db\Card;
@@ -156,8 +155,7 @@ class CardServiceTest extends TestCase {
->method('getNumberOfCommentsForObject') ->method('getNumberOfCommentsForObject')
->willReturn(0); ->willReturn(0);
$boardMock = $this->createMock(Board::class); $boardMock = $this->createMock(Board::class);
$stackMock = new Stack(); $stackMock = $this->createMock(Stack::class);
$stackMock->setBoardId(1234);
$this->stackMapper->expects($this->any()) $this->stackMapper->expects($this->any())
->method('find') ->method('find')
->willReturn($stackMock); ->willReturn($stackMock);
@@ -170,21 +168,13 @@ class CardServiceTest extends TestCase {
->method('find') ->method('find')
->with(123) ->with(123)
->willReturn($card); ->willReturn($card);
$a1 = new Assignment();
$a1->setCardId(1337);
$a1->setType(0);
$a1->setParticipant('user1');
$a2 = new Assignment();
$a2->setCardId(1337);
$a2->setType(0);
$a2->setParticipant('user2');
$this->assignedUsersMapper->expects($this->any()) $this->assignedUsersMapper->expects($this->any())
->method('findIn') ->method('findAll')
->with([1337]) ->with(1337)
->willReturn([$a1, $a2]); ->willReturn(['user1', 'user2']);
$cardExpected = new Card(); $cardExpected = new Card();
$cardExpected->setId(1337); $cardExpected->setId(1337);
$cardExpected->setAssignedUsers([$a1, $a2]); $cardExpected->setAssignedUsers(['user1', 'user2']);
$cardExpected->setRelatedBoard($boardMock); $cardExpected->setRelatedBoard($boardMock);
$cardExpected->setRelatedStack($stackMock); $cardExpected->setRelatedStack($stackMock);
$cardExpected->setLabels([]); $cardExpected->setLabels([]);

View File

@@ -83,6 +83,10 @@ class DefaultBoardServiceTest extends TestCase {
->method('getUserValue') ->method('getUserValue')
->willReturn('yes'); ->willReturn('yes');
$this->boardMapper->expects($this->once())
->method('findAllByUser')
->willReturn($userBoards);
$this->config->expects($this->once()) $this->config->expects($this->once())
->method('setUserValue'); ->method('setUserValue');
@@ -103,6 +107,10 @@ class DefaultBoardServiceTest extends TestCase {
->method('getUserValue') ->method('getUserValue')
->willReturn('no'); ->willReturn('no');
$this->boardMapper->expects($this->once())
->method('findAllByUser')
->willReturn($userBoards);
$result = $this->service->checkFirstRun($this->userId); $result = $this->service->checkFirstRun($this->userId);
$this->assertEquals($result, false); $this->assertEquals($result, false);
} }

View File

@@ -30,9 +30,6 @@ use OCP\IUserManager;
use OCP\Server; use OCP\Server;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
/**
* @group DB
*/
class TrelloJsonServiceTest extends \Test\TestCase { class TrelloJsonServiceTest extends \Test\TestCase {
private TrelloJsonService $service; private TrelloJsonService $service;
/** @var IURLGenerator|MockObject */ /** @var IURLGenerator|MockObject */
@@ -60,7 +57,7 @@ class TrelloJsonServiceTest extends \Test\TestCase {
} }
public function testValidateUsersWithInvalidUser() { public function testValidateUsersWithInvalidUser() {
$this->expectExceptionMessage('Trello user trello_user not found in property "members" of json data'); $this->expectErrorMessage('Trello user trello_user not found in property "members" of json data');
$importService = $this->createMock(BoardImportService::class); $importService = $this->createMock(BoardImportService::class);
$importService $importService
->method('getConfig') ->method('getConfig')

View File

@@ -236,11 +236,6 @@ class PermissionServiceTest extends \Test\TestCase {
$board->setAcl($this->getAcls($boardId)); $board->setAcl($this->getAcls($boardId));
$this->boardMapper->expects($this->any())->method('find')->willReturn($board); $this->boardMapper->expects($this->any())->method('find')->willReturn($board);
$this->aclMapper->expects($this->any())
->method('findAll')
->with($boardId)
->willReturn($this->getAcls($boardId));
$this->shareManager->expects($this->any()) $this->shareManager->expects($this->any())
->method('sharingDisabledForUser') ->method('sharingDisabledForUser')
->willReturn(false); ->willReturn(false);
@@ -267,17 +262,12 @@ class PermissionServiceTest extends \Test\TestCase {
$this->boardMapper->expects($this->any())->method('find')->willReturn($board); $this->boardMapper->expects($this->any())->method('find')->willReturn($board);
} }
$this->aclMapper->expects($this->any())
->method('findAll')
->with($boardId)
->willReturn($this->getAcls($boardId));
if ($result) { if ($result) {
$actual = $this->service->checkPermission($mapper, $boardId, $permission); $actual = $this->service->checkPermission($mapper, 1234, $permission);
$this->assertTrue($actual); $this->assertTrue($actual);
} else { } else {
$this->expectException(NoPermissionException::class); $this->expectException(NoPermissionException::class);
$this->service->checkPermission($mapper, $boardId, $permission); $this->service->checkPermission($mapper, 1234, $permission);
} }
} }
@@ -350,7 +340,7 @@ class PermissionServiceTest extends \Test\TestCase {
$aclGroup->setParticipant('group1'); $aclGroup->setParticipant('group1');
$board = $this->createMock(Board::class); $board = $this->createMock(Board::class);
$board->expects($this->any()) $board->expects($this->once())
->method('__call') ->method('__call')
->with('getOwner', []) ->with('getOwner', [])
->willReturn('user1'); ->willReturn('user1');
@@ -362,8 +352,8 @@ class PermissionServiceTest extends \Test\TestCase {
->method('find') ->method('find')
->with(123) ->with(123)
->willReturn($board); ->willReturn($board);
$this->userManager->expects($this->any()) $this->userManager->expects($this->exactly(2))
->method('userExists') ->method('get')
->withConsecutive(['user1'], ['user2']) ->withConsecutive(['user1'], ['user2'])
->willReturnOnConsecutiveCalls($user1, $user2); ->willReturnOnConsecutiveCalls($user1, $user2);
@@ -377,9 +367,9 @@ class PermissionServiceTest extends \Test\TestCase {
->willReturn($group); ->willReturn($group);
$users = $this->service->findUsers(123); $users = $this->service->findUsers(123);
$this->assertEquals([ $this->assertEquals([
'user1' => new User($user1->getUID(), $this->userManager), 'user1' => new User($user1),
'user2' => new User($user2->getUID(), $this->userManager), 'user2' => new User($user2),
'user3' => new User($user3->getUID(), $this->userManager), 'user3' => new User($user3),
], $users); ], $users);
} }
} }

View File

@@ -34,7 +34,6 @@ use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Db\Stack; use OCA\Deck\Db\Stack;
use OCA\Deck\Db\StackMapper; use OCA\Deck\Db\StackMapper;
use OCA\Deck\Validators\StackServiceValidator; use OCA\Deck\Validators\StackServiceValidator;
use OCP\EventDispatcher\IEventDispatcher;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use \Test\TestCase; use \Test\TestCase;
@@ -72,8 +71,6 @@ class StackServiceTest extends TestCase {
private $changeHelper; private $changeHelper;
/** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
private $logger; private $logger;
/** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */
private $eventDispatcher;
/** @var StackServiceValidator|\PHPUnit\Framework\MockObject\MockObject */ /** @var StackServiceValidator|\PHPUnit\Framework\MockObject\MockObject */
private $stackServiceValidator; private $stackServiceValidator;
@@ -91,7 +88,6 @@ class StackServiceTest extends TestCase {
$this->activityManager = $this->createMock(ActivityManager::class); $this->activityManager = $this->createMock(ActivityManager::class);
$this->changeHelper = $this->createMock(ChangeHelper::class); $this->changeHelper = $this->createMock(ChangeHelper::class);
$this->logger = $this->createMock(LoggerInterface::class); $this->logger = $this->createMock(LoggerInterface::class);
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
$this->stackServiceValidator = $this->createMock(StackServiceValidator::class); $this->stackServiceValidator = $this->createMock(StackServiceValidator::class);
$this->stackService = new StackService( $this->stackService = new StackService(
@@ -107,7 +103,6 @@ class StackServiceTest extends TestCase {
$this->activityManager, $this->activityManager,
$this->changeHelper, $this->changeHelper,
$this->logger, $this->logger,
$this->eventDispatcher,
$this->stackServiceValidator $this->stackServiceValidator
); );
} }
@@ -115,12 +110,10 @@ class StackServiceTest extends TestCase {
public function testFindAll() { public function testFindAll() {
$this->permissionService->expects($this->once())->method('checkPermission'); $this->permissionService->expects($this->once())->method('checkPermission');
$this->stackMapper->expects($this->once())->method('findAll')->willReturn($this->getStacks()); $this->stackMapper->expects($this->once())->method('findAll')->willReturn($this->getStacks());
$this->cardService->expects($this->atLeastOnce())->method('enrichCards')->will( $this->cardService->expects($this->atLeastOnce())->method('enrich')->will(
$this->returnCallback( $this->returnCallback(
function ($cards) { function ($card) {
foreach ($cards as $card) { $card->setLabels($this->getLabels()[$card->getId()]);
$card->setLabels($this->getLabels()[$card->getId()]);
}
} }
) )
); );
@@ -203,7 +196,6 @@ class StackServiceTest extends TestCase {
$this->permissionService->expects($this->once())->method('checkPermission'); $this->permissionService->expects($this->once())->method('checkPermission');
$stackToBeDeleted = new Stack(); $stackToBeDeleted = new Stack();
$stackToBeDeleted->setId(1); $stackToBeDeleted->setId(1);
$stackToBeDeleted->setBoardId(1);
$this->stackMapper->expects($this->once())->method('find')->willReturn($stackToBeDeleted); $this->stackMapper->expects($this->once())->method('find')->willReturn($stackToBeDeleted);
$this->stackMapper->expects($this->once())->method('update')->willReturn($stackToBeDeleted); $this->stackMapper->expects($this->once())->method('update')->willReturn($stackToBeDeleted);
$this->cardMapper->expects($this->once())->method('findAll')->willReturn([]); $this->cardMapper->expects($this->once())->method('findAll')->willReturn([]);
@@ -252,7 +244,6 @@ class StackServiceTest extends TestCase {
private function createStack($id, $order) { private function createStack($id, $order) {
$stack = new Stack(); $stack = new Stack();
$stack->setId($id); $stack->setId($id);
$stack->setBoardId(1);
$stack->setOrder($order); $stack->setOrder($order);
return $stack; return $stack;
} }

View File

@@ -24,7 +24,6 @@
namespace OCA\Deck\Controller; namespace OCA\Deck\Controller;
use OCA\Deck\Db\Acl; use OCA\Deck\Db\Acl;
use OCA\Deck\Db\Board;
use OCP\IUser; use OCP\IUser;
class BoardControllerTest extends \Test\TestCase { class BoardControllerTest extends \Test\TestCase {
@@ -89,12 +88,11 @@ class BoardControllerTest extends \Test\TestCase {
} }
public function testRead() { public function testRead() {
$board = new Board();
$this->boardService->expects($this->once()) $this->boardService->expects($this->once())
->method('find') ->method('find')
->with(123) ->with(123)
->willReturn($board); ->willReturn(1);
$this->assertEquals($board, $this->controller->read(123)); $this->assertEquals(1, $this->controller->read(123));
} }
public function testCreate() { public function testCreate() {