Compare commits

..

16 Commits

Author SHA1 Message Date
Julius Härtl
aa41f383cc Merge pull request #556 from nextcloud/release/v0.4.1
Prepare 0.4.1
2018-07-28 13:40:08 +02:00
Julius Härtl
81ea719329 Prepare changelog for 0.4.1
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-07-28 13:28:11 +02:00
Julius Härtl
498d062657 Fix more 14 layout issues
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-07-28 13:10:42 +02:00
Julius Härtl
480d01fe9d Fix eslint
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-07-28 13:10:42 +02:00
Julius Härtl
38ec3ec0ca Cleanup css
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-07-28 13:10:42 +02:00
Julius Härtl
0dd70fb461 Move app sidebar handling to angular/css
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-07-28 13:10:41 +02:00
Julius Härtl
123e5998a9 Add CSS rules for 13 to be backward compatible
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-07-28 13:10:41 +02:00
Julius Härtl
9e21c8597f Move app sidebar out of app content
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-07-28 13:10:41 +02:00
Julius Härtl
aa4db7f789 Check when assigning users
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-07-28 13:09:04 +02:00
Julius Härtl
9b63e4d745 Simly remove 4byte chars from the description if those are not supported
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-07-28 13:08:49 +02:00
Julius Härtl
dfe6e2f216 Cast uploadLimit to integer to catch possible INF result
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-07-28 13:08:26 +02:00
Julius Härtl
0c7ff7477b Bump version to 0.4.1
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-07-25 18:48:08 +02:00
Nextcloud bot
a1fc5cd809 [tx-robot] updated from transifex 2018-07-25 18:46:47 +02:00
Nextcloud bot
7af9ff68ad [tx-robot] updated from transifex 2018-07-25 18:46:42 +02:00
Nextcloud bot
c2fb501f4e [tx-robot] updated from transifex 2018-07-25 18:46:35 +02:00
Julius Härtl
2736783bf3 Bump version to 0.4.1-dev
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-07-25 18:41:26 +02:00
365 changed files with 8272 additions and 28953 deletions

View File

@@ -1,65 +1,150 @@
kind: pipeline
name: checkers
steps:
- name: compatibility
image: nextcloudci/php7.2:php7.2-13
environment:
APP_NAME: deck
CORE_BRANCH: master
DB: sqlite
commands:
# Pre-setup steps
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
- bash ./before_install.sh $APP_NAME $CORE_BRANCH $DB
- cd ../server
# Code checker
- ./occ app:check-code $APP_NAME -c strong-comparison
- ./occ app:check-code $APP_NAME -c deprecation
- cd apps/$APP_NAME/
- name: syntax-php7.2
image: nextcloudci/php7.2:php7.2-13
environment:
APP_NAME: deck
CORE_BRANCH: master
DB: sqlite
commands:
- composer install
- ./vendor/bin/parallel-lint --exclude ./vendor/ .
- name: syntax-php7.3
image: nextcloudci/php7.3:php7.3-2
environment:
APP_NAME: deck
CORE_BRANCH: master
DB: sqlite
commands:
- composer install
- ./vendor/bin/parallel-lint --exclude ./vendor/ .
- name: syntax-php7.4
image: nextcloudci/php7.4:2
environment:
APP_NAME: deck
CORE_BRANCH: master
DB: sqlite
commands:
clone:
git:
image: plugins/git
depth: 1
pipeline:
check-app-compatbility:
image: nextcloudci/php7.0:php7.0-17
environment:
- APP_NAME=deck
- CORE_BRANCH=master
- DB=sqlite
commands:
# Pre-setup steps
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
- bash ./before_install.sh $APP_NAME $CORE_BRANCH $DB
- cd ../server
# Code checker
- ./occ app:check-code $APP_NAME -c strong-comparison
- ./occ app:check-code $APP_NAME -c deprecation
- cd apps/$APP_NAME/
when:
matrix:
TESTS: check-app-compatbility
check-app-compatbility-12:
image: nextcloudci/php7.0:php7.0-17
environment:
- APP_NAME=deck
- CORE_BRANCH=stable12
- DB=sqlite
commands:
# Pre-setup steps
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
- bash ./before_install.sh $APP_NAME $CORE_BRANCH $DB
- cd ../server
# Code checker
- ./occ app:check-code $APP_NAME -c strong-comparison
- ./occ app:check-code $APP_NAME -c deprecation
- cd apps/$APP_NAME/
when:
matrix:
TESTS: check-app-compatbility-13
check-app-compatbility-12:
image: nextcloudci/php7.0:php7.0-17
environment:
- APP_NAME=deck
- CORE_BRANCH=stable12
- DB=sqlite
commands:
# Pre-setup steps
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
- bash ./before_install.sh $APP_NAME $CORE_BRANCH $DB
- cd ../server
# Code checker
- ./occ app:check-code $APP_NAME -c strong-comparison
- ./occ app:check-code $APP_NAME -c deprecation
- cd apps/$APP_NAME/
when:
matrix:
TESTS: check-app-compatbility-12
signed-off-check:
image: nextcloudci/php7.0:php7.0-17
environment:
- APP_NAME=deck
- CORE_BRANCH=master
- DB=sqlite
commands:
- wget https://raw.githubusercontent.com/nextcloud/server/master/build/signed-off-checker.php
- php ./signed-off-checker.php
secrets: [ github_token ]
when:
matrix:
TESTS: signed-off-check
syntax-php5.6:
image: nextcloudci/php5.6:php5.6-8
environment:
- APP_NAME=deck
- CORE_BRANCH=stable13
- DB=sqlite
commands:
- composer install
- ./vendor/bin/parallel-lint --exclude ./vendor/ .
trigger:
branch:
- master
- stable*
event:
- pull_request
- push
---
kind: pipeline
name: unit-php7.2
steps:
- name: php7.2
image: nextcloudci/php7.2:php7.2-13
when:
matrix:
TESTS: syntax-php5.6
syntax-php7.0:
image: nextcloudci/php7.0:php7.0-17
environment:
APP_NAME: deck
CORE_BRANCH: master
DB: sqlite
- APP_NAME=deck
- CORE_BRANCH=master
- DB=sqlite
commands:
- composer install
- ./vendor/bin/parallel-lint --exclude ./vendor/ .
when:
matrix:
TESTS: syntax-php7.0
syntax-php7.1:
image: nextcloudci/php7.1:php7.1-15
environment:
- APP_NAME=deck
- CORE_BRANCH=master
- DB=sqlite
commands:
- composer install
- ./vendor/bin/parallel-lint --exclude ./vendor/ .
when:
matrix:
TESTS: syntax-php7.1
syntax-php7.2:
image: nextcloudci/php7.2:php7.2-9
environment:
- APP_NAME=deck
- CORE_BRANCH=master
- DB=sqlite
commands:
- composer install
- ./vendor/bin/parallel-lint --exclude ./vendor/ .
when:
matrix:
TESTS: syntax-php7.2
php5.6:
image: nextcloudci/php5.6:php5.6-8
environment:
- APP_NAME=deck
- CORE_BRANCH=stable13
- DB=sqlite
commands:
- apt update && apt-get -y install php5-xdebug
# Pre-setup steps
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
- bash ./before_install.sh $APP_NAME $CORE_BRANCH $DB
- cd ../server/
- ./occ app:enable $APP_NAME
- cd apps/$APP_NAME
- phpunit -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
- phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
when:
matrix:
TESTS: php5.6
php7.0:
image: nextcloudci/php7.0:php7.0-17
environment:
- APP_NAME=deck
- CORE_BRANCH=master
- DB=sqlite
commands:
# Pre-setup steps
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
@@ -67,26 +152,18 @@ steps:
- cd ../server/
- php occ app:enable deck
- cd apps/$APP_NAME
- composer install
# Run phpunit tests
- phpunit -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
- phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
trigger:
branch:
- master
- stable*
event:
- pull_request
- push
---
kind: pipeline
name: unit-php7.3
steps:
- name: php7.3
image: nextcloudci/php7.3:php7.3-5
when:
matrix:
TESTS: php7.0
php7.1:
image: nextcloudci/php7.1:php7.1-15
environment:
APP_NAME: deck
CORE_BRANCH: master
DB: sqlite
- APP_NAME=deck
- CORE_BRANCH=master
- DB=sqlite
commands:
# Pre-setup steps
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
@@ -94,26 +171,35 @@ steps:
- cd ../server/
- php occ app:enable deck
- cd apps/$APP_NAME
- composer install
- phpunit -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
- phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
trigger:
branch:
- master
- stable*
event:
- pull_request
- push
---
kind: pipeline
name: integration
steps:
- name: integration
image: nextcloudci/php7.2:php7.2-13
when:
matrix:
TESTS: php7.1
php7.2:
image: nextcloudci/php7.2:php7.2-9
environment:
APP_NAME: deck
CORE_BRANCH: master
DB: sqlite
- APP_NAME=deck
- CORE_BRANCH=master
- DB=sqlite
commands:
# Pre-setup steps
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
- bash ./before_install.sh $APP_NAME $CORE_BRANCH $DB
- cd ../server/
- php occ app:enable deck
- cd apps/$APP_NAME
- phpunit -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
- phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
when:
matrix:
TESTS: php7.2
integration:
image: nextcloudci/integration-php7.0:integration-php7.0-6
environment:
- APP_NAME=deck
- CORE_BRANCH=master
- DB=sqlite
commands:
# Pre-setup steps
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
@@ -122,32 +208,43 @@ steps:
- php occ app:enable deck
- cd apps/$APP_NAME
- cd tests/integration
- ./run.sh || true
trigger:
branch:
- master
- stable*
event:
- pull_request
- push
---
kind: pipeline
name: frontend
steps:
- name: eslint
- ./run.sh
when:
matrix:
TESTS: integration
eslint:
image: nextcloudci/eslint:eslint-1
commands:
- ./run-eslint.sh
- name: jsbuild
image: node:lts-alpine
when:
matrix:
TESTS: eslint
jsbuild:
image: mhart/alpine-node:6.8.0
commands:
- apk add --no-cache make
- make build-js
trigger:
branch:
- master
- stable*
event:
- pull_request
- push
when:
matrix:
TESTS: jsbuild
matrix:
include:
- TESTS: check-app-compatbility
- TESTS: check-app-compatbility-12
- TESTS: check-app-compatbility-13
- TESTS: signed-off-check
- TESTS: syntax-php5.6
- TESTS: syntax-php7.0
- TESTS: syntax-php7.1
- TESTS: syntax-php7.2
- TESTS: php5.6
- TESTS: php7.0
- TESTS: php7.1
- TESTS: php7.2
- TESTS: eslint
- TESTS: jsbuild
#- TESTS: integration
branches: [ master, stable* ]

View File

@@ -6,7 +6,6 @@ extends:
env:
browser: true
amd: true
es6: true
globals:
global: false

View File

@@ -1,17 +0,0 @@
* Resolves: # <!-- related github issue -->
* Target version: master
### Summary
### TODO
- [ ] ...
### Checklist
- [ ] Code is properly formatted
- [ ] Sign-off message is added to all commits
- [ ] Tests (unit, integration, api and/or acceptance) are included
- [ ] Documentation (manuals or wiki) has been updated or is not required

25
.github/stale.yml vendored
View File

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

2
.gitignore vendored
View File

@@ -9,5 +9,3 @@ tests/integration/vendor/
tests/integration/composer.lock
vendor/
*.lock
\.idea/

View File

@@ -1,31 +0,0 @@
/build/
/.git
/js/node_modules
/js/tests
/js/legacy
/js/controller
/js/directive
/js/filters
/js/service
/js/bower.json
/js/.bowerrc
/js/.jshintrc
/js/Gruntfile.js
/js/package.json
/js/package-lock.json
/docs/
/tests
/.codecov.yml
/composer.json
/composer.lock
/_config.yml
/.drone.yml
/.travis.yml
/.eslintignore
/.eslintrc.yml
/.gitignore
/issue_template.md
/krankerl.toml
/Makefile
/mkdocs.yml
/run-eslint.sh

View File

@@ -2,13 +2,18 @@ language: php
services:
- mysql
php:
- 7.0
- 7.1
- 7.2
- 7.3
env:
- CORE_BRANCH=master DB=mysql
before_install:
- export PATH="$PWD/vendor/bin:$PATH"
- wget https://phar.phpunit.de/phpunit-5.7.phar
- chmod +x phpunit-5.7.phar
- mkdir bin
- mv phpunit-5.7.phar bin/phpunit
- export PATH="$PWD/bin:$PATH"
- phpunit --version
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
- bash ./before_install.sh deck $CORE_BRANCH $DB
@@ -19,7 +24,6 @@ before_script:
- cd apps/deck
script:
- composer install
- make test-unit
after_success:

View File

@@ -1,157 +1,17 @@
# Changelog
All notable changes to this project will be documented in this file.
## 0.8.2 - 2020-04-08
## Fixed
- Fix build with missing javascript bundles
## 0.8.1 - 2020-04-08
## Added
- Nextcloud 19 compatibility
## Fixed
- Let new shares only use the current users permissions
- Check for board membership on comments entity event
## 0.8.0 - 2020-01-16
## Added
- Case insensitive search (@matchish)
## Fixed
- Fix reversed permissions for reordering stacks (@JLueke)
- Fix reversed visibility of 'add stack' field (@JLueke)
- Fix occ export command
- Fix error causing cron execution to fail
- Fix activity entry on moving cards
- Proper wording in activity timeline (@a11exandru)
## 0.7.0 - 2019-08-20
## Added
- Make deck compatible to Nextcloud 17
- Allow to set the description when creating cards though the REST API
## 0.6.6 - 2019-08-01
### Fixed
- Bump security related dependencies
## 0.6.5 - 2019-07-28
### Fixed
- Fix attachment upload/delete failures
- Bump dependencies
## 0.6.4 - 2019-06-30
### Fixed
- Restore stable15 compatibility
## 0.6.3 - 2019-06-30
### Fixed
- Fix issues with comments and activity stream
- Fix setting archived state through API
- Fix type of acl in API responses
- Fix type mismatch with fulltext search
## 0.6.2 - 2019-05-15
### Fixed
- Fix group limit for nonexisting groups
- Only map circle ACLs if the app is enabled
- Fix updating sharing permissions
- Add app version to capabilities
## 0.6.1 - 2019-04-27
### Fixed
- Fix issue with boards not being shown after update
- Fix board selection in projects view outside of deck
- Remove collections text from sidebar
- Remove leftover use statement
## 0.6.0 - 2019-04-23
### Added
- Share boards with circles
- Integration with collections in Nextcloud 16
- Support for full text search
- Nextcloud 16 compatibility
### Fixed
- Fix duplicate call to delete
- Prevent duplicate tag names @jakobroehrl
- Prevent loading details when editing the card title @jakobroehrl
- Hide sidebar after card deletion @jakobroehrl
- Update labels after change in the UI @jakobroehrl
- Allow limiting the app to groups again
- Various REST API enhancements and fixes
- Fix some issues with comments/activites
## 0.5.2 - 2018-12-20
### Fixed
- Mark notification as read if a card with duedate gets archived
- Use proper timezone and locale format for due date activities
- Various translation fixes and updates
- Check group limit properly
- Fix comment activities on Nextcloud 15
- Fix issues with Edge
- API: Fix numeric types that were returned as strings
- API: Fix If-Modified-Since header parsing
## 0.5.1 - 2018-12-05
### Added
- Separate settings for description changes in activity
- Less verbose description change activities
- Use server settings to restrict sharing to groups
- Add setting to exclude groups from creating their own boards
### Fixed
- Fix issue when using a separate table prefix @bpcurse
- Fix invalid activity parameters being published
- Wording fixes @cloud2018
- Improve loading performance by removing unused activity preloading
- Fix timestamp issues in deleted items tab
- Remember show state of the board navigation @weeman1337
- Add optional classes for custom styling @tinko92
- Fix missing details on activity emails
- Fix unrelated comments in board activity list
- Fix search not working properly
- Trigger comment notification on update only
## 0.5.0 - 2018-11-15
## 0.4.1 - 2018-07-28
### Added
- Activity stream for board and cards
- Comments on cards
- Use users locale format on date picker
- Compact display mode
- Card title inline editing
- REST API
- Empty content view for board lists
- Undo for card and stack deletion
- Show tag name on board
- Notify users about card assignments
- Add shortcut to assign a card to yourself
- Improved view for printing
- Support for Nextcloud 15
- Make app compatible with Nextcloud 14
### Fixed
- Accesibility improvements
- Don't allow empty card titles
- Improved checkbox handling in markdown
- Fix bug with file upload on unlimited quota
- Fix issue on MySQL databases that don't support 4-byte characters
- Fix check when assigning users
## 0.4.0 - 2018-07-11

View File

@@ -20,15 +20,12 @@ clean-build:
clean-dist:
rm -rf js/node_modules
install-deps: install-deps-js
composer install
install-deps-js:
install-deps:
cd js && npm install
build: install-deps build-js
build: build-js
build-js: install-deps-js
build-js: install-deps
cd js && npm run build
build-js-dev: install-deps
@@ -84,7 +81,7 @@ test-unit:
ifeq (, $(shell which phpunit 2> /dev/null))
@echo "No phpunit command available, downloading a copy from the web"
mkdir -p $(build_tools_directory)
curl -sSL https://phar.phpunit.de/phpunit-8.2.phar -o $(build_tools_directory)/phpunit.phar
curl -sSL https://phar.phpunit.de/phpunit-5.7.phar -o $(build_tools_directory)/phpunit.phar
php $(build_tools_directory)/phpunit.phar -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
php $(build_tools_directory)/phpunit.phar -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
else

View File

@@ -1,6 +1,6 @@
# Deck
[![Build Status](https://travis-ci.org/nextcloud/deck.svg?branch=master)](https://travis-ci.org/nextcloud/deck) [![CodeCov](https://codecov.io/github/nextcloud/deck/coverage.svg?branch=master)](https://codecov.io/github/nextcloud/deck) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/e403f723f42a4abd93b2cfe36cbd7eee)](https://www.codacy.com/app/juliushaertl/deck?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=nextcloud/deck&amp;utm_campaign=Badge_Grade) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/nextcloud/deck/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/nextcloud/deck/?branch=master) [![#nextcloud-deck](https://img.shields.io/badge/IRC-%23nextcloud--deck%20on%20freenode-blue.svg)](https://webchat.freenode.net/?channels=nextcloud-deck)
[![Build Status](https://travis-ci.org/nextcloud/deck.svg?branch=master)](https://travis-ci.org/nextcloud/deck) [![CodeCov](https://codecov.io/github/nextcloud/deck/coverage.svg?branch=master)](https://codecov.io/github/nextcloud/deck) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/e403f723f42a4abd93b2cfe36cbd7eee)](https://www.codacy.com/app/juliushaertl/deck?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=nextcloud/deck&amp;utm_campaign=Badge_Grade) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/nextcloud/deck/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/nextcloud/deck/?branch=master) [![Dependency Status](https://www.versioneye.com/user/projects/5af6c4090fb24f0e3a423c40/badge.svg)](https://www.versioneye.com/user/projects/5af6c4090fb24f0e3a423c40) [![#nextcloud-deck](https://img.shields.io/badge/IRC-%23nextcloud--deck%20on%20freenode-blue.svg)](https://webchat.freenode.net/?channels=nextcloud-deck)
Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.
@@ -9,18 +9,14 @@ Deck is a kanban style organization tool aimed at personal planning and project
- :page_facing_up: Write down additional notes in markdown
- :bookmark: Assign labels for even better organization
- :busts_in_silhouette: Share with your team, friends or family
- :family: Integrates with the [Circles](https://github.com/nextcloud/circles) app!
- :paperclip: Attach files and embed them in your markdown description
- :speech_balloon: Discuss with your team using comments
- :zap: Keep track of changes in the activity stream
- :rocket: Get your project organized
![Deck - Manage cards on your board](https://download.bitgrid.net/nextcloud/deck/screenshots/Deck.png)
![Deck - Manage cards on your board](https://download.bitgrid.net/nextcloud/deck/screenshots/Deck_Board.png)
## Installation/Update
This app is supposed to work on the two latest Nextcloud versions.
This app is supposed to work on Nextcloud version 12 or later.
### Install latest release
@@ -34,10 +30,10 @@ If you want to run the latest development version from git source, you need to c
git clone https://github.com/nextcloud/deck.git
cd deck
make install-deps
make build
make
```
Please make sure you have installed the following dependencies: `make, which, tar, npm, curl, composer`
Please make sure you have installed the following dependencies: `make, which, tar, npm, curl`
### Install the nightly builds
@@ -58,15 +54,13 @@ You can use the provided Makefile to run all tests by using:
make test
### Documentation
The documentation for our REST API can be found at https://deck.readthedocs.io/en/latest/API/
## Contribution Guidelines
Please read the [Code of Conduct](https://nextcloud.com/community/code-of-conduct/). This document offers some guidance to ensure Nextcloud participants can cooperate effectively in a positive and inspiring atmosphere, and to explain how together we can strengthen and support each other.
For more information please review the [guidelines for contributing](https://github.com/nextcloud/server/blob/master/.github/CONTRIBUTING.md) to this repository.
For more information please review the [guidelines for contributing](https://github.com/nextcloud/server/blob/master/CONTRIBUTING.md) to this repository.
### Apply a license

View File

@@ -21,15 +21,6 @@
*
*/
if ((@include_once __DIR__ . '/../vendor/autoload.php')===false) {
throw new Exception('Cannot include autoload. Did you run install dependencies using composer?');
}
$app = new \OCA\Deck\AppInfo\Application();
$app->registerNavigationEntry();
$app->registerNotifications();
$app->registerCommentsEntity();
$app->registerFullTextSearch();
/** Load activity style global so it is availabile in the activity app as well */
\OC_Util::addStyle('deck', 'activity');
$app->registerNotifications();

View File

@@ -5,20 +5,20 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
namespace OCA\Deck\AppInfo;
@@ -28,4 +28,4 @@ use OCP\AppFramework\App;
/**
* Additional autoloader registration, e.g. registering composer autoloaders
*/
require_once __DIR__ . '/../vendor/autoload.php';
// require_once __DIR__ . '/../vendor/autoload.php';

View File

@@ -46,13 +46,6 @@
<notnull>false</notnull>
<unsigned>true</unsigned>
</field>
<field>
<name>last_modified</name>
<type>integer</type>
<default></default>
<notnull>false</notnull>
<unsigned>true</unsigned>
</field>
</declaration>
</table>
<table>
@@ -84,21 +77,6 @@
<length>8</length>
<notnull>false</notnull>
</field>
<field>
<name>deleted_at</name>
<type>integer</type>
<default>0</default>
<length>8</length>
<notnull>false</notnull>
<unsigned>true</unsigned>
</field>
<field>
<name>last_modified</name>
<type>integer</type>
<default></default>
<notnull>false</notnull>
<unsigned>true</unsigned>
</field>
<index>
<name>deck_stacks_board_id_index</name>
<field>
@@ -135,11 +113,6 @@
<type>clob</type>
<notnull>false</notnull>
</field>
<field>
<name>description_prev</name>
<type>clob</type>
<notnull>false</notnull>
</field>
<field>
<name>stack_id</name>
<type>integer</type>
@@ -160,12 +133,6 @@
<notnull>false</notnull>
<unsigned>true</unsigned>
</field>
<field>
<name>last_editor</name>
<type>text</type>
<notnull>false</notnull>
<length>64</length>
</field>
<field>
<name>created_at</name>
<type>integer</type>
@@ -200,14 +167,6 @@
<type>boolean</type>
<default>false</default>
</field>
<field>
<name>deleted_at</name>
<type>integer</type>
<default>0</default>
<length>8</length>
<notnull>false</notnull>
<unsigned>true</unsigned>
</field>
<index>
<name>deck_cards_stack_id_index</name>
<field>
@@ -317,13 +276,6 @@
<notnull>true</notnull>
<length>8</length>
</field>
<field>
<name>last_modified</name>
<type>integer</type>
<default></default>
<notnull>false</notnull>
<unsigned>true</unsigned>
</field>
<index>
<name>deck_labels_board_id_index</name>
<field>

View File

@@ -11,37 +11,26 @@
- 📄 Write down additional notes in markdown
- 🔖 Assign labels for even better organization
- 👥 Share with your team, friends or family
- 📎 Attach files and embed them in your markdown description
- 💬 Discuss with your team using comments
- ⚡ Keep track of changes in the activity stream
- 🚀 Get your project organized
</description>
<version>0.8.2</version>
<version>0.4.1</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Deck</namespace>
<types>
<dav />
</types>
<category>organization</category>
<category>office</category>
<website>https://github.com/nextcloud/deck</website>
<bugs>https://github.com/nextcloud/deck/issues</bugs>
<repository type="git">https://github.com/nextcloud/deck.git</repository>
<screenshot>https://download.bitgrid.net/nextcloud/deck/screenshots/0.5/deck-notifications.png</screenshot>
<screenshot>https://download.bitgrid.net/nextcloud/deck/screenshots/0.5/deck-comment2.png</screenshot>
<screenshot>https://download.bitgrid.net/nextcloud/deck/screenshots/Deck_Board.png</screenshot>
<screenshot>https://download.bitgrid.net/nextcloud/deck/screenshots/Deck_Details.png</screenshot>
<dependencies>
<php min-version="5.6"/>
<database min-version="9.4">pgsql</database>
<database>sqlite</database>
<database min-version="5.5">mysql</database>
<nextcloud min-version="17" max-version="19" />
<nextcloud min-version="12" max-version="14" />
</dependencies>
<background-jobs>
<job>OCA\Deck\Cron\DeleteCron</job>
<job>OCA\Deck\Cron\ScheduledNotifications</job>
<job>OCA\Deck\Cron\CardDescriptionActivity</job>
</background-jobs>
<repair-steps>
<post-migration>
@@ -51,22 +40,4 @@
<commands>
<command>OCA\Deck\Command\UserExport</command>
</commands>
<activity>
<settings>
<setting>OCA\Deck\Activity\Setting</setting>
<setting>OCA\Deck\Activity\SettingComment</setting>
<setting>OCA\Deck\Activity\DescriptionSetting</setting>
</settings>
<filters>
<filter>OCA\Deck\Activity\Filter</filter>
</filters>
<providers>
<provider>OCA\Deck\Activity\DeckProvider</provider>
</providers>
</activity>
<fulltextsearch>
<provider min-version="16">OCA\Deck\Provider\DeckProvider</provider>
</fulltextsearch>
</info>

View File

@@ -3,32 +3,28 @@
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
* @author Ryan Fletcher <ryan.fletcher@codepassion.ca>
*
* @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/>.
*
*
*/
return [
'routes' => [
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
['name' => 'Config#get', 'url' => '/config', 'verb' => 'GET'],
['name' => 'Config#setValue', 'url' => '/config/{key}', 'verb' => 'POST'],
// boards
['name' => 'board#index', 'url' => '/boards', 'verb' => 'GET'],
['name' => 'board#create', 'url' => '/boards', 'verb' => 'POST'],
@@ -47,7 +43,6 @@ return [
['name' => 'stack#update', 'url' => '/stacks/{stackId}', 'verb' => 'PUT'],
['name' => 'stack#reorder', 'url' => '/stacks/{stackId}/reorder', 'verb' => 'PUT'],
['name' => 'stack#delete', 'url' => '/stacks/{stackId}', 'verb' => 'DELETE'],
['name' => 'stack#deleted', 'url' => '/{boardId}/stacks/deleted', 'verb' => 'GET'],
['name' => 'stack#archived', 'url' => '/stacks/{boardId}/archived', 'verb' => 'GET'],
// cards
@@ -55,7 +50,6 @@ return [
['name' => 'card#create', 'url' => '/cards', 'verb' => 'POST'],
['name' => 'card#update', 'url' => '/cards/{cardId}', 'verb' => 'PUT'],
['name' => 'card#delete', 'url' => '/cards/{cardId}', 'verb' => 'DELETE'],
['name' => 'card#deleted', 'url' => '/{boardId}/cards/deleted', 'verb' => 'GET'],
['name' => 'card#rename', 'url' => '/cards/{cardId}/rename', 'verb' => 'PUT'],
['name' => 'card#reorder', 'url' => '/cards/{cardId}/reorder', 'verb' => 'PUT'],
['name' => 'card#archive', 'url' => '/cards/{cardId}/archive', 'verb' => 'PUT'],
@@ -80,47 +74,5 @@ return [
['name' => 'label#update', 'url' => '/labels/{labelId}', 'verb' => 'PUT'],
['name' => 'label#delete', 'url' => '/labels/{labelId}', 'verb' => 'DELETE'],
// api
['name' => 'board_api#index', 'url' => '/api/v1.0/boards', 'verb' => 'GET'],
['name' => 'board_api#get', 'url' => '/api/v1.0/boards/{boardId}', 'verb' => 'GET'],
['name' => 'board_api#create', 'url' => '/api/v1.0/boards', 'verb' => 'POST'],
['name' => 'board_api#delete', 'url' => '/api/v1.0/boards/{boardId}', 'verb' => 'DELETE'],
['name' => 'board_api#update', 'url' => '/api/v1.0/boards/{boardId}', 'verb' => 'PUT'],
['name' => 'board_api#undo_delete', 'url' => '/api/v1.0/boards/{boardId}/undo_delete', 'verb' => 'POST'],
['name' => 'board_api#addAcl', 'url' => '/api/v1.0/boards/{boardId}/acl', 'verb' => 'POST'],
['name' => 'board_api#deleteAcl', 'url' => '/api/v1.0/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
['name' => 'board_api#updateAcl', 'url' => '/api/v1.0/boards/{boardId}/acl/{aclId}', 'verb' => 'PUT'],
['name' => 'stack_api#index', 'url' => '/api/v1.0/boards/{boardId}/stacks', 'verb' => 'GET'],
['name' => 'stack_api#getArchived', 'url' => '/api/v1.0/boards/{boardId}/stacks/archived', 'verb' => 'GET'],
['name' => 'stack_api#get', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}', 'verb' => 'GET'],
['name' => 'stack_api#create', 'url' => '/api/v1.0/boards/{boardId}/stacks', 'verb' => 'POST'],
['name' => 'stack_api#update', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}', 'verb' => 'PUT'],
['name' => 'stack_api#delete', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}', 'verb' => 'DELETE'],
['name' => 'card_api#get', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'GET'],
['name' => 'card_api#create', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards', 'verb' => 'POST'],
['name' => 'card_api#update', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'PUT'],
['name' => 'card_api#assignLabel', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignLabel', 'verb' => 'PUT'],
['name' => 'card_api#removeLabel', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/removeLabel', 'verb' => 'PUT'],
['name' => 'card_api#assignUser', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignUser', 'verb' => 'PUT'],
['name' => 'card_api#unassignUser', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/unassignUser', 'verb' => 'PUT'],
['name' => 'card_api#reorder', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/reorder', 'verb' => 'PUT'],
['name' => 'card_api#delete', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'DELETE'],
['name' => 'label_api#get', 'url' => '/api/v1.0/boards/{boardId}/labels/{labelId}', 'verb' => 'GET'],
['name' => 'label_api#create', 'url' => '/api/v1.0/boards/{boardId}/labels', 'verb' => 'POST'],
['name' => 'label_api#update', 'url' => '/api/v1.0/boards/{boardId}/labels/{labelId}', 'verb' => 'PUT'],
['name' => 'label_api#delete', 'url' => '/api/v1.0/boards/{boardId}/labels/{labelId}', 'verb' => 'DELETE'],
['name' => 'attachment_api#getAll', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments', 'verb' => 'GET'],
['name' => 'attachment_api#display', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'GET'],
['name' => 'attachment_api#create', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments', 'verb' => 'POST'],
['name' => 'attachment_api#update', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'PUT'],
['name' => 'attachment_api#delete', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'DELETE'],
['name' => 'attachment_api#restore', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}/restore', 'verb' => 'PUT'],
['name' => 'board_api#preflighted_cors', 'url' => '/api/v1.0/{path}','verb' => 'OPTIONS', 'requirements' => ['path' => '.+']],
]
];

View File

@@ -8,13 +8,9 @@
"email": "jus@bitgrid.net"
}
],
"require": {
"cogpowered/finediff": "0.3.*"
},
"require": {},
"require-dev": {
"roave/security-advisories": "dev-master",
"christophwurst/nextcloud": "^17",
"jakub-onderka/php-parallel-lint": "^1.0.0",
"phpunit/phpunit": "^8"
"christophwurst/nextcloud": "^13.0",
"jakub-onderka/php-parallel-lint": "^1.0.0"
}
}

View File

@@ -1,28 +0,0 @@
.activitymessage .visualdiff ins {
background-color: rgba(70, 186, 97, 0.2);
text-decoration: none;
}
.activitymessage .visualdiff del {
background-color: rgba(233, 50, 45, 0.2);
text-decoration: none;
}
.activitymessage .visualdiff {
overflow: scroll;
max-height: 200px;
}
.activityTabView .avatardiv-container {
display: inline-block;
bottom: -3px;
margin-left: 3px;
}
.activityTabView .avatar-name-wrapper {
font-weight: bold;
}
.activityTabView .activitysubject a {
font-weight: bold;
}

View File

@@ -1,43 +0,0 @@
/*
* @copyright Copyright (c) 2018 Michael Weimann <mail@michael-weimann.eu>
*
* @author 2018 Michael Weimann <mail@michael-weimann.eu>
*
* @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/>.
*
*/
.compact-item.ng-enter,
.compact-item.ng-leave {
overflow: hidden;
transition: all 250ms linear;
}
.compact-item.ng-enter {
max-height: 0;
&.ng-enter-active {
max-height: 50px;
}
}
.compact-item.ng-leave {
max-height: 50px;
&.ng-leave-active {
max-height: 0;
}
}

View File

@@ -1,77 +0,0 @@
/**
* based upon apps/comments/js/vendor/At.js/dist/css/jquery.atwho.css,
* only changed colors and font-weight
*/
.atwho-view {
position:absolute;
top: 0;
left: 0;
display: none;
margin-top: 18px;
background: var(--color-main-background);
color: var(--color-main-text);
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
box-shadow: 0 0 5px var(--color-box-shadow);
min-width: 120px;
z-index: 11110 !important;
}
.atwho-view .atwho-header {
padding: 5px;
margin: 5px;
cursor: pointer;
border-bottom: solid 1px var(--color-border);
color: var(--color-main-text);
font-size: 11px;
font-weight: bold;
}
.atwho-view .atwho-header .small {
color: var(--color-main-text);
float: right;
padding-top: 2px;
margin-right: -5px;
font-size: 12px;
font-weight: normal;
}
.atwho-view .atwho-header:hover {
cursor: default;
}
.atwho-view .cur {
background: var(--color-primary);
color: var(--color-primary-text);
}
.atwho-view .cur small {
color: var(--color-primary-text);
}
.atwho-view strong {
color: var(--color-main-text);
font-weight: normal;
}
.atwho-view .cur strong {
color: var(--color-primary-text);
font-weight: normal;
}
.atwho-view ul {
/* width: 100px; */
list-style:none;
padding:0;
margin:auto;
max-height: 200px;
overflow-y: auto;
}
.atwho-view ul li {
display: block;
padding: 5px 10px;
border-bottom: 1px solid var(--color-border);
cursor: pointer;
}
.atwho-view small {
font-size: smaller;
color: var(--color-main-text);
font-weight: normal;
}

View File

@@ -1,10 +0,0 @@
.icon-deck {
background-image: url('../img/deck-dark.svg');
}
.resource-type-deck img {
opacity: 0.4 !important;
}
.resource-type-deck:hover img {
opacity: 0.7 !important;
}

View File

@@ -1,261 +0,0 @@
/*
* Copyright (c) 2016
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
#commentsTabView .emptycontent {
margin-top: 0;
}
#commentsTabView .newCommentForm {
margin-left: 36px;
position: relative;
}
#commentsTabView .newCommentForm .message {
width: 100%;
padding: 10px;
min-height: 44px;
margin: 0;
/* Prevent the text from overlapping with the submit button. */
padding-right: 30px;
}
#commentsTabView .newCommentForm {
.submit,
.submitLoading {
width: 44px;
height: 44px;
margin: 0;
padding: 13px;
background-color: transparent;
border: none;
opacity: .3;
position: absolute;
bottom: 0;
right: 0;
}
}
#commentsTabView .deleteLoading {
padding: 14px;
vertical-align: middle;
}
#commentsTabView .newCommentForm .submit:not(:disabled):hover,
#commentsTabView .newCommentForm .submit:not(:disabled):focus {
opacity: 1;
}
#commentsTabView .newCommentForm div.message {
resize: none;
}
#commentsTabView .newCommentForm div.message:empty:before {
content: attr(data-placeholder);
color: grey;
}
#commentsTabView .comment {
position: relative;
/** padding bottom is little more so that the top and bottom gap look uniform **/
padding: 10px 0 15px;
}
#commentsTabView .comments .comment {
border-top: 1px solid var(--color-border);
}
#commentsTabView .comment .avatar,
.atwho-view-ul * .avatar{
width: 32px;
height: 32px;
line-height: 32px;
margin-right: 5px;
}
#commentsTabView .comment .message .avatar,
.atwho-view-ul * .avatar
{
display: inline-block;
}
#activityTabView li.comment.collapsed .activitymessage,
#commentsTabView .comment.collapsed .message {
white-space: pre-wrap;
}
#activityTabView li.comment.collapsed .activitymessage,
#commentsTabView .comment.collapsed .message {
max-height: 70px;
overflow: hidden;
}
#activityTabView li.comment .message-overlay,
#commentsTabView .comment .message-overlay {
display: none;
}
#activityTabView li.comment.collapsed .message-overlay,
#commentsTabView .comment.collapsed .message-overlay {
display: block;
position: absolute;
z-index: 2;
height: 50px;
pointer-events: none;
left: 0;
right: 0;
bottom: 0;
background: -moz-linear-gradient(rgba(var(--color-main-background), 0), var(--color-main-background));
background: -webkit-linear-gradient(rgba(var(--color-main-background), 0), var(--color-main-background));
background: -o-linear-gradient(rgba(var(--color-main-background), 0), var(--color-main-background));
background: -ms-linear-gradient(rgba(var(--color-main-background), 0), var(--color-main-background));
background: linear-gradient(rgba(var(--color-main-background), 0), var(--color-main-background));
background-repeat: no-repeat;
}
#commentsTabView .hidden {
display: none !important;
}
/** set min-height as 44px to ensure that it fits the button sizes. **/
#commentsTabView .comment .authorRow {
min-height: 44px;
}
#commentsTabView .comment .authorRow .tooltip {
/** because of the padding on the element, the tooltip appear too far up,
adding this brings them closer to the element**/
margin-top: 5px;
}
.atwho-view-ul * .avatar-name-wrapper,
#commentsTabView .comment .authorRow {
position: relative;
display: inline-flex;
align-items: center;
width: 100%;
}
#commentsTabView .comment:not(.newCommentRow) .message .avatar-name-wrapper:not(.currentUser),
#commentsTabView .comment:not(.newCommentRow) .message .avatar-name-wrapper:not(.currentUser) .avatar,
#commentsTabView .comment:not(.newCommentRow) .message .avatar-name-wrapper:not(.currentUser) .avatar img,
#commentsTabView .comment .authorRow .avatar:not(.currentUser),
#commentsTabView .comment .authorRow .author:not(.currentUser) {
cursor: pointer;
}
.atwho-view-ul .avatar-name-wrapper,
.atwho-view-ul .avatar-name-wrapper .avatar,
.atwho-view-ul .avatar-name-wrapper .avatar img {
cursor: pointer;
}
#commentsTabView .comments li .message .atwho-inserted,
#commentsTabView .newCommentForm .atwho-inserted {
.avatar-name-wrapper {
/* Make the wrapper the positioning context of its child contacts
* menu. */
position: relative;
display: inline;
vertical-align: top;
background-color: var(--color-background-dark);
border-radius: 50vh;
padding: 1px 7px 1px 1px;
/* Ensure that the avatar and the user name will be kept together. */
white-space: nowrap;
.avatar {
img {
vertical-align: top;
}
height: 16px;
width: 16px;
vertical-align: middle;
padding: 1px;
margin-top: -3px;
margin-left: 0;
margin-right: 2px;
}
strong {
/* Ensure that the user name is shown in bold, as different browsers
* use different font weights for strong elements. */
font-weight: bold;
}
}
.avatar-name-wrapper.currentUser {
background-color: var(--color-primary);
color: var(--color-primary-text);
}
}
.atwho-view-ul * .avatar-name-wrapper {
white-space: nowrap;
}
#commentsTabView .comment .author,
#commentsTabView .comment .date {
opacity: .5;
}
#commentsTabView .comment .author {
max-width: 210px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
#commentsTabView .comment .date {
margin-left: auto;
/** this is to fix the tooltip being too close due to the margin-top applied
to bring the tooltip closer for the action icons **/
padding: 10px 0px;
}
#commentsTabView .comments > li:not(.newCommentRow) .message {
padding-left: 40px;
word-wrap: break-word;
overflow-wrap: break-word;
}
#commentsTabView .comment .action {
opacity: 0.3;
padding: 14px;
display: block;
}
#commentsTabView .comment .action:hover,
#commentsTabView .comment .action:focus {
opacity: 1;
}
#commentsTabView .newCommentRow .action-container {
margin-left: auto;
}
#commentsTabView .comment.disabled .message {
opacity: 0.3;
}
#commentsTabView .comment.disabled .action {
display: none;
}
#commentsTabView .message.error {
color: #e9322d;
border-color: #e9322d;
box-shadow: 0 0 6px #f8b9b7;
}
.app-files .action-comment {
padding: 16px 14px;
}
#commentsTabView .comment .message .contactsmenu-popover {
left: -6px;
top: 24px;
}

38
css/comp-13.scss Normal file
View File

@@ -0,0 +1,38 @@
#content-wrapper #content {
height: 100%;
}
#app-content {
flex-grow: 1;
height: 100%;
&.details-visible {
margin-right: 500px;
}
}
#app-sidebar {
right: -500px;
max-width: 100%;
width: 500px;
display:flex;
flex-direction: column;
&.details-visible {
right: 0;
}
}
#content[class*='app-'] * {
box-sizing: border-box;
}
body:not(.snapjs-left) {
.app-navigation-hide {
#app-content {
margin-left: 0 !important;
}
#app-navigation {
display: none;
}
}
}

View File

@@ -1,43 +0,0 @@
/*
* @copyright Copyright (c) 2018 Michael Weimann <mail@michael-weimann.eu>
*
* @author 2018 Michael Weimann <mail@michael-weimann.eu>
*
* @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/>.
*
*/
.compact-mode {
.card {
margin: $compact-board-item-margin;
&:last-child {
margin: $compact-board-last-item-margin;
}
}
.stack {
.as-sortable-placeholder {
margin: $compact-board-item-margin;
min-height: 43px;
height: 43px;
&:last-child {
margin: $compact-board-last-item-margin;
}
}
}
}

View File

@@ -34,47 +34,9 @@
}
.icon-home {
background-image: var(--icon-home-000, url('../../../core/img/places/home.svg'));
}
.icon-description {
background-image: var(--icon-text-000, url('../img/description.svg'));
background-image: url('../../../core/img/places/home.svg');
}
.icon-badge {
background-image: url('../img/calendar-dark.svg');
}
.icon-toggle-compact-collapsed {
background-image: url('../img/toggle-view-expand.svg');
}
.icon-toggle-compact-expanded {
background-image: url('../img/toggle-view-collapse.svg');
}
@if mixin-exists('icon-black-white') {
@include icon-black-white('deck', 'deck', 1);
@include icon-black-white('archive', 'deck', 1);
@include icon-black-white('circles', 'deck', 1);
.icon-toggle-compact-collapsed {
@include icon-color('toggle-view-expand', 'deck', $color-black);
}
.icon-toggle-compact-expanded {
@include icon-color('toggle-view-collapse', 'deck', $color-black);
}
.icon-activity {
@include icon-color('activity-dark', 'activity', $color-black);
}
}
.avatardiv.circles {
background: var(--color-primary);
}
.icon-circles {
opacity: 1;
background-size: 20px;
background-position: center center;
background-image: url('../../../core/img/places/calendar-dark.svg');
}

View File

@@ -1,90 +0,0 @@
@media print {
/* hide stuff */
#body-user {
#header,
div#app-navigation,
div.board-header-controls,
#app-navigation-toggle,
#app-navigation-toggle-custom,
div#controls.ng-scope div.crumb:not(.title),
div#controls.ng-scope div.crumb a.bullet,
a.ng-binding + a,
div.card.create,
button.card-options {
display: none !important;
}
#content {
margin: 0;
padding: 0;
}
#app-content {
margin: 0 !important;
}
}
div#app-navigation-toggle.icon-menu {
display:block;
width:0px;
height:0px;
background:none;
}
/* title */
div#controls.ng-scope {padding-left:20px;}
div#controls.ng-scope div.crumb.title {
display:inline;
font-size: 2em;
line-height:2.5em;
background:none;
}
div#controls.ng-scope div.crumb.title a.ng-binding {
color:#000;
opacity:1;
}
/*Due, assigned-users and description*/
div.card-controls {
flex-direction:row;
flex-wrap:wrap;
}
div.card-controls i.icon.icon-filetype-text {background:none;}
div.card-controls i.icon.icon-filetype-text:after {
content: attr(title);
display:block;
width:289px;
height:1.5em;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
span.due { }
div.card-assigned-users {
margin-right:10px;
}
ul.labels li.ng-scope span.ng-binding {
color:#000;
display:inline;
padding-left:5px;
}
/* Layout */
@page {
size: A4 landscape;
margin: 2cm;
}
div#innerBoard {
display:flex;
flex-wrap: wrap;
}
div.stack.ng-scope.as-sortable-item {border-right: 1px solid #000;}
div#innerBoard.ng-pristine.ng-untouched.ng-valid.ng-scope.ng-not-empty div.stack.ng-scope.as-sortable-item:nth-child(6n) {
page-break-after: always;
}
}

View File

@@ -5,7 +5,6 @@
* @author Artem Anufrij <artem.anufrij@live.de>
* @author Marin Treselj <marin@pixelipo.com>
* @author Oskar Kurz <oskar.kurz@gmail.com>
* @author Ryan Fletcher <ryan.fletcher@codepassion.ca>
*
* @license GNU AGPL version 3 or any later version
*
@@ -24,30 +23,17 @@
*
*/
// colors
$color-warning-light: nc-lighten($color-warning, 15%);
$color-lightgrey: nc-darken($color-main-background, 4%);
$color-grey: nc-darken($color-main-background, 7%);
$color-darkgrey: nc-darken($color-main-background, 32%);
// margins/paddings
$board-item-margin: 10px 10px 20px 10px;
$board-last-item-margin: 10px;
$compact-board-item-margin: 5px 10px 10px 10px;
$compact-board-last-item-margin: 5px 10px 10px;
@import 'comp-appnav';
@import 'icons';
@import 'animations';
@import 'compact-mode';
@import 'autocomplete';
@import 'comments';
@import 'comp-appnav.scss';
@import 'icons.scss';
/**
* General styles
*/
button,
.button,
.app-deck .icon {
@@ -149,22 +135,6 @@ input.input-inline {
}
#app-settings-content {
overflow: initial;
.ui-select-match-item {
border: 1px solid var(--color-background-darker) !important;
.select-label {
color: var(--color-main-text);
}
}
p.hint {
margin-top: 10px;
color: var(--color-text-light);
}
}
/**
* Board view
*/
@@ -173,7 +143,7 @@ input.input-inline {
z-index: 999;
width: 100%;
height: 100%;
background-color: var(--color-main-background, $color-main-background);
background-color: $color-main-background;
}
#board {
@@ -202,10 +172,10 @@ input.input-inline {
&.card-selected {
.card {
box-shadow: 0px 0px 7px 0px var(--color-background-darker, $color-grey);
box-shadow: 0px 0px 7px 0px $color-grey;
&.current {
box-shadow: 0px 0px 7px 0px var(--color-text-lighter, $color-darkgrey);
box-shadow: 0px 0px 7px 0px $color-darkgrey;
}
}
}
@@ -230,16 +200,10 @@ input.input-inline {
padding: 10px;
> .as-sortable-placeholder {
display: flex !important;
width: 320px;
min-width: 320px;
display: inline-block !important;
margin-top: 0;
margin-left: 0;
}
> .as-sortable-drag {
background-color: var(--color-main-background $color-main-background);
}
}
#controls {
@@ -277,17 +241,9 @@ input.input-inline {
}
button {
width: 36px;
height: 36px;
padding: 9px;
height: inherit;
}
#stack-add form {
button {
height: auto;
width: 32px;
}
}
input[type='text'] {
padding: 6px;
border: 0 none transparent;
@@ -352,7 +308,7 @@ input.input-inline {
}
#stack-add {
background-color: var(--color-background-dark, $color-lightgrey);
background-color: $color-lightgrey;
border-radius: 3px;
margin: 3px;
display: flex;
@@ -397,8 +353,9 @@ input.input-inline {
width: 100%;
margin: 0;
font-size: 12pt;
font-weight: 700;
border: 0;
background-color: var(--color-main-background, $color-main-background);
background-color: $color-main-background;
min-height: initial;
}
@@ -421,20 +378,15 @@ input.input-inline {
}
.as-sortable-placeholder {
margin: $board-item-margin;
margin: 10px 10px 20px 10px;
border: 1px dashed $color-darkgrey;
min-height: 96px;
transition: margin 250ms linear;
&:last-child {
margin: $board-last-item-margin;
margin: 10px;
}
}
&.as-sortable-item {
height: 100%;
display: flex;
}
> ul {
display: flex;
@@ -443,17 +395,17 @@ input.input-inline {
}
.card {
background-color: var(--color-main-background, $color-main-background);
margin: $board-item-margin;
background-color: $color-main-background;
margin: 10px 10px 20px 10px;
white-space: normal;
position: relative;
box-shadow: 0 0 3px 1px var(--color-background-darker, $color-darkgrey);
box-shadow: 0 0 3px $color-darkgrey;
border-radius: 3px;
transition: margin 250ms linear;
&:last-child {
margin: $board-last-item-margin;
margin: 10px;
}
&.archived .card-upper {
@@ -477,7 +429,7 @@ input.input-inline {
}
.card-controls {
background: var(--color-background-dark, $color-lightgrey);
background: $color-lightgrey;
display: flex;
position: relative;
padding-left: 10px;
@@ -490,10 +442,9 @@ input.input-inline {
opacity: 1;
}
.icon-description {
.icon-filetype-text {
margin: 10px;
margin-left: 0px;
opacity: 0.5;
}
.due {
@@ -511,7 +462,7 @@ input.input-inline {
&.overdue {
background-color: $color-error;
color: var(--color-primary-text, $color-primary-text);
color: $color-primary-text;
.icon-badge {
background-image: url('../img/calendar-white.svg');
@@ -529,7 +480,7 @@ input.input-inline {
}
}
.card-tasks, .card-files, .card-comments {
.card-tasks, .card-files {
border-radius: 3px;
margin: 4px 4px 4px 0px;
padding: 0 2px;
@@ -558,39 +509,37 @@ input.input-inline {
font-weight: normal;
font-size: 10pt;
padding: 0;
margin: 0 5px;
margin: 5px;
overflow: hidden;
text-overflow: ellipsis;
}
span {
padding: 6px 0;
padding-top: 7px;
display: block;
}
input {
width: 100%;
margin: 0;
}
&.has-labels h4 {
margin-top: 15px;
}
.labels {
position: relative;
margin-left: 5px;
position: absolute;
top: -5px;
left: 10px;
li {
padding: 0 4px;
margin: 0 2px 2px 0;
padding: 0;
width: 15px;
height: 20px;
border-radius: 3px;
font-size: 75%;
font-size: 80%;
border: none transparent;
float: left;
span {
display: inline-block;
font-weight: bold;
display: none;
}
&:hover span {
position: absolute;
padding: 3px;
background-color: inherit;
line-height: normal;
}
}
}
@@ -655,7 +604,7 @@ input.input-inline {
min-height: 16px;
}
.popovermenu:not(.action-item__menu) {
.popovermenu {
z-index: 999;
opacity: 1;
display: block;
@@ -690,17 +639,12 @@ input.input-inline {
*/
#sidebar-header {
position: sticky;
top: 0;
background-color: var(--color-main-background, $color-main-background);
z-index: 200;
h3 {
font-size: 14pt;
padding: 15px 15px 3px;
padding: 9px 10px;
margin: 0;
overflow: hidden;
background-color: $color-lightgrey;
input {
min-height: 0px;
@@ -708,12 +652,6 @@ input.input-inline {
}
}
#card-dates {
font-size: 80%;
opacity: 0.5;
padding-left: 15px;
}
.icon-close {
position: absolute;
top: 0px;
@@ -733,7 +671,7 @@ input.input-inline {
position: absolute;
width: 100%;
height: 100%;
background-color: var(--color-main-background, $color-main-background);
background-color: #fff;
z-index: 100;
opacity: 0.9;
text-align: center;
@@ -754,7 +692,7 @@ input.input-inline {
height: 100%;
display: flex;
flex-direction: column;
padding: 0 15px;
padding: 15px;
.duedate {
display: flex;
@@ -793,7 +731,6 @@ input.input-inline {
flex: 1;
}
}
.section-header-tabbed {
margin-top: 10px;
margin-bottom: 5px;
@@ -803,15 +740,8 @@ input.input-inline {
margin: 0;
flex-grow: 1;
}
}
.tabDetails {
display: flex;
height: 40px;
align-items: center;
justify-content: center;
input[type=button] {
width: 32px;
.tabDetails {
display: flex;
}
}
@@ -821,14 +751,15 @@ input.input-inline {
padding: 0 10px;
font-size: 8pt;
display: none;
align-self: flex-end;
text-align: center;
&.saved {
background-color: $color-success;
color: $color-primary-text;
}
&.unsaved {
background-color: var(--color-background-dark, $color-lightgrey);
color: var(--color-text-light, $color-darkgrey);
background-color: $color-lightgrey;
color: $color-darkgrey;
}
}
@@ -838,6 +769,12 @@ input.input-inline {
display: inline;
}
#card-dates {
font-size: 80%;
opacity: 0.5;
text-align: right;
}
.card-details-assign-users {
.select2 .ui-select-choices-row-inner {
@@ -871,7 +808,6 @@ input.input-inline {
background-color: rgba($color-darkgrey, 0.5);
left: 0;
top: 0;
z-index: 300;
}
.attachment-list {
&.selector {
@@ -884,10 +820,10 @@ input.input-inline {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: var(--color-main-background, $color-main-background);
background-color: $color-main-background;
z-index: 2;
border-radius: 3px;
box-shadow: 0 0 3px var(--color-background-dark, $color-darkgrey);
box-shadow: 0 0 3px $color-darkgrey;
overflow: scroll;
}
h3.attachment-selector {
@@ -981,75 +917,12 @@ input.input-inline {
}
.container {
background-color: var(--color-main-background, $color-main-background);
background-color: $color-main-background;
}
}
}
}
.activity-icon {
opacity: 1 !important;
.avatardiv-container {
top: -4px;
left: -7px;
margin-right: 5px;
img {
max-width: 24px;
max-height: 24px;
opacity: 1;
}
}
& > img {
opacity: 0.7;
}
}
.activitysubject.commentAuthor {
margin-left: 26px;
margin-right: 0;
margin-top: 10px;
}
.activityTabView {
.activity {
margin-bottom: 20px;
}
.activitytime {
margin: 0 !important;
}
}
.activitysubject .app-popover-menu-utils {
display: inline-block;
position: relative;
a {
font-weight: normal;
}
button {
opacity: .5;
padding: 7px;
margin-left: 10px;
}
}
#commentsTabView {
.newCommentRow .avatardiv-container {
left: -7px;
}
.comment {
position: relative;
padding: 0 0 15px;
.avatardiv {
width: 24px;
height: 24px;
line-height: 24px;
}
}
.newCommentForm {
margin-left: 26px;
}
}
.card-attachments {
.error {
padding-left: 38px;
@@ -1267,7 +1140,6 @@ input.input-inline {
display: inline-block;
overflow: hidden;
vertical-align: middle;
flex-grow: 1;
}
.icon-delete {
@@ -1293,7 +1165,7 @@ input.input-inline {
width: 32px;
height: 32px;
.icon-group, .icon {
.icon-group {
padding: 16px;
opacity: 0.5;
}
@@ -1307,22 +1179,6 @@ input.input-inline {
position: relative;
}
.board-detail__deleted-list__item {
display: flex;
flex-direction: row;
* {
flex-basis: 44px;
}
.title {
flex-grow: 2;
}
.live-relative-timestamp {
flex-grow: 1;
}
}
#board-detail-labels {
ul li {
input {
@@ -1368,23 +1224,12 @@ input.input-inline {
.tabHeaders {
clear: both;
overflow: initial;
overflow: hidden;
margin-bottom: 0;
.icon {
display: inline-block;
background-size: contain;
margin-right: 5px;
opacity: 0.5;
}
}
.tabsContainer {
margin-top: 15px;
height: 100%;
.tab {
height: 100%;
}
}
.ui-select-offscreen {
@@ -1395,7 +1240,7 @@ input.input-inline {
padding: 0;
float: left !important;
display: block;
border-radius: 3px !important;
border-radius: 0px 0px 5px 5px !important;
.select-label {
color: $color-primary-text;
@@ -1426,10 +1271,6 @@ input.input-inline {
}
.select2-search-field {
margin-right: -10px;
flex-grow: 1;
input {
width: 100% !important;
}
}
}
@@ -1467,8 +1308,6 @@ input.input-inline {
*/
#markdown {
width: 100% !important;
min-height: 40px;
cursor: text;
p {
margin-bottom: 15px;
@@ -1522,7 +1361,7 @@ input.input-inline {
}
pre {
background-color: var(--color-background-dark, $color-lightgrey);
background-color: $color-lightgrey;
padding: 3px;
overflow: auto;
@@ -1545,50 +1384,11 @@ input.input-inline {
display: inline-block;
min-height: 12px;
}
li input[type="checkbox"].checkbox + label::before {
margin-left: -15px;
}
input[type="checkbox"].checkbox + label::before {
position: relative;
z-index: 100;
margin-right: 10px;
margin-top: 0;
}
li input[type="checkbox"].checkbox:not(:checked) + label::before {
background-color: $color-main-background;
}
table {
margin-bottom: 10px;
border-collapse: collapse;
thead {
background-color: var(--color-background-dark, $color-lightgrey);
}
td, th {
border: 1px solid var(--color-background-darker, $color-darkgrey);
padding: 3px;
}
li input[type=checkbox] {
margin: 0px 10px 0px -20px;
}
}
.section-wrapper {
display: flex;
max-width: 100%;
margin-top: 10px;
}
.section-label {
background-position: 0px center;
width:28px;
flex-shrink: 0;
}
.section-details {
flex-grow: 1;
}
/**
* Mobile optimizations
*/
@@ -1651,9 +1451,4 @@ body:not(.snapjs-left) {
#app-navigation-toggle-custom {
display: none;
}
}
/**
* Print settings, better leave them at the eof
*/
@import 'print.scss';
}

View File

@@ -1,42 +0,0 @@
# Nextcloud APIs
## Available sharees
When sharing a board to a user, group or circle, the possible sharees can be obtained though the files_sharing API.
API endpoint: https://nextcloud.local/index.php/apps/files_sharing/api/v1/sharees
### Parameters
- format: **The response format**
- perPage: **Limit response number**
- itemType: **List of types. Currently supported are**
- 0 user
- 1 group
- 7 circle
## Comments
Comments are stored using the Nextcloud Comments API. You can use the WebDAV endpoint of Nextcloud to fetch, update and delete comments.
### List comments
PROPFIND`remote.php/dav/comments/deckCard/{cardId}`
### Create comment
POST `remote.php/dav/comments/deckCard/{cardId}`
### Update comment
PROPPATCH `remote.php/dav/comments/deckCard/{cardId}/{commentId}`
### Delete comment
DELETE `remote.php/dav/comments/deckCard/{cardId}/{commentId}`
## Activity
The Nextcloud activity app provides an API to fetch activities filtered for deck: [Activity app API documentation](https://github.com/nextcloud/activity/blob/master/docs/endpoint-v2.md)
The deck app offers a filter `deck` to only request activity events that are relevant.

View File

@@ -1,938 +0,0 @@
The REST API provides access for authenticated users to their data inside the Deck app. To get a better understand of Decks data models and their relations, please have a look at the [data structure](structure.md) documentation.
# Prequisited
- All requests require a `OCS-APIRequest` HTTP header to be set to `true` and a `Content-Type` of `application/json`.
- The API is located at https://nextcloud.local/index.php/apps/deck/api/v1.0
## Naming
- Board is the the project like grouping of tasks that can be shared to different users and groups
- Stack is the grouping of cards which is rendered in vertical columns in the UI
- Card is the representation of a single task
- Labels are defined on a board level and can be assigned to any number of cards
## Global responses
### 400 Bad request
In case the request is invalid, e.g. because a parameter is missing, a 400 error will be returned:
```json
{
"status": 400,
"message": "title must be provided"
}
```
### 403 Permission denied
In any case a user doesn't have access to a requested entity, a 403 error will be returned:
```json
{
"status": 403,
"message": "Permission denied"
}
```
## Headers
### If-Modified-Since
Some index endpoints support limiting the result set to entries that have been changed since the given time.
The supported date formats are:
* IMF-fixdate: `Sun, 03 Aug 2019 10:34:12 GMT`
* (obsolete) RFC 850: `Sunday, 03-Aug-19 10:34:12 GMT`
* (obsolete) ANSI C asctime(): `Sun Aug 3 10:34:12 2019`
It is highly recommended to only use the IMF-fixdate format.
Example curl request:
```bash
curl -u admin:admin -X GET \
'http://localhost:8000/index.php/apps/deck/api/v1.0/boards/2/stacks' \
-H "OCS-APIRequest: true" \
-H "If-Modified-Since: Mon, 05 Nov 2018 09:28:00 GMT"
```
# Endpoints
## Boards
### GET /boards - Get a list of boards
#### Headers
The board list endpoint supports setting an `If-Modified-Since` header to limit the results to entities that are changed after the provided time.
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------- |
| options | Bool | **Optional** Enhance boards with details about labels, stacks and users |
#### Response
##### 200 Success
Returns an array of board items
```json
[
{
"title": "Board title",
"owner": {
"primaryKey": "admin",
"uid": "admin",
"displayname": "Administrator"
},
"color": "ff0000",
"archived": false,
"labels": [],
"acl": [],
"permissions": {
"PERMISSION_READ": true,
"PERMISSION_EDIT": true,
"PERMISSION_MANAGE": true,
"PERMISSION_SHARE": true
},
"users": [],
"shared": 0,
"deletedAt": 0,
"id": 10
}
]
```
### POST /boards - Create a new board
#### Request body
| Parameter | Type | Description |
| --------- | ------ | ---------------------------------------------------- |
| title | String | The title of the new board |
| color | String | The hexadecimal color of the new board (e.g. FF0000) |
```json
{
"title": "Board title",
"color": "ff0000"
}
```
#### Response
##### 200 Success
```json
{
"title": "Board title",
"owner": {
"primaryKey": "admin",
"uid": "admin",
"displayname": "Administrator"
},
"color": "ff0000",
"archived": false,
"labels": [
{
"title": "Finished",
"color": "31CC7C",
"boardId": 10,
"cardId": null,
"id": 37
},
{
"title": "To review",
"color": "317CCC",
"boardId": 10,
"cardId": null,
"id": 38
},
{
"title": "Action needed",
"color": "FF7A66",
"boardId": 10,
"cardId": null,
"id": 39
},
{
"title": "Later",
"color": "F1DB50",
"boardId": 10,
"cardId": null,
"id": 40
}
],
"acl": [],
"permissions": {
"PERMISSION_READ": true,
"PERMISSION_EDIT": true,
"PERMISSION_MANAGE": true,
"PERMISSION_SHARE": true
},
"users": [],
"deletedAt": 0,
"id": 10
}
```
### GET /boards/{boardId} - Get board details
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------- |
| boardId | Integer | The id of the board to fetch |
#### Response
##### 200 Success
```json
{
"title": "Board title",
"owner": {
"primaryKey": "admin",
"uid": "admin",
"displayname": "Administrator"
},
"color": "ff0000",
"archived": false,
"labels": [
{
"title": "Finished",
"color": "31CC7C",
"boardId": "10",
"cardId": null,
"id": 37
},
{
"title": "To review",
"color": "317CCC",
"boardId": "10",
"cardId": null,
"id": 38
},
{
"title": "Action needed",
"color": "FF7A66",
"boardId": "10",
"cardId": null,
"id": 39
},
{
"title": "Later",
"color": "F1DB50",
"boardId": "10",
"cardId": null,
"id": 40
}
],
"acl": [],
"permissions": {
"PERMISSION_READ": true,
"PERMISSION_EDIT": true,
"PERMISSION_MANAGE": true,
"PERMISSION_SHARE": true
},
"users": [
{
"primaryKey": "admin",
"uid": "admin",
"displayname": "Administrator"
}
],
"deletedAt": 0,
"id": 10
}
```
### PUT /boards/{boardId} - Update board details
#### Request body
| Parameter | Type | Description |
| --------- | ------ | ---------------------------------------------------- |
| title | String | The title of the new board |
| color | String | The hexadecimal color of the new board (e.g. FF0000) |
| archived | Bool | The hexadecimal color of the new board (e.g. FF0000) |
```json
{
"title": "Board title",
"color": "ff0000",
"archived": false
}
```
#### Response
##### 200 Success
### DELETE /boards/{boardId} - Delete a board
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------- |
| boardId | Integer | The id of the board to fetch |
#### Response
##### 200 Success
### POST /boards/{boardId}/undo_delete - Restore a deleted board
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------- |
| boardId | Integer | The id of the board to fetch |
#### Response
##### 200 Success
### POST /boards/{boardId}/acl - Add new acl rule
#### Request body
| Parameter | Type | Description |
| --------- | ------ | ---------------------------------------------------- |
| type | Integer | Type of the participant |
| participant | String | The uid of the participant |
| permissionEdit | Bool | Setting if the participant has edit permissions |
| permissionShare | Bool | Setting if the participant has sharing permissions |
| permissionManage | Bool | Setting if the participant has management permissions |
##### Supported participant types:
- 0 User
- 1 Group
- 7 Circle
#### Response
##### 200 Success
```json
[{
"participant": {
"primaryKey": "userid",
"uid": "userid",
"displayname": "User Name"
},
"type": 0,
"boardId": 1,
"permissionEdit": true,
"permissionShare": false,
"permissionManage": true,
"owner": false,
"id": 1
}]
```
### PUT /boards/{boardId}/acl/{aclId} - Update an acl rule
#### Request parameters
| Parameter | Type | Description |
| --------- | ------ | ---------------------------------------------------- |
| permissionEdit | Bool | Setting if the participant has edit permissions |
| permissionShare | Bool | Setting if the participant has sharing permissions |
| permissionManage | Bool | Setting if the participant has management permissions |
#### Response
##### 200 Success
### DELETE /boards/{boardId}/acl/{aclId} - Delete an acl rule
#### Response
##### 200 Success
## Stacks
### GET /boards/{boardId}/stacks - Get stacks
#### Headers
The board list endpoint supports setting an `If-Modified-Since` header to limit the results to entities that are changed after the provided time.
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------- |
| boardId | Integer | The id of the board to fetch |
#### Response
```json
[
{
"title": "ToDo",
"boardId": 2,
"deletedAt": 0,
"lastModified": 1541426139,
"cards": [...],
"order": 999,
"id": 4
}
]
```
##### 200 Success
### GET /boards/{boardId}/stacks/archived - Get list of archived stacks
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------- |
| boardId | Integer | The id of the board to fetch |
#### Response
```json
[
{
"title": "ToDo",
"boardId": 2,
"deletedAt": 0,
"lastModified": 1541426139,
"cards": [...],
"order": 999,
"id": 4
}
]
```
##### 200 Success
### GET /boards/{boardId}/stacks/{stackId} - Get stack details
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------------- |
| boardId | Integer | The id of the board the stack belongs to |
| stackId | Integer | The id of the stack |
#### Response
##### 200 Success
### POST /boards/{boardId}/stacks - Create a new stack
#### Request body
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------------------------- |
| title | String | The title of the new stack |
| order | Integer | Order for sorting the stacks |
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------- |
| boardId | Integer | The id of the board to fetch |
#### Response
##### 200 Success
### PUT /boards/{boardId}/stacks/{stackId} - Update stack details
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------------- |
| boardId | Integer | The id of the board the stack belongs to |
| stackId | Integer | The id of the stack |
#### Request body
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------------------------- |
| title | String | The title of the new stack |
| order | Integer | Order for sorting the stacks |
#### Response
##### 200 Success
### DELETE /boards/{boardId}/stacks/{stackId} - Delete a stack
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------------- |
| boardId | Integer | The id of the board the stack belongs to |
| stackId | Integer | The id of the stack |
#### Response
##### 200 Success
## Cards
### GET /boards/{boardId}/stacks/{stackId}/cards/{cardId} - Get card details
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| boardId | Integer | The id of the board the card belongs to |
| stackId | Integer | The id of the stack the card belongs to |
| cardId | Integer | The id of the card |
#### Response
##### 200 Success
### POST /boards/{boardId}/stacks/{stackId}/cards - Create a new card
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| boardId | Integer | The id of the board the card belongs to |
| stackId | Integer | The id of the stack the card belongs to |
#### Request body
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------------------------- |
| title | String | The title of the new stack |
| type | String | Type of the card (for later use) use 'plain' for now |
| order | Integer | Order for sorting the stacks |
#### Response
```json
{
"title":"Test",
"description":null,
"stackId":6,
"type":"plain",
"lastModified":1541528026,
"createdAt":1541528026,
"labels":null,
"assignedUsers":null,
"attachments":null,
"attachmentCount":null,
"owner":"admin",
"order":999,
"archived":false,
"duedate":null,
"deletedAt":0,
"commentsUnread":0,
"id":10,
"overdue":0
}
```
##### 200 Success
### PUT /boards/{boardId}/stacks/{stackId}/cards/{cardId} - Update card details
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| boardId | Integer | The id of the board the card belongs to |
| stackId | Integer | The id of the stack the card belongs to |
| cardId | Integer | The id of the card |
#### Request data
| Parameter | Type | Description |
|-------------|-----------|------------------------------------------------------|
| title | String | The card title |
| description | String | The markdown description of the card |
| type | String | Type of the card (for later use) use 'plain' for now |
| order | Integer | Order for sorting the stacks |
| duedate | timestamp | The duedate of the card or null |
```
{
"title": "Test card",
"description": "A card description",
"type": "plain",
"order": 999,
"duedate": null,
}
```
#### Response
##### 200 Success
### DELETE /boards/{boardId}/stacks/{stackId}/cards/{cardId} - Delete a card
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| boardId | Integer | The id of the board the card belongs to |
| stackId | Integer | The id of the stack the card belongs to |
| cardId | Integer | The id of the card |
#### Response
##### 200 Success
### PUT /boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignLabel - Assign a label to a card
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| boardId | Integer | The id of the board the card belongs to |
| stackId | Integer | The id of the stack the card belongs to |
| cardId | Integer | The id of the card |
#### Request data
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| labelId | Integer | The label id to assign to the card |
#### Response
##### 200 Success
### PUT /boards/{boardId}/stacks/{stackId}/cards/{cardId}/removeLabel - Remove a label to a card
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| boardId | Integer | The id of the board the card belongs to |
| stackId | Integer | The id of the stack the card belongs to |
| cardId | Integer | The id of the card |
#### Request data
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| labelId | Integer | The label id to remove to the card |
#### Response
##### 200 Success
### PUT /boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignUser - Assign a user to a card
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| boardId | Integer | The id of the board the card belongs to |
| stackId | Integer | The id of the stack the card belongs to |
| cardId | Integer | The id of the card |
#### Request data
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| userId | String | The user id to assign to the card |
#### Response
##### 200 Success
```json
{
"id": 3,
"participant": {
"primaryKey": "admin",
"uid": "admin",
"displayname": "admin"
},
"cardId": 1
}
```
##### 400 Bad request
```json
{
"status": 400,
"message": "The user is already assigned to the card"
}
```
The request can fail with a bad request response for the following reasons:
- Missing or wrongly formatted request parameters
- The user is already assigned to the card
- The user is not part of the board
### PUT /boards/{boardId}/stacks/{stackId}/cards/{cardId}/unassignUser - Assign a user to a card
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| boardId | Integer | The id of the board the card belongs to |
| stackId | Integer | The id of the stack the card belongs to |
| cardId | Integer | The id of the card |
#### Request data
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| userId | String | The user id to assign to the card |
#### Response
##### 200 Success
### PUT /boards/{boardId}/stacks/{stackId}/cards/{cardId}/reorder - Change the sorting order of a card
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| boardId | Integer | The id of the board the card belongs to |
| stackId | Integer | The id of the stack the card belongs to |
| cardId | Integer | The id of the card |
#### Request data
| Parameter | Type | Description |
| --------- | ------- | ----------------------------------------------------------- |
| order | Integer | The position in the stack where the card should be moved to |
| stackId | Integer | The id of the stack where the card should be moved to |
#### Response
##### 200 Success
## Labels
### GET /boards/{boardId}/labels/{labelId} - Get label details
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------------- |
| boardId | Integer | The id of the board the label belongs to |
| labelId | Integer | The id of the label |
#### Response
##### 200 Success
```json
{
"title": "Abgeschlossen",
"color": "31CC7C",
"boardId": "2",
"cardId": null,
"id": 5
}
```
### POST /boards/{boardId}/labels - Create a new label
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------------- |
| boardId | Integer | The id of the board the label belongs to |
#### Request data
```json
{
"title": "Finished",
"color": "31CC7C"
}
```
#### Response
##### 200 Success
### PUT /boards/{boardId}/labels/{labelId} - Update label details
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------------- |
| boardId | Integer | The id of the board the label belongs to |
| labelId | Integer | The id of the label |
#### Request data
```json
{
"title": "Finished",
"color": "31CC7C"
}
```
#### Response
##### 200 Success
### DELETE /boards/{boardId}/labels/{labelId} - Delete a label
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------------- |
| boardId | Integer | The id of the board the label belongs to |
| labelId | Integer | The id of the label |
#### Response
##### 200 Success
## Attachments
### GET /boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments - Get a list of attachments
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------- |
| boardId | Integer | The id of the board the card belongs to |
| stackId | Integer | The id of the stack the card belongs to |
| cardId | Integer | The id of the card |
#### Response
##### 200 Success
```json
[
{
"cardId": 5,
"type": "deck_file",
"data": "6DADC2C69F4.eml",
"lastModified": 1541529048,
"createdAt": 1541529048,
"createdBy": "admin",
"deletedAt": 0,
"extendedData": {
"filesize": 922258,
"mimetype": "application/octet-stream",
"info": {
"dirname": ".",
"basename": "6DADC2C69F4.eml",
"extension": "eml",
"filename": "6DADC2C69F4"
}
},
"id": 6
}
]
```
### GET /boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId} - Get the attachment file
#### Request parameters
| Parameter | Type | Description |
| ------------ | ------- | --------------------------------------------- |
| boardId | Integer | The id of the board the attachment belongs to |
| stackId | Integer | The id of the stack the attachment belongs to |
| cardId | Integer | The id of the card the attachment belongs to |
| attachmentId | Integer | The id of the attachment |
#### Response
##### 200 Success
### POST /boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments - Upload an attachment
#### Request parameters
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------------- |
| boardId | Integer | The id of the board the attachment belongs to |
| stackId | Integer | The id of the stack the attachment belongs to |
| cardId | Integer | The id of the card the attachment belongs to |
#### Request data
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------------- |
| type | String | The type of the attachement |
| file | Binary | File data to add as an attachment |
For now only `deck_file` is supported as an attachment type.
#### Response
##### 200 Success
### PUT /boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId} - Update an attachment
#### Request parameters
| Parameter | Type | Description |
| ------------ | ------- | --------------------------------------------- |
| boardId | Integer | The id of the board the attachment belongs to |
| stackId | Integer | The id of the stack the attachment belongs to |
| cardId | Integer | The id of the card the attachment belongs to |
| attachmentId | Integer | The id of the attachment |
#### Request data
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------------- |
| type | String | The type of the attachement |
| file | Binary | File data to add as an attachment |
For now only `deck_file` is supported as an attachment type.
#### Response
##### 200 Success
### DELETE /boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId} - Delete an attachment
#### Request parameters
| Parameter | Type | Description |
| ------------ | ------- | --------------------------------------------- |
| boardId | Integer | The id of the board the attachment belongs to |
| stackId | Integer | The id of the stack the attachment belongs to |
| cardId | Integer | The id of the card the attachment belongs to |
| attachmentId | Integer | The id of the attachment |
#### Response
##### 200 Success
### PUT /boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}/restore - Resore a deleted attachment
#### Request parameters
| Parameter | Type | Description |
| ------------ | ------- | --------------------------------------------- |
| boardId | Integer | The id of the board the attachment belongs to |
| stackId | Integer | The id of the stack the attachment belongs to |
| cardId | Integer | The id of the card the attachment belongs to |
| attachmentId | Integer | The id of the attachment |
#### Response
##### 200 Success

View File

@@ -1,27 +0,0 @@
## What is Markdown
The [wikipedia markdown entry](https://en.wikipedia.org/wiki/Markdown) introduced markdown as :
> Markdown is a lightweight markup language with plain text formatting syntax. It is designed so that it can be converted to HTML and many other formats using a tool by the same name. Markdown is often used to format readme files, for writing messages in online discussion forums, and to create rich text using a plain text editor. As the initial description of Markdown contained ambiguities and unanswered questions, many implementations and extensions of Markdown appeared over the years to answer these issues.
## Markdown in Deck
The Deck application plugin uses the [markdown-it](https://github.com/markdown-it/markdown-it) script to offer support for markdown in the cards description field.
## Supported Markdown
Markdown comes in may flavors. The best way to learn markdown and understand how to use it, is simply to [try it](https://markdown-it.github.io) on the original script official playground.
That same link offers also a comprehensive list of what is supported, and what is not - rendering it unnecessary to duplicate that content in here.
[CommonMark Markdown Reference](http://commonmark.org/help/)
## Known Issues
As per [issue #127](https://github.com/nextcloud/deck/issues/127) Due to a known limitation of the current script to support markdown, Links that contain the `")"` character will not display well, or will break.
The recommended solution is to use `"<"` and `">"` to wrap those links. It should assure their integrity.
If you come by another case of broken link, or broken display of links, please report it by opening a new issue.

View File

@@ -1,55 +0,0 @@
Releasing a new version works quite easy with [krankerl](https://github.com/ChristophWurst/krankerl) and [github-release](https://github.com/aktau/github-release) installed:
1. Run krankerl to build the package
```
krankerl package
```
2. Tag the release on GitHub
```
# For a prerelease
github-release release -u nextcloud -r deck -t v0.3.1 -p
# For a regular release
github-release release -u nextcloud -r deck -t v0.3.1
```
3. Upload the release package to GitHub
```
github-release upload -u nextcloud -r deck -t v0.3.1 -n deck.tar.gz -f build/artifacts/deck.tar.gz
```
4. Run krankerl to release the package to the app store (add `--nightly` for prerelease packages)
```
krankerl publish https://github.com/nextcloud/deck/releases/download/v0.3.1/deck.tar.gz
```
## Release PR template
```
## Backports
- [ ] ...
## Translations
- [ ] ...
## Release
- [ ] Set proper Nextcloud versions in info.xml
- [ ] Update changelog
- [ ] Build test release
- [ ] Tested on
- [ ] Nextcloud 13
- [ ] Nextcloud 14
- [ ] Nextcloud 15
- [ ] Merge
- [ ] Build final release
- [ ] Publish release
- [ ] Upload to the app store
```

View File

@@ -1,69 +0,0 @@
## Introduction
### What about Deck ?
You may know Kanban website like Trello ? Deck is about the same thing but secured and respectful of your privacy !
Integrated in Nextcloud, you can easily manage your projects while having your data secured.
### Use cases
Project management, time management or ideation, Deck makes it easier for you to manage your work.
## Using Deck
Overall, Deck is easy to use. You can create boards, add users, share the Deck, work collaboratively and in real time.
1. [Create my first board](#1-create-my-first-board)
2. [Create stacks and cards](#2-create-stacks-and-cards)
3. [Handle cards options](#3-handle-cards-options)
4. [Archive old tasks](#4-archive-old-tasks)
5. [Manage your board](#5-manage-your-board)
### 1. Create my first board
In this example, we're going to create a board and share it with an other nextcloud user.
![Gif for creating boards](resources/gifs/EN_create_board.gif)
### 2. Create stacks and cards
Stacks are simply columns with list of cards. It can represent a category of tasks or an y step in your projects for example.
**Check this out :**
![Gif for creating columns](resources/gifs/EN_create_columns.gif)
What about the cards? Cards are tasks, objects or ideas that fit into a stack. You can put a lot of cards in a stack! An infinity? Who knows! Who knows!
And all the magic of this software consists on moving your cards from a stack to an other.
**Check this out :**
![Gif for creating tasks](resources/gifs/EN_create_task.gif)
### 3. Handle cards options
Once you have created your cards, you can modify them or add options by clicking on them. So, what are the options? Well, there are several of them:
- Tag Management
- Assign a card to a user (s·he will receive a notification)
- Render date, or deadline
![Gif for puting infos on tasks](resources/gifs/EN_put_infos.gif)
And even :
- Description in markdown language
- Attachment - *you can leave a document, a picture or some other bonus like that.*
![Gif for puting infos on tasks 2](resources/gifs/EN_put_infos_2.gif)
### 4. Archive old tasks
Once finished or obsolete, a task could be archived. The tasks is not deleted, it's just archived, and you can retrieve it later
![Gif for puting infos on tasks 2](resources/gifs/EN_archive.gif)
### 5. Manage your board
You can manage the settings of your Deck once you are inside it, by clicking on the small wheel at the top right.
Once in this menu, you have access to several things:
- Sharing
- Tags
- Deleted objects
- Timeline
The **sharing tab** allows you to add users or even groups to your boards.
**Tags** allows you to modify the tags available for the cards.
**Deleted objects** allows you to return previously deleted stacks or cards.
The **Timeline** allows you to see everything that happened in your boards. Everything!

View File

@@ -1,3 +0,0 @@
.subnav ul {
padding-left: 20px;
}

View File

@@ -1 +0,0 @@
../README.md

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 556 KiB

View File

@@ -1,6 +0,0 @@
## Database structure
Deck stores most of its data inside of the database. The structure and relationships between entities is documented in the following ER diagram:
![Screenshot](resources/er-diagram.jpg)

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" version="1.1" height="32" viewbox="0 0 32 32"><path fill="#000" d="m8 2c-1.108 0-2 0.892-2 2v4c0 1.108 0.892 2 2 2s2-0.892 2-2v-4c0-1.108-0.892-2-2-2zm16 0c-1.108 0-2 0.892-2 2v4c0 1.108 0.892 2 2 2s2-0.892 2-2v-4c0-1.108-0.892-2-2-2zm-13 4v2c0 1.662-1.338 3-3 3s-3-1.338-3-3v-1.875a3.993 3.993 0 0 0 -3 3.875v16c0 2.216 1.784 4 4 4h20c2.216 0 4-1.784 4-4v-16a3.993 3.993 0 0 0 -3 -3.875v1.875c0 1.662-1.338 3-3 3s-3-1.338-3-3v-2zm-4.906 10h19.812a0.09 0.09 0 0 1 0.094 0.094v9.812a0.09 0.09 0 0 1 -0.094 0.094h-19.812a0.09 0.09 0 0 1 -0.094 -0.094v-9.812a0.09 0.09 0 0 1 0.094 -0.094z"/></svg>

Before

Width:  |  Height:  |  Size: 646 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58" width="512" height="512"><g fill="#000"><path d="M54.319 37.839C54.762 35.918 55 33.96 55 32c0-9.095-4.631-17.377-12.389-22.153a1 1 0 1 0-1.049 1.703C48.724 15.96 53 23.604 53 32c0 1.726-.2 3.451-.573 5.147A6.992 6.992 0 0 0 51 37c-3.86 0-7 3.141-7 7s3.14 7 7 7 7-3.141 7-7a7.006 7.006 0 0 0-3.681-6.161zM38.171 54.182A23.867 23.867 0 0 1 29 56a24.047 24.047 0 0 1-17.017-7.092A6.974 6.974 0 0 0 14 44c0-3.859-3.14-7-7-7s-7 3.141-7 7 3.14 7 7 7a6.952 6.952 0 0 0 3.381-.875C15.26 55.136 21.994 58 29 58c3.435 0 6.778-.663 9.936-1.971.51-.211.753-.796.542-1.307a1.001 1.001 0 0 0-1.307-.54zM4 31.213a1 1 0 0 0 1.068-.927c.712-10.089 7.586-18.52 17.22-21.314C23.142 11.874 25.825 14 29 14c3.86 0 7-3.141 7-7s-3.14-7-7-7c-3.851 0-6.985 3.127-6.999 6.975C11.42 9.922 3.851 19.12 3.073 30.146A.999.999 0 0 0 4 31.213z"/></g></svg>

Before

Width:  |  Height:  |  Size: 885 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" version="1.1" height="16"><path fill="#000" d="m2.5 1c-0.28 0-0.5 0.22-0.5 0.5v13c0 0.28 0.22 0.5 0.5 0.5h11c0.28 0 0.5-0.22 0.5-0.5v-10.5l-3-3h-8.5zm1.5 2h6v1h-6v-1zm0 3h5v1h-5v-1zm0 3h8v1h-8v-1zm0 3h4v1h-4v-1z"/></svg>

Before

Width:  |  Height:  |  Size: 292 B

View File

@@ -1 +0,0 @@
<svg width="16" height="16" version="1.1" viewBox="0 0 4.2333 4.2333" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -292.77)" display="none" stroke-width=".23666"><rect x=".28112" y="293.43" width="3.7042" height="1.1906" ry=".20225"/><rect x=".26458" y="295.15" width="3.7042" height="1.1906" ry=".20225"/></g><g transform="translate(0 -292.77)"><g transform="matrix(.040404 0 0 .040404 -3.0978 290.01)"><rect x="83.629" y="114.13" width="91.678" height="13.097" stroke-width="3.9049"/><path d="m155.25 81.388-26.194 26.194-26.194-26.154z" stroke-width="6.5484"/><path d="m155.25 159.97-26.194-26.194-26.194 26.154z" stroke-width="6.5484"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 671 B

View File

@@ -1 +0,0 @@
<svg width="16" height="16" version="1.1" viewBox="0 0 4.2333 4.2333" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -292.77)" display="none" stroke-width=".23666"><rect x=".28112" y="293.43" width="3.7042" height="1.1906" ry=".20225"/><rect x=".26458" y="295.15" width="3.7042" height="1.1906" ry=".20225"/></g><g transform="translate(0 -292.77)"><g transform="matrix(.040404 0 0 .040404 -3.0978 290.01)"><rect x="83.629" y="114.13" width="91.678" height="13.097" stroke-width="3.9049"/><path d="m155.25 107.58-26.194-26.194-26.194 26.154z" stroke-width="6.5484"/><path d="m155.25 133.78-26.194 26.194-26.194-26.154z" stroke-width="6.5484"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 671 B

View File

@@ -1,12 +0,0 @@
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
browsers: ['last 2 versions', 'ie >= 11']
}
}
]
]
}

View File

@@ -1,6 +1,4 @@
{
"esversion": 6,
"globals": {
"jasmine" : false,
"spyOn" : false,
@@ -23,6 +21,7 @@
"devel" : true,
"eqeqeq" : true,
"eqnull" : false,
"es5" : true,
"evil" : false,
"forin" : true,
"immed" : true,
@@ -40,6 +39,7 @@
"plusplus" : false,
"quotmark" : "single",
"regexp" : false,
"strict" : true,
"sub" : true,
"trailing" : true,
"undef" : true,

View File

@@ -4,20 +4,20 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
/* global angular */
@@ -48,17 +48,13 @@ import ngsortable from 'ng-sortable';
import md from 'angular-markdown-it';
import nganimate from 'angular-animate';
import 'angular-file-upload';
import ngInfiniteScroll from 'ng-infinite-scroll';
import '../legacy/jquery.atwho.min';
import '../legacy/jquery.caret.min';
var app = angular.module('Deck', [
ngsanitize,
uirouter,
angularuiselect,
ngsortable, md, nganimate,
'angularFileUpload',
ngInfiniteScroll
'angularFileUpload'
]);
export default app;

View File

@@ -4,20 +4,20 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
import app from './App.js';

View File

@@ -1,350 +0,0 @@
/*
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* global OC OCA OCP t escapeHTML Handlebars moment */
import CommentCollection from '../legacy/commentcollection';
import CommentModel from '../legacy/commentmodel';
class ActivityController {
constructor ($scope, CardService, ActivityService, BoardService) {
'ngInject';
this.cardservice = CardService;
this.boardservice = BoardService;
this.activityservice = ActivityService;
this.$scope = $scope;
this.type = '';
this.loading = false;
this.status = {
commentCreateLoading: false
};
this.$scope.newComment = '';
this.$scope.newCommentString = 'New comment…';
this.currentUser = OC.getCurrentUser();
const self = this;
this.$scope.$watch(function () {
return self.element.id;
}, function (params) {
if (self.type === 'deck_card') {
self.activityservice.loadComments(self.element.id);
}
if (self.getData(self.element.id).length === 0) {
self.loading = true;
self.fetchUntilResults();
}
self.activityservice.fetchNewerActivities(self.type, self.element.id).then(function () {});
if (self.type === 'deck_card') {
self.cardservice.getCurrent().commentsUnread = 0;
}
}, true);
let $target = $('.newCommentForm .message');
this.applyAtWho($target);
this.activityservice.subscribe(this.$scope, function() {
self.$scope.$apply();
});
if (typeof OCA.Activity.Templates !== 'undefined') {
OCA.Activity.Templates.userLocal = Handlebars.template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
var helper;
// Compiled handlesbars template
// '<span class="avatar-name-wrapper"><avatar ng-attr-contactsmenu ng-attr-tooltip ng-attr-user="{{ id }}" ng-attr-displayname="{{name}}" ng-attr-size="16"></avatar> {{ name }}</span>';
return "<span class=\"avatar-name-wrapper\"><avatar ng-attr-contactsmenu ng-attr-tooltip ng-attr-user=\""
+ container.escapeExpression(((helper = (helper = helpers.id || (depth0 != null ? depth0.id : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"id","hash":{},"data":data}) : helper)))
+ "\" ng-attr-displayname=\""
+ container.escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"name","hash":{},"data":data}) : helper)))
+ "\" ng-attr-size=\"16\"></avatar> "
+ container.escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"name","hash":{},"data":data}) : helper)))
+ "</span>";
},"useData":true});
} else {
OCA.Activity.RichObjectStringParser._userLocalTemplate = '<span class="avatar-name-wrapper"><avatar ng-attr-contactsmenu ng-attr-tooltip ng-attr-user="{{ id }}" ng-attr-displayname="{{name}}" ng-attr-size="16"></avatar> {{ name }}</span>';
}
}
applyAtWho($target) {
const self = this;
if (!$target) {
return;
}
$target.atwho({
at: '@',
callbacks: {
remoteFilter: function(query, callback) {
let uids = self.boardservice.getUsers();
uids = uids.filter((x) => x.uid.toLowerCase().includes(query.toLowerCase()) || x.displayname.toLowerCase().includes(query.toLowerCase()));
callback(uids);
},
highlighter: function (li) {
// misuse the highlighter callback to instead of
// highlighting loads the avatars.
var $li = $(li);
$li.find('.avatar').avatar(undefined, 32);
return $li;
},
sorter: function (q, items) { return items; }
},
displayTpl: function (item) {
return '<li>' +
'<span class="avatar-name-wrapper">' +
'<span class="avatar" ' +
'data-username="' + escapeHTML(item.uid) + '" ' + // for avatars
'data-user="' + escapeHTML(item.uid) + '" ' + // for contactsmenu
'data-user-display-name="' + escapeHTML(item.displayname) + '">' +
'</span>' +
'<strong>' + escapeHTML(item.displayname) + '</strong>' +
'</span></li>';
},
insertTpl: function (item) {
return '' +
'<span class="avatar-name-wrapper">' +
'<span class="avatar" ' +
'data-username="' + escapeHTML(item.uid) + '" ' + // for avatars
'data-user="' + escapeHTML(item.uid) + '" ' + // for contactsmenu
'data-user-display-name="' + escapeHTML(item.displayname) + '">' +
'</span>' +
'<strong>' + escapeHTML(item.displayname) + '</strong>' +
'</span>';
},
searchKey: 'displayname'
});
$target.on('inserted.atwho', function (je, $el) {
$(je.target).find(
'span[data-username="' + $el.find('[data-username]').data('username') + '"]'
).avatar(undefined, 16);
});
$target.on('shown.atwho', function (je) {
$target.find('.avatar').avatar(undefined, 16);
});
}
commentBodyToPlain(content) {
let $comment = $('<div/>').html(content);
$comment.find('.avatar-name-wrapper').each(function () {
var $this = $(this);
var $inserted = $this.parent();
$inserted.html('@' + $this.find('.avatar').data('username'));
});
$comment.html(OCP.Comments.richToPlain($comment.html()));
$comment.html($comment.html().replace(/<br\s*[\/]?>/gi, '\n'));
return $comment.text();
}
static _composeHTMLMention(uid, displayName) {
var avatar = '' +
'<span class="avatar" data-username="' + escapeHTML(uid) + '" data-user="' + escapeHTML(uid) + '" ng-attr-size="16" ' +
'ng-attr-user="' + escapeHTML(uid) + '" ' +
'ng-attr-displayname="' + escapeHTML(displayName) + '" ng-attr-contactsmenu="true">' +
'</span>';
var isCurrentUser = (uid === OC.getCurrentUser().uid);
return '' +
'<span class="atwho-inserted" contenteditable="false">' +
'<span class="avatar-name-wrapper' + (isCurrentUser ? ' currentUser' : '') + '">' +
avatar +
'<strong>' + escapeHTML(displayName) + '</strong>' +
'</span>' +
'</span>';
}
formatMessage(activity) {
let message = activity.message;
let mentions = activity.commentModel.get('mentions');
const editMode = false;
message = escapeHTML(message).replace(/\n/g, '<br/>');
for(var i in mentions) {
if(!mentions.hasOwnProperty(i)) {
return;
}
var mention = '@' + mentions[i].mentionId;
// escape possible regex characters in the name
mention = mention.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const displayName = ActivityController._composeHTMLMention(mentions[i].mentionId, mentions[i].mentionDisplayName);
// replace every mention either at the start of the input or after a whitespace
// followed by a non-word character.
message = message.replace(new RegExp('(^|\\s)(' + mention + ')\\b', 'g'),
function(match, p1) {
// to get number of whitespaces (0 vs 1) right
return p1+displayName;
}
);
}
if(editMode !== true) {
message = OCP.Comments.plainToRich(message);
}
return message;
}
postComment() {
const self = this;
this.status.commentCreateLoading = true;
let content = this.commentBodyToPlain(self.$scope.newComment);
if (content.length < 1) {
self.status.commentCreateLoading = false;
OC.Notification.showTemporary(t('deck', 'Please provide a content for your comment.'));
return;
}
var model = this.activityservice.commentCollection.create({
actorId: OC.getCurrentUser().uid,
actorDisplayName: OC.getCurrentUser().displayName,
actorType: 'users',
verb: 'comment',
message: content,
creationDateTime: (new Date()).toUTCString()
}, {
at: 0,
// wait for real creation before adding
wait: true,
success: function() {
self.$scope.newComment = '';
self.activityservice.fetchNewerActivities(self.type, self.element.id).then(function () {});
self.status.commentCreateLoading = false;
},
error: function() {
self.status.commentCreateLoading = false;
OC.Notification.showTemporary(t('deck', 'Posting the comment failed.'));
}
});
}
updateComment(item) {
item.commentEdit = this.formatMessage(item);
let $target = $('.newCommentForm .message');
this.applyAtWho($target);
/** Workaround to trigger avatar rendering after the view has been updated */
window.setTimeout(function () {
$target.find('.avatar').avatar(undefined, 16);
}, 0);
}
editComment(item) {
const self = this;
let content = this.commentBodyToPlain(item.commentEdit);
if (content.length < 1) {
OC.Notification.showTemporary(t('deck', 'Please provide a content for your comment.'));
return;
}
/** We need to save the model and afterwards run a fetch to update the mentions
* and call apply to propagate the changes to angular
*/
item.commentModel.on('sync', function() {
item.commentModel.off('sync');
item.commentModel.fetch({
success: function() {
self.$scope.$apply();
}
});
});
item.commentModel.save({
message: content,
});
item.message = content;
item.commentEdit = undefined;
}
deleteComment(item) {
item.commentModel.destroy();
item.deleted = true;
item.commentModel = undefined;
item.message = t('deck', 'The comment has been deleted');
}
getData(id) {
return this.activityservice.getData(this.type, id);
}
parseMessage(activity) {
let subject = activity.subject_rich[0];
let parameters = activity.subject_rich[1];
if (parameters.after && typeof parameters.after.id === 'string' && parameters.after.id.startsWith('dt:')) {
let dateTime = parameters.after.id.substr(3);
parameters.after.name = moment(dateTime).format('L LTS');
}
return OCA.Activity.RichObjectStringParser.parseMessage(subject, parameters);
}
fetchUntilResults () {
const self = this;
let dataLengthBefore = self.getData(self.element.id).length;
let _executeFetch = function() {
let promise = self.activityservice.fetchMoreActivities(self.type, self.element.id);
promise.then(function (data) {
let dataLengthAfter = self.getData(self.element.id).length;
if (data !== null && (dataLengthAfter <= dataLengthBefore || dataLengthAfter < self.activityservice.RESULT_PER_PAGE)) {
_executeFetch();
} else {
self.loading = false;
}
}, function () {
self.loading = false;
self.$scope.$apply();
});
};
_executeFetch();
}
getComments() {
return this.activityservice.comments;
}
getActivityStream() {
let activities = this.activityservice.getData(this.type, this.element.id);
return activities;
}
page() {
if (!this.activityservice.since[this.type][this.element.id].finished) {
this.loading = true;
this.fetchUntilResults();
} else {
this.loading = false;
}
}
loadingNewer() {
return this.activityservice.runningNewer;
}
t(text) {
return t('deck', text);
}
}
let activityComponent = {
templateUrl: OC.linkTo('deck', 'templates/part.card.activity.html'),
controller: ActivityController,
bindings: {
type: '@',
element: '='
}
};
export default activityComponent;

View File

@@ -4,25 +4,25 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
import app from '../app/App.js';
/* globals oc_current_user: false */
/** global: OC */
app.controller('AppController', function ($scope, $location, $http, $log, $rootScope, $attrs) {
$rootScope.sidebar = {
show: false
@@ -31,14 +31,12 @@ app.controller('AppController', function ($scope, $location, $http, $log, $rootS
$scope.user = oc_current_user;
$rootScope.config = JSON.parse($attrs.config);
$rootScope.compactMode = localStorage.getItem('deck.compactMode') === 'true';
$scope.appNavigationHide = localStorage.getItem('deck.appNavigationHide') === 'true';
$scope.appNavigationHide = false;
$scope.toggleSidebar = function() {
if ($(window).width() > 768) {
$log.debug($scope.appNavigationHide);
$scope.appNavigationHide = !$scope.appNavigationHide;
localStorage.setItem('deck.appNavigationHide', JSON.stringify($scope.appNavigationHide));
console.log($scope.appNavigationHide);
}
};
});

View File

@@ -4,33 +4,25 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
/* global oc_defaults oc_config OC OCP OCA t n */
import app from '../app/App.js';
import Vue from 'vue';
Vue.prototype.t = t;
Vue.prototype.n = n;
Vue.prototype.OC = OC;
import CollaborationView from '../views/CollaborationView';
app.controller('BoardController', function ($rootScope, $scope, $element, $stateParams, StatusService, BoardService, StackService, CardService, LabelService, $state, $transitions, $filter, FileService) {
/* global oc_defaults OC */
app.controller('BoardController', function ($rootScope, $scope, $stateParams, StatusService, BoardService, StackService, CardService, LabelService, $state, $transitions, $filter, FileService) {
$scope.sidebar = $rootScope.sidebar;
@@ -49,48 +41,6 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
$scope.defaultColors = ['31CC7C', '317CCC', 'FF7A66', 'F1DB50', '7C31CC', 'CC317C', '3A3B3D', 'CACBCD'];
$scope.board = BoardService.getCurrent();
$scope.uploader = FileService.uploader;
$scope.searchText = '';
$scope.startTitleEdit = function(card) {
card.renameTitle = card.title;
card.status = card.status || {};
card.status.editCard = true;
};
$scope.finishTitleEdit = function(card) {
var newTitle;
if (!card.renameTitle || !card.renameTitle.trim()) {
newTitle = '';
} else {
newTitle = card.renameTitle.trim();
}
if (newTitle === card.title) {
// title unchanged
card.status.editCard = false;
delete card.renameTitle;
} else if (newTitle !== '') {
// title changed
card.title = newTitle;
CardService.update(card).then(function (data) {
card.status.editCard = false;
delete card.renameTitle;
});
} else {
// empty title
card.status.editCard = false;
delete card.renameTitle;
}
};
$scope.$watch(function() {
return $scope.params.tab;
}, function (newTab, oldTab) {
if (newTab === 2 && oldTab !== 2) {
CardService.fetchDeleted($scope.id);
StackService.fetchDeleted($scope.id);
}
});
// workaround for $stateParams changes not being propagated
$scope.$watch(function() {
@@ -109,8 +59,8 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
* @returns array of [finished, total] checkboxes
*/
$scope.getCheckboxes = function(text) {
const regTotal = /\[(X|\s|\_|\-)\]/igm;
const regFinished = /\[(X|\_|\-)\]/igm;
const regTotal = /\[(X|\s|\_|\-)\]\s(.*)/ig;
const regFinished = /\[(X|\_|\-)\]\s(.*)/ig;
return [
((text || '').match(regFinished) || []).length,
((text || '').match(regTotal) || []).length
@@ -156,36 +106,6 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
}
});
if (parseInt(oc_config.version.split('.')[0]) >= 16) {
const ComponentVM = new Vue({
render: h => h(CollaborationView),
data: {
model: BoardService.getCurrent()
},
});
$scope.mountCollections = function () {
const MountingPoint = document.getElementById('collaborationResources');
if (MountingPoint) {
ComponentVM.model = BoardService.getCurrent();
ComponentVM.$mount(MountingPoint);
}
};
$scope.$$postDigest($scope.mountCollections);
$scope.$watch(function () {
return BoardService.getCurrent();
}, function () {
ComponentVM.model = BoardService.getCurrent();
if ($scope.sidebar.show) {
$scope.$$postDigest($scope.mountCollections);
}
});
}
$scope.toggleCompactMode = function() {
$rootScope.compactMode = !$rootScope.compactMode;
localStorage.setItem('deck.compactMode', JSON.stringify($rootScope.compactMode));
};
$scope.stacksData = StackService;
$scope.stacks = [];
$scope.$watch('stacksData', function () {
@@ -255,97 +175,31 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
};
$scope.createCard = function (stack, title) {
if (this['addCardForm' + stack].$valid) {
var newCard = {
'title': title,
'stackId': stack,
'type': 'plain'
};
CardService.create(newCard).then(function (data) {
$scope.stackservice.addCard(data);
$scope.newCard.title = '';
});
}
};
$scope.stackDelete = function (stack) {
$scope.stackservice.delete(stack.id);
};
$scope.stackUndoDelete = function (deletedStack) {
return StackService.undoDelete(deletedStack);
var newCard = {
'title': title,
'stackId': stack,
'type': 'plain'
};
CardService.create(newCard).then(function (data) {
$scope.stackservice.addCard(data);
$scope.newCard.title = '';
});
};
$scope.cardDelete = function (card) {
CardService.delete(card.id).then(function () {
StackService.removeCard(card);
$scope.sidebar.show = false;
});
};
$scope.cardOrCardAndStackUndoDelete = function (deletedCard) {
var associatedDeletedStack = $scope.stackservice.deleted[deletedCard.stackId];
if(associatedDeletedStack !== undefined) {
$scope.cardAndStackUndoDeleteAskForConfirmation(deletedCard, associatedDeletedStack);
} else {
$scope.cardUndoDelete(deletedCard);
}
};
$scope.cardAndStackUndoDeleteAskForConfirmation = function(deletedCard, associatedDeletedStack) {
OC.dialogs.confirm(
t('deck', 'The associated stack is deleted as well, it will be restored as well.'),
t('deck', 'Restore associated stack'),
function(state) {
if (state) {
$scope.cardAndStackUndoDelete(deletedCard, associatedDeletedStack);
}
OC.dialogs.confirm(t('deck', 'Are you sure you want to delete this card with all of its data?'), t('deck', 'Delete'), function(state) {
if (!state) {
return;
}
);
};
$scope.cardAndStackUndoDelete = function(deletedCard, associatedDeletedStack) {
$scope.stackUndoDelete(associatedDeletedStack).then(function() {
$scope.cardUndoDelete(deletedCard);
CardService.delete(card.id).then(function () {
StackService.removeCard(card);
});
});
};
$scope.cardUndoDelete = function(deletedCard) {
CardService.undoDelete(deletedCard).then(function() {
StackService.addCard(deletedCard);
});
};
$scope.cardArchive = function (card) {
CardService.archive(card);
StackService.removeCard(card);
};
$scope.isCurrentUserAssigned = function (card) {
if (! CardService.get(card.id).assignedUsers) {
return false;
}
var userList = CardService.get(card.id).assignedUsers.filter(function (obj) {
return obj.participant.uid === OC.getCurrentUser().uid;
});
return userList.length === 1;
};
$scope.cardAssignToMe = function (card) {
CardService.assignUser(card, OC.getCurrentUser().uid)
.then(
function() {StackService.updateCard(card);}
);
// TODO: remove this jquery call. Fix and use appPopoverMenuUtils instead
$('.popovermenu').addClass('hidden');
};
$scope.cardUnassignFromMe = function (card) {
CardService.unassignUser(card, OC.getCurrentUser().uid);
StackService.updateCard(card);
// TODO: remove this jquery call.Fix and use appPopoverMenuUtils instead
$('.popovermenu').addClass('hidden');
};
$scope.cardUnarchive = function (card) {
CardService.unarchive(card);
StackService.removeCard(card);
@@ -356,22 +210,8 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
// remove from board data
var i = BoardService.getCurrent().labels.indexOf(label);
BoardService.getCurrent().labels.splice(i, 1);
// remove from cards
var cards = CardService.data;
for (var card in cards) {
if (Object.prototype.hasOwnProperty.call(cards, card)) {
var labelsFromCard = cards[card].labels;
labelsFromCard.forEach(function (labelFromCard, index) {
if (labelFromCard.id === label.id) {
cards[card].labels.splice(index, 1);
}
});
}
}
// TODO: remove from cards
};
$scope.labelCreate = function (label) {
label.boardId = $scope.id;
LabelService.create(label).then(function (data) {
@@ -379,35 +219,11 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
BoardService.getCurrent().labels.push(data);
$scope.status.createLabel = false;
$scope.newLabel = {};
}).catch((err) => {
OC.Notification.showTemporary(err);
});
};
$scope.labelUpdateBefore = function (label) {
label.renameTitle = label.title;
};
$scope.labelUpdate = function (label) {
label.edit = false;
LabelService.update(label).catch((err) => {
label.title = label.renameTitle;
OC.Notification.showTemporary(err);
});
// update labels in UI
var cards = CardService.data;
for (var card in cards) {
if (Object.prototype.hasOwnProperty.call(cards, card)) {
var labelsFromCard = cards[card].labels;
labelsFromCard.forEach(function (labelFromCard, index) {
if (labelFromCard.id === label.id) {
cards[card].labels[index] = label;
}
});
}
}
LabelService.update(label);
};
$scope.aclAdd = function (sharee) {
@@ -415,14 +231,12 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
BoardService.addAcl(sharee);
$scope.status.addSharee = null;
};
$scope.aclDelete = function (acl) {
BoardService.deleteAcl(acl).then(function(data) {
$scope.loadDefault();
$scope.refreshData();
});
};
$scope.aclUpdate = function (acl) {
BoardService.updateAcl(acl);
};
@@ -436,8 +250,6 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
return 'user';
case OC.Share.SHARE_TYPE_GROUP:
return 'group';
case OC.Share.SHARE_TYPE_CIRCLE:
return 'circles';
default:
return '';
}
@@ -449,7 +261,7 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
itemMoved: function (event) {
event.source.itemScope.modelValue.status = event.dest.sortableScope.$parent.column;
var order = event.dest.index;
var card = $scope.cardservice.get(event.source.itemScope.c.id);
var card = event.source.itemScope.c;
var newStack = event.dest.sortableScope.$parent.s.id;
var oldStack = card.stackId;
card.stackId = newStack;
@@ -465,7 +277,7 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
},
orderChanged: function (event) {
var order = event.dest.index;
var card = $scope.cardservice.get(event.source.itemScope.c.id);
var card = event.source.itemScope.c;
var stack = event.dest.sortableScope.$parent.s.id;
CardService.reorder(card, order).then(function (data) {
StackService.reorderCard(card, order);
@@ -542,14 +354,6 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
};
};
$scope.colorValue = function(color) {
const re = /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/;
if (re.test(color)) {
return color;
}
return '';
};
$scope.attachmentCount = function(card) {
if (Array.isArray(card.attachments)) {
return card.attachments.filter((obj) => obj.deletedAt === 0).length;
@@ -557,12 +361,4 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
return card.attachmentCount;
};
$scope.unreadCommentCount = function(card) {
return card.commentsUnread;
};
$scope.isTimelineEnabled = function() {
return OCP.Comments && OCA.Activity;
};
});

View File

@@ -4,29 +4,28 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
/* global app moment angular OC OCP OCA */
/* global app moment angular OC */
import app from '../app/App.js';
app.controller('CardController', function ($scope, $rootScope, $sce, $location, $stateParams, $state, $interval, $timeout, $filter, BoardService, CardService, StackService, StatusService, markdownItConverter, FileService) {
$scope.sidebar = $rootScope.sidebar;
$scope.status = {
renameTitle: '',
lastEdit: 0,
lastSave: Date.now()
};
@@ -90,10 +89,9 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
});
$scope.cardRenameShow = function () {
if ($scope.archived || !BoardService.canEdit()) {
return false;
} else {
$scope.status.renameTitle = CardService.getCurrent().title;
if ($scope.archived || !BoardService.canEdit())
{return false;}
else {
$scope.status.cardRename = true;
}
};
@@ -101,11 +99,11 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
$scope.toggleCheckbox = function (id) {
$('#markdown input[type=checkbox]').attr('disabled', true);
$scope.status.edit = angular.copy(CardService.getCurrent());
var reg = /\[(X|\s|\_|\-)\]/ig;
var reg = /\[(X|\s|\_|\-)\]\s(.*)/ig;
var nth = 0;
$scope.status.edit.description = $scope.status.edit.description.replace(reg, function (match, i, original) {
var result = match;
if ('' + nth++ === '' + id) {
if (nth++ === id) {
if (match.match(/^\[\s\]/i)) {
result = match.replace(/\[\s\]/i, '[x]');
}
@@ -117,7 +115,7 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
return match;
});
CardService.update($scope.status.edit).then(function (data) {
var header = $('.tabDetails');
var header = $('.section-header-tabbed .tabDetails');
header.find('.save-indicator.unsaved').hide();
header.find('.save-indicator.saved').fadeIn(250).fadeOut(1000);
});
@@ -128,11 +126,10 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
var checkboxId = $($event.target).data('id');
if ($event.target.tagName === 'LABEL') {
$scope.toggleCheckbox(checkboxId);
$event.stopPropagation();
return false;
return;
}
if ($event.target.tagName === 'INPUT') {
$event.stopPropagation();
$scope.toggleCheckbox(checkboxId);
return;
}
if (BoardService.isArchived() || CardService.getCurrent().archived) {
@@ -147,7 +144,7 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
};
$scope.cardEditDescriptionChanged = function ($event) {
$scope.status.lastEdit = Date.now();
var header = $('.tabDetails');
var header = $('.section-header-tabbed .tabDetails');
header.find('.save-indicator.unsaved').show();
header.find('.save-indicator.saved').hide();
};
@@ -157,10 +154,10 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
if (timeSinceEdit > 1000 && $scope.status.lastEdit > $scope.status.lastSave && !$scope.status.saving) {
$scope.status.lastSave = currentTime;
$scope.status.saving = true;
var header = $('.tabDetails');
var header = $('.section-header-tabbed .tabDetails');
header.find('.save-indicator.unsaved').fadeIn(500);
CardService.update($scope.status.edit).then(function (data) {
var header = $('.tabDetails');
var header = $('.section-header-tabbed .tabDetails');
header.find('.save-indicator.unsaved').hide();
header.find('.save-indicator.saved').fadeIn(250).fadeOut(1000);
$scope.status.saving = false;
@@ -170,33 +167,15 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
// handle rename to update information on the board as well
$scope.cardRename = function (card) {
var newTitle;
if (!$scope.status.renameTitle || !$scope.status.renameTitle.trim()) {
newTitle = '';
} else {
newTitle = $scope.status.renameTitle.trim();
}
if (newTitle === card.title) {
// title unchanged
CardService.rename(card).then(function (data) {
$scope.status.renameCard = false;
} else if (newTitle !== '') {
// title changed
card.title = newTitle;
CardService.rename(card).then(function (data) {
$scope.status.renameCard = false;
});
} else {
// empty title
$scope.status.renameTitle = card.title;
$scope.status.renameCard = false;
}
});
};
$scope.cardUpdate = function (card) {
CardService.update(card).then(function (data) {
$scope.status.cardEditDescription = false;
$scope.updateMarkdown($scope.status.edit.description);
var header = $('.tabDetails');
var header = $('.section-header-tabbed .tabDetails');
header.find('.save-indicator.unsaved').hide();
header.find('.save-indicator.saved').fadeIn(500).fadeOut(1000);
});
@@ -241,7 +220,7 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
element.duedate = null;
CardService.update(element);
};
/**
* Show ui-select field when clicking the add button
*/
@@ -279,8 +258,4 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
};
};
$scope.isTimelineEnabled = function() {
return OCP.Comments && OCA.Activity;
};
});

View File

@@ -4,25 +4,25 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
/* global app angular oc_isadmin */
/* global app angular */
var ListController = function ($scope, $location, $filter, BoardService, $element, $timeout, $stateParams, $state, StatusService, $http, $q, $rootScope) {
var ListController = function ($scope, $location, $filter, BoardService, $element, $timeout, $stateParams, $state, StatusService) {
function calculateNewColor() {
var boards = BoardService.getAll();
@@ -55,63 +55,6 @@ var ListController = function ($scope, $location, $filter, BoardService, $elemen
$scope.colors = ['0082c9', '00c9c6','00c906', 'c92b00', 'F1DB50', '7C31CC', '3A3B3D', 'CACBCD'];
$scope.boardservice = BoardService;
$scope.updatingBoard = null;
$scope.isAdmin = oc_isadmin;
$scope.canCreate = $rootScope.config.canCreate;
if ($scope.isAdmin) {
OC.Apps.enableDynamicSlideToggle();
$scope.groups = [];
$scope.groupLimit = [];
$scope.groupLimitDisabled = true;
let fetchGroups = function () {
var deferred = $q.defer();
// TODO: move to groups/details once 15 is min version
$http.get(OC.linkToOCS('cloud', 2) + 'groups').then(function (response) {
$scope.groups = response.data.ocs.data.groups.reduce((obj, item) => {
obj.push({
id: item,
displayname: item,
});
return obj;
}, []);
deferred.resolve($scope.groups);
}, function (error) {
deferred.reject('Error while loading groups');
});
$http.get(OC.generateUrl('apps/deck/config')).then(function (response) {
$scope.groupLimit = response.data.groupLimit;
$scope.groupLimitDisabled = false;
deferred.resolve(response.data);
}, function (error) {
deferred.reject('Error while loading groupLimit');
});
return deferred.promise;
};
let updateConfig = function() {
$scope.groupLimitDisabled = true;
var deferred = $q.defer();
$http.post(OC.generateUrl('apps/deck/config/groupLimit'), {value: $scope.groupLimit}).then(function (response) {
$scope.groupLimitDisabled = false;
deferred.resolve(response.data);
}, function (error) {
deferred.reject('Error while saving groupLimit');
});
return deferred.promise;
};
$scope.groupLimitAdd = function (element, model) {
$scope.groupLimit.push(element);
updateConfig();
};
$scope.groupLimitRemove = function (element, model) {
$scope.groupLimit = $scope.groupLimit.filter((el) => {
return el.id !== element.id;
});
updateConfig();
};
fetchGroups();
}
var filterData = function () {
if($element.attr('id') === 'app-navigation') {

View File

@@ -4,20 +4,20 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
import app from '../app/App.js';
@@ -32,10 +32,6 @@ app.directive('avatar', function() {
link: function(scope, element, attr){
scope.uid = attr.displayname;
scope.displayname = attr.displayname;
scope.size = attr.size;
if (typeof scope.size === 'undefined') {
scope.size = 32;
}
var value = attr.user;
var avatardiv = $(element).find('.avatardiv');
if(typeof attr.contactsmenu !== 'undefined' && attr.contactsmenu !== 'false') {
@@ -48,8 +44,8 @@ app.directive('avatar', function() {
placement: 'top'
});
}
avatardiv.avatar(value, scope.size, false, false, false, attr.displayname);
avatardiv.avatar(value, 32, false, false, false, attr.displayname);
},
controller: function () {}
};
});
});

View File

@@ -1,38 +0,0 @@
/*
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
app.directive('bindHtmlCompile', function ($compile) {
'use strict';
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(function () {
return scope.$eval(attrs.bindHtmlCompile);
}, function (value) {
element.html(value);
$compile(element.contents())(scope);
});
}
};
});

View File

@@ -1,59 +0,0 @@
/*
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App';
app.directive('ngContenteditable', function($compile) {
return {
require: 'ngModel',
restrict: 'A',
scope: {
submit: '&ngSubmit'
},
link: function(scope, element, attrs, ngModel) {
//read the text typed in the div (syncing model with the view)
function read() {
ngModel.$setViewValue(element.html());
}
//render the data now in your model into your view
//$render is invoked when the modelvalue differs from the viewvalue
//see documentation: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController#
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
};
//do this whenever someone starts typing
element.bind('blur keyup change', function(event) {
scope.$apply(read);
});
element.bind('keydown', function(event) {
if(event.which === 13 && event.shiftKey) {
scope.submit();
}
});
}
};
});

View File

@@ -30,10 +30,9 @@ app.directive('datepicker', function () {
return {
link: function (scope, elm, attr) {
return elm.datepicker({
dateFormat: moment.localeData().longDateFormat('L').replace('YYYY', 'YY').toLowerCase(),
dateFormat: 'yy-mm-dd',
onSelect: function(date, inst) {
var selectedDate = $(this).datepicker('getDate');
scope.setDuedate(moment(selectedDate));
scope.setDuedate(moment(date));
scope.$apply();
},
beforeShow: function(input, inst) {

View File

@@ -4,20 +4,20 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
import app from '../app/App.js';
@@ -30,32 +30,22 @@ app.directive('search', function ($document, $location) {
'onSearch': '='
},
link: function (scope) {
if (OCA.Search && OCA.Search.Core) {
// eslint-disable-next-line no-unused-vars
const search = new OCA.Search((term) => {
scope.$apply(function () {
scope.onSearch(term);
});
}, () => {
scope.$apply(function () {
scope.onSearch('');
});
var box = $('#searchbox');
box.val($location.search().search);
var doSearch = function() {
var value = box.val();
scope.$apply(function () {
scope.onSearch(value);
});
} else {
const box = $('#searchbox');
box.val($location.search().search);
};
var doSearch = function () {
var value = box.val();
scope.$apply(function () {
scope.onSearch(value);
});
};
box.on('search keyup', function (event) {
box.on('search keyup', function (event) {
if (event.type === 'search' || event.keyCode === 13 ) {
doSearch();
});
}
}
});
}
};
});

View File

@@ -4,20 +4,20 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
import app from '../app/App.js';
@@ -31,9 +31,7 @@ app.filter('cardSearchFilter', function() {
angular.forEach(cards, function(card){
var _card = card;
Object.keys(rules).some(function(rule) {
var pattern = rules[rule];
// eslint-disable-next-line detect-non-literal-regexp
if(_card[rule].search(new RegExp(pattern, 'i'))>=0) {
if(_card[rule].search(rules[rule])>=0) {
_result[_card.id] = _card;
}
});

View File

@@ -46,8 +46,7 @@ app.filter('dateToTimestamp', function() {
app.filter('parseDate', function() {
return function (date) {
if(moment(date).isValid()) {
var dateFormat = moment.localeData().longDateFormat('L');
return moment(date).format(dateFormat);
return moment(date).format('YYYY-MM-DD');
}
return '';
};

View File

@@ -1,69 +0,0 @@
/*
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
'use strict';
/* global __webpack_nonce__ __webpack_public_path__ OC t n */
// eslint-disable-next-line
__webpack_nonce__ = btoa(OC.requestToken);
// eslint-disable-next-line
__webpack_public_path__ = OC.linkTo('deck', 'js/build/');
import Vue from 'vue';
Vue.prototype.t = t;
Vue.prototype.n = n;
Vue.prototype.OC = OC;
import BoardSelector from './views/BoardSelector';
import './../css/collections.css';
((function(OCP) {
OCP.Collaboration.registerType('deck', {
action: () => {
return new Promise((resolve, reject) => {
const container = document.createElement('div');
container.id = 'deck-board-select';
const body = document.getElementById('body-user');
body.append(container);
const ComponentVM = new Vue({
render: h => h(BoardSelector),
});
ComponentVM.$mount(container);
ComponentVM.$root.$on('close', () => {
ComponentVM.$el.remove();
ComponentVM.$destroy();
reject();
});
ComponentVM.$root.$on('select', (id) => {
resolve(id);
ComponentVM.$el.remove();
ComponentVM.$destroy();
});
});
},
typeString: t('deck', 'Link to a board'),
typeIconClass: 'icon-deck'
});
})(window.OCP));

View File

@@ -1,13 +1,5 @@
'use strict';
import "@babel/polyfill";
/* global __webpack_nonce__ __webpack_public_path__ OC t n */
// eslint-disable-next-line
__webpack_nonce__ = btoa(OC.requestToken);
// eslint-disable-next-line
__webpack_public_path__ = OC.linkTo('deck', 'js/build/');
// used for building a vendor stylesheet
import 'ng-sortable/dist/ng-sortable.css';
@@ -22,11 +14,9 @@ import './app/Run.js';
import ListController from 'controller/ListController.js';
import attachmentListComponent from './controller/AttachmentController.js';
import activityComponent from './controller/ActivityController.js';
app.controller('ListController', ListController);
app.component('attachmentListComponent', attachmentListComponent);
app.component('activityComponent', activityComponent);
// require all the js files from subdirectories

View File

@@ -1,161 +0,0 @@
/**
* @licence
*/
import CommentModel from './commentmodel.js';
import CommentSummaryModel from './commentsummarymodel.js';
/**
* @class CommentCollection
* @classdesc
*
* Collection of comments assigned to a file
*
*/
var CommentCollection = OC.Backbone.Collection.extend(
/** @lends OCA.AnnouncementCenter.Comments.CommentCollection.prototype */ {
sync: OC.Backbone.davSync,
model: CommentModel,
/**
* Object type
*
* @type string
*/
_objectType: 'deckCard',
/**
* Object id
*
* @type string
*/
_objectId: null,
/**
* True if there are no more page results left to fetch
*
* @type bool
*/
_endReached: false,
/**
* Number of comments to fetch per page
*
* @type int
*/
_limit : 5,
/**
* Initializes the collection
*
* @param {string} [options.objectType] object type
* @param {string} [options.objectId] object id
*/
initialize: function(models, options) {
options = options || {};
if (options.objectType) {
this._objectType = options.objectType;
}
if (options.objectId) {
this._objectId = options.objectId;
}
},
url: function() {
return OC.linkToRemote('dav') + '/comments/' +
encodeURIComponent(this._objectType) + '/' +
encodeURIComponent(this._objectId) + '/';
},
setObjectId: function(objectId) {
this._objectId = objectId;
},
hasMoreResults: function() {
return !this._endReached;
},
reset: function() {
this._endReached = false;
this._summaryModel = null;
return OC.Backbone.Collection.prototype.reset.apply(this, arguments);
},
/**
* Fetch the next set of results
*/
fetchNext: function(options) {
var self = this;
if (!this.hasMoreResults()) {
return null;
}
var body = '<?xml version="1.0" encoding="utf-8" ?>\n' +
'<oc:filter-comments xmlns:D="DAV:" xmlns:oc="http://owncloud.org/ns">\n' +
// load one more so we know there is more
' <oc:limit>' + (this._limit + 1) + '</oc:limit>\n' +
' <oc:offset>' + this.length + '</oc:offset>\n' +
'</oc:filter-comments>\n';
options = options || {};
var success = options.success;
options = _.extend({
remove: false,
parse: true,
data: body,
davProperties: CommentCollection.prototype.model.prototype.davProperties,
success: function(resp) {
if (resp.length <= self._limit) {
// no new entries, end reached
self._endReached = true;
} else {
// remove last entry, for next page load
resp = _.initial(resp);
}
if (!self.set(resp, options)) {
return false;
}
if (success) {
success.apply(null, arguments);
}
self.trigger('sync', 'REPORT', self, options);
}
}, options);
return this.sync('REPORT', this, options);
},
/**
* Returns the matching summary model
*
* @return {OCA.AnnouncementCenter.Comments.CommentSummaryModel} summary model
*/
getSummaryModel: function() {
if (!this._summaryModel) {
this._summaryModel = new CommentSummaryModel({
id: this._objectId,
objectType: this._objectType
});
}
return this._summaryModel;
},
/**
* Updates the read marker for this comment thread
*
* @param {Date} [date] optional date, defaults to now
* @param {Object} [options] backbone options
*/
updateReadMarker: function(date, options) {
options = options || {};
return this.getSummaryModel().save({
readMarker: (date || new Date()).toUTCString()
}, options);
}
});
export default CommentCollection;

View File

@@ -1,119 +0,0 @@
/*
* Copyright (c) 2016
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
var NS_OWNCLOUD = 'http://owncloud.org/ns';
/**
* @class CommentModel
* @classdesc
*
* Comment
*
*/
var CommentModel = OC.Backbone.Model.extend(
/** @lends OCA.Comments.CommentModel.prototype */ {
sync: OC.Backbone.davSync,
/**
* Object type
*
* @type string
*/
_objectType: 'deckCard',
/**
* Object id
*
* @type string
*/
_objectId: null,
initialize: function(model, options) {
options = options || {};
if (options.objectType) {
this._objectType = options.objectType;
}
if (options.objectId) {
this._objectId = options.objectId;
}
},
defaults: {
actorType: 'users',
objectType: 'deckCard'
},
davProperties: {
'id': '{' + NS_OWNCLOUD + '}id',
'message': '{' + NS_OWNCLOUD + '}message',
'actorType': '{' + NS_OWNCLOUD + '}actorType',
'actorId': '{' + NS_OWNCLOUD + '}actorId',
'actorDisplayName': '{' + NS_OWNCLOUD + '}actorDisplayName',
'creationDateTime': '{' + NS_OWNCLOUD + '}creationDateTime',
'objectType': '{' + NS_OWNCLOUD + '}objectType',
'objectId': '{' + NS_OWNCLOUD + '}objectId',
'isUnread': '{' + NS_OWNCLOUD + '}isUnread',
'mentions': '{' + NS_OWNCLOUD + '}mentions'
},
parse: function(data) {
return {
id: data.id,
message: data.message,
actorType: data.actorType,
actorId: data.actorId,
actorDisplayName: data.actorDisplayName,
creationDateTime: data.creationDateTime,
objectType: data.objectType,
objectId: data.objectId,
isUnread: (data.isUnread === 'true'),
mentions: this._parseMentions(data.mentions)
};
},
_parseMentions: function(mentions) {
if(_.isUndefined(mentions)) {
return {};
}
var result = {};
for(var i in mentions) {
var mention = mentions[i];
if(_.isUndefined(mention.localName) || mention.localName !== 'mention') {
continue;
}
result[i] = {};
for (var child = mention.firstChild; child; child = child.nextSibling) {
if(_.isUndefined(child.localName) || !child.localName.startsWith('mention')) {
continue;
}
result[i][child.localName] = child.textContent;
}
}
return result;
},
url: function() {
let baseUrl;
if (typeof this.collection === 'undefined') {
baseUrl = OC.linkToRemote('dav') + '/comments/' +
encodeURIComponent(this.get('objectType')) + '/' +
encodeURIComponent(this.get('objectId')) + '/';
} else {
baseUrl = this.collection.url();
}
if (typeof this.get('id') !== 'undefined') {
return baseUrl + this.get('id');
} else {
return baseUrl;
}
}
});
export default CommentModel;

View File

@@ -1,54 +0,0 @@
var NS_OWNCLOUD = 'http://owncloud.org/ns';
/**
* @class OCA.AnnouncementCenter.Comments.CommentSummaryModel
* @classdesc
*
* Model containing summary information related to comments
* like the read marker.
*
*/
var CommentSummaryModel = OC.Backbone.Model.extend(
/** @lends OCA.AnnouncementCenter.Comments.CommentSummaryModel.prototype */ {
sync: OC.Backbone.davSync,
/**
* Object type
*
* @type string
*/
_objectType: 'deckCard',
/**
* Object id
*
* @type string
*/
_objectId: null,
davProperties: {
'readMarker': '{' + NS_OWNCLOUD + '}readMarker'
},
/**
* Initializes the summary model
*
* @param {string} [options.objectType] object type
* @param {string} [options.objectId] object id
*/
initialize: function(attrs, options) {
options = options || {};
if (options.objectType) {
this._objectType = options.objectType;
}
},
url: function() {
return OC.linkToRemote('dav') + '/comments/' +
encodeURIComponent(this._objectType) + '/' +
encodeURIComponent(this.id) + '/';
}
});
export default CommentSummaryModel;

File diff suppressed because one or more lines are too long

View File

@@ -1,561 +0,0 @@
/*
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
(function($, undefined) {
var _input = document.createElement('input');
var _support = {
setSelectionRange: ('setSelectionRange' in _input) || ('selectionStart' in _input),
createTextRange: ('createTextRange' in _input) || ('selection' in document)
};
var _rNewlineIE = /\r\n/g,
_rCarriageReturn = /\r/g;
var _getValue = function(input) {
if (typeof(input.value) !== 'undefined') {
return input.value;
}
return $(input).text();
};
var _setValue = function(input, value) {
if (typeof(input.value) !== 'undefined') {
input.value = value;
} else {
$(input).text(value);
}
};
var _getIndex = function(input, pos) {
var norm = _getValue(input).replace(_rCarriageReturn, '');
var len = norm.length;
if (typeof(pos) === 'undefined') {
pos = len;
}
pos = Math.floor(pos);
// Negative index counts backward from the end of the input/textarea's value
if (pos < 0) {
pos = len + pos;
}
// Enforce boundaries
if (pos < 0) { pos = 0; }
if (pos > len) { pos = len; }
return pos;
};
var _hasAttr = function(input, attrName) {
return input.hasAttribute ? input.hasAttribute(attrName) : (typeof(input[attrName]) !== 'undefined');
};
/**
* @class
* @constructor
*/
var Range = function(start, end, length, text) {
this.start = start || 0;
this.end = end || 0;
this.length = length || 0;
this.text = text || '';
};
Range.prototype.toString = function() {
return JSON.stringify(this, null, ' ');
};
var _getCaretW3 = function(input) {
return input.selectionStart;
};
/**
* @see http://stackoverflow.com/q/6943000/467582
*/
var _getCaretIE = function(input) {
var caret, range, textInputRange, rawValue, len, endRange;
// Yeah, you have to focus twice for IE 7 and 8. *cries*
input.focus();
input.focus();
range = document.selection.createRange();
if (range && range.parentElement() === input) {
rawValue = _getValue(input);
len = rawValue.length;
// Create a working TextRange that lives only in the input
textInputRange = input.createTextRange();
textInputRange.moveToBookmark(range.getBookmark());
// Check if the start and end of the selection are at the very end
// of the input, since moveStart/moveEnd doesn't return what we want
// in those cases
endRange = input.createTextRange();
endRange.collapse(false);
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
caret = rawValue.replace(_rNewlineIE, '\n').length;
} else {
caret = -textInputRange.moveStart("character", -len);
}
return caret;
}
// NOTE: This occurs when you highlight part of a textarea and then click in the middle of the highlighted portion in IE 6-10.
// There doesn't appear to be anything we can do about it.
// alert("Your browser is incredibly stupid. I don't know what else to say.");
// alert(range + '\n\n' + range.parentElement().tagName + '#' + range.parentElement().id);
return 0;
};
/**
* Gets the position of the caret in the given input.
* @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
* @returns {Number}
* @see http://stackoverflow.com/questions/263743/how-to-get-cursor-position-in-textarea/263796#263796
*/
var _getCaret = function(input) {
if (!input) {
return undefined;
}
// Mozilla, et al.
if (_support.setSelectionRange) {
return _getCaretW3(input);
}
// IE
else if (_support.createTextRange) {
return _getCaretIE(input);
}
return undefined;
};
var _setCaretW3 = function(input, pos) {
input.setSelectionRange(pos, pos);
};
var _setCaretIE = function(input, pos) {
var range = input.createTextRange();
range.move('character', pos);
range.select();
};
/**
* Sets the position of the caret in the given input.
* @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
* @param {Number} pos
* @see http://parentnode.org/javascript/working-with-the-cursor-position/
*/
var _setCaret = function(input, pos) {
input.focus();
pos = _getIndex(input, pos);
// Mozilla, et al.
if (_support.setSelectionRange) {
_setCaretW3(input, pos);
}
// IE
else if (_support.createTextRange) {
_setCaretIE(input, pos);
}
};
/**
* Inserts the specified text at the current caret position in the given input.
* @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
* @param {String} text
* @see http://parentnode.org/javascript/working-with-the-cursor-position/
*/
var _insertAtCaret = function(input, text) {
var curPos = _getCaret(input);
var oldValueNorm = _getValue(input).replace(_rCarriageReturn, '');
var newLength = +(curPos + text.length + (oldValueNorm.length - curPos));
var maxLength = +input.getAttribute('maxlength');
if(_hasAttr(input, 'maxlength') && newLength > maxLength) {
var delta = text.length - (newLength - maxLength);
text = text.substr(0, delta);
}
_setValue(input, oldValueNorm.substr(0, curPos) + text + oldValueNorm.substr(curPos));
_setCaret(input, curPos + text.length);
};
var _getInputRangeW3 = function(input) {
var range = new Range();
range.start = input.selectionStart;
range.end = input.selectionEnd;
var min = Math.min(range.start, range.end);
var max = Math.max(range.start, range.end);
range.length = max - min;
range.text = _getValue(input).substring(min, max);
return range;
};
/** @see http://stackoverflow.com/a/3648244/467582 */
var _getInputRangeIE = function(input) {
var range = new Range();
input.focus();
var selection = document.selection.createRange();
if (selection && selection.parentElement() === input) {
var len, normalizedValue, textInputRange, endRange, start = 0, end = 0;
var rawValue = _getValue(input);
len = rawValue.length;
normalizedValue = rawValue.replace(/\r\n/g, "\n");
// Create a working TextRange that lives only in the input
textInputRange = input.createTextRange();
textInputRange.moveToBookmark(selection.getBookmark());
// Check if the start and end of the selection are at the very end
// of the input, since moveStart/moveEnd doesn't return what we want
// in those cases
endRange = input.createTextRange();
endRange.collapse(false);
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
start = end = len;
} else {
start = -textInputRange.moveStart("character", -len);
start += normalizedValue.slice(0, start).split("\n").length - 1;
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
end = len;
} else {
end = -textInputRange.moveEnd("character", -len);
end += normalizedValue.slice(0, end).split("\n").length - 1;
}
}
/// normalize newlines
start -= (rawValue.substring(0, start).split('\r\n').length - 1);
end -= (rawValue.substring(0, end).split('\r\n').length - 1);
/// normalize newlines
range.start = start;
range.end = end;
range.length = range.end - range.start;
range.text = normalizedValue.substr(range.start, range.length);
}
return range;
};
/**
* Gets the selected text range of the given input.
* @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
* @returns {Range}
* @see http://stackoverflow.com/a/263796/467582
* @see http://stackoverflow.com/a/2966703/467582
*/
var _getInputRange = function(input) {
if (!input) {
return undefined;
}
// Mozilla, et al.
if (_support.setSelectionRange) {
return _getInputRangeW3(input);
}
// IE
else if (_support.createTextRange) {
return _getInputRangeIE(input);
}
return undefined;
};
var _setInputRangeW3 = function(input, startPos, endPos) {
input.setSelectionRange(startPos, endPos);
};
var _setInputRangeIE = function(input, startPos, endPos) {
var tr = input.createTextRange();
tr.moveEnd('textedit', -1);
tr.moveStart('character', startPos);
tr.moveEnd('character', endPos - startPos);
tr.select();
};
/**
* Sets the selected text range of (i.e., highlights text in) the given input.
* @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
* @param {Number} startPos Zero-based index
* @param {Number} endPos Zero-based index
* @see http://parentnode.org/javascript/working-with-the-cursor-position/
* @see http://stackoverflow.com/a/2966703/467582
*/
var _setInputRange = function(input, startPos, endPos) {
startPos = _getIndex(input, startPos);
endPos = _getIndex(input, endPos);
// Mozilla, et al.
if (_support.setSelectionRange) {
_setInputRangeW3(input, startPos, endPos);
}
// IE
else if (_support.createTextRange) {
_setInputRangeIE(input, startPos, endPos);
}
};
/**
* Replaces the currently selected text with the given string.
* @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
* @param {String} text New text that will replace the currently selected text.
* @see http://parentnode.org/javascript/working-with-the-cursor-position/
*/
var _replaceInputRange = function(input, text) {
var $input = $(input);
var oldValue = $input.val();
var selection = _getInputRange(input);
var newLength = +(selection.start + text.length + (oldValue.length - selection.end));
var maxLength = +$input.attr('maxlength');
if($input.is('[maxlength]') && newLength > maxLength) {
var delta = text.length - (newLength - maxLength);
text = text.substr(0, delta);
}
// Now that we know what the user selected, we can replace it
var startText = oldValue.substr(0, selection.start);
var endText = oldValue.substr(selection.end);
$input.val(startText + text + endText);
// Reset the selection
var startPos = selection.start;
var endPos = startPos + text.length;
_setInputRange(input, selection.length ? startPos : endPos, endPos);
};
var _selectAllW3 = function(elem) {
var selection = window.getSelection();
var range = document.createRange();
range.selectNodeContents(elem);
selection.removeAllRanges();
selection.addRange(range);
};
var _selectAllIE = function(elem) {
var range = document.body.createTextRange();
range.moveToElementText(elem);
range.select();
};
/**
* Select all text in the given element.
* @param {HTMLElement} elem Any block or inline element other than a form element.
*/
var _selectAll = function(elem) {
var $elem = $(elem);
if ($elem.is('input, textarea') || elem.select) {
$elem.select();
return;
}
// Mozilla, et al.
if (_support.setSelectionRange) {
_selectAllW3(elem);
}
// IE
else if (_support.createTextRange) {
_selectAllIE(elem);
}
};
var _deselectAll = function() {
if (document.selection) {
document.selection.empty();
}
else if (window.getSelection) {
window.getSelection().removeAllRanges();
}
};
$.extend($.fn, {
/**
* Gets or sets the position of the caret or inserts text at the current caret position in an input or textarea element.
* @returns {Number|jQuery} The current caret position if invoked as a getter (with no arguments)
* or this jQuery object if invoked as a setter or inserter.
* @see http://web.archive.org/web/20080704185920/http://parentnode.org/javascript/working-with-the-cursor-position/
* @since 1.0.0
* @example
* <pre>
* // Get position
* var pos = $('input:first').caret();
* </pre>
* @example
* <pre>
* // Set position
* $('input:first').caret(15);
* $('input:first').caret(-3);
* </pre>
* @example
* <pre>
* // Insert text at current position
* $('input:first').caret('Some text');
* </pre>
*/
caret: function() {
var $inputs = this.filter('input, textarea');
// getCaret()
if (arguments.length === 0) {
var input = $inputs.get(0);
return _getCaret(input);
}
// setCaret(position)
else if (typeof arguments[0] === 'number') {
var pos = arguments[0];
$inputs.each(function(_i, input) {
_setCaret(input, pos);
});
}
// insertAtCaret(text)
else {
var text = arguments[0];
$inputs.each(function(_i, input) {
_insertAtCaret(input, text);
});
}
return this;
},
/**
* Gets or sets the selection range or replaces the currently selected text in an input or textarea element.
* @returns {Range|jQuery} The current selection range if invoked as a getter (with no arguments)
* or this jQuery object if invoked as a setter or replacer.
* @see http://stackoverflow.com/a/2966703/467582
* @since 1.0.0
* @example
* <pre>
* // Get selection range
* var range = $('input:first').range();
* </pre>
* @example
* <pre>
* // Set selection range
* $('input:first').range(15);
* $('input:first').range(15, 20);
* $('input:first').range(-3);
* $('input:first').range(-8, -3);
* </pre>
* @example
* <pre>
* // Replace the currently selected text
* $('input:first').range('Replacement text');
* </pre>
*/
range: function() {
var $inputs = this.filter('input, textarea');
// getRange() = { start: pos, end: pos }
if (arguments.length === 0) {
var input = $inputs.get(0);
return _getInputRange(input);
}
// setRange(startPos, endPos)
else if (typeof arguments[0] === 'number') {
var startPos = arguments[0];
var endPos = arguments[1];
$inputs.each(function(_i, input) {
_setInputRange(input, startPos, endPos);
});
}
// replaceRange(text)
else {
var text = arguments[0];
$inputs.each(function(_i, input) {
_replaceInputRange(input, text);
});
}
return this;
},
/**
* Selects all text in each element of this jQuery object.
* @returns {jQuery} This jQuery object
* @see http://stackoverflow.com/a/11128179/467582
* @since 1.5.0
* @example
* <pre>
* // Select the contents of span elements when clicked
* $('span').on('click', function() { $(this).highlight(); });
* </pre>
*/
selectAll: function() {
return this.each(function(_i, elem) {
_selectAll(elem);
});
}
});
$.extend($, {
/**
* Deselects all text on the page.
* @returns {jQuery} The jQuery function
* @since 1.5.0
* @example
* <pre>
* // Select some text
* $('span').selectAll();
*
* // Deselect the text
* $.deselectAll();
* </pre>
*/
deselectAll: function() {
_deselectAll();
return this;
}
});
}(window.jQuery || window.Zepto || window.$));

View File

@@ -17,15 +17,11 @@ checkboxReplace = function(md, options, Token) {
idPrefix: 'checkbox'
};
options = Object.assign(defaults, options);
pattern = /(.*?)(\[(X|\s|\_|\-)\])(.*)/igm;
createTokens = function(checked, label, Token, before) {
pattern = /\[(X|\s|\_|\-)\]\s(.*)/i;
createTokens = function(checked, label, Token) {
var id, idNumeric, nodes, token;
nodes = [];
token = new Token("text", "", 0);
token.content = before;
nodes.push(token);
/**
* <div class="checkbox">
*/
@@ -46,7 +42,6 @@ checkboxReplace = function(md, options, Token) {
if (checked === true) {
token.attrs.push(["checked", "true"]);
}
token.attrs.push(["class", "checkbox"]);
nodes.push(token);
/**
@@ -73,20 +68,19 @@ checkboxReplace = function(md, options, Token) {
return nodes;
};
splitTextToken = function(original, Token) {
var checked, label, matches, text, value, before;
var checked, label, matches, text, value;
text = original.content;
matches = pattern.exec(text);
matches = text.match(pattern);
if (matches === null) {
return original;
}
checked = false;
before = matches[1];
value = matches[3];
label = matches[4];
value = matches[1];
label = matches[2];
if (value === "X" || value === "x") {
checked = true;
}
return createTokens(checked, label, Token, before);
return createTokens(checked, label, Token);
};
return function(state) {
lastId = 0;
@@ -100,11 +94,11 @@ checkboxReplace = function(md, options, Token) {
continue;
}
tokens = blockTokens[j].children;
i = 0;
while (i < tokens.length) {
i = tokens.length - 1;
while (i >= 0) {
token = tokens[i];
blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, splitTextToken(token, state.Token));
i++;
i--;
}
j++;
}

8807
js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,48 +3,37 @@
"description": "Frontend for the Nextcloud Deck app",
"repository": "https://github.com/nextcloud/deck",
"version": "1.0.0",
"main": "Gruntfile.js",
"directories": {
"test": "tests"
},
"dependencies": {
"@uirouter/angularjs": "^1.0.25",
"angular": "^1.7.9",
"angular-animate": "^1.7.9",
"@uirouter/angularjs": "^1.0.18",
"angular": "^1.7.2",
"angular-animate": "^1.7.2",
"angular-file-upload": "^2.5.0",
"angular-markdown-it": "^0.6.1",
"angular-sanitize": "^1.7.9",
"angular-sanitize": "^1.7.2",
"babel-polyfill": "^6.26.0",
"markdown-it": "^10.0.0",
"markdown-it": "^8.4.1",
"markdown-it-link-target": "^1.0.2",
"nextcloud-axios": "^0.2.1",
"nextcloud-vue": "^0.12.8",
"nextcloud-vue-collections": "^0.6.0",
"ng-infinite-scroll": "^1.3.0",
"ng-sortable": "^1.3.8",
"ui-select": "^0.19.8",
"vue": "^2.6.11",
"vuex": "^3.1.3"
"ui-select": "^0.19.8"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.9.0",
"babel-loader": "^8.1.0",
"css-loader": "^3.4.2",
"karma": "^4.4.1",
"mini-css-extract-plugin": "^0.9.0",
"style-loader": "^1.1.3",
"url-loader": "^4.0.0",
"vue-loader": "^15.9.1",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11",
"webpack-merge": "^4.2.2"
"babel-core": "^6.26.0",
"babel-loader": "^7.1.5",
"babel-preset-env": "^1.7.0",
"css-loader": "^1.0.0",
"karma": "^2.0.4",
"mini-css-extract-plugin": "^0.4.1",
"node-sass": "^4.9.2",
"webpack": "^4.15.1",
"webpack-cli": "^3.0.8",
"webpack-merge": "^4.1.3"
},
"scripts": {
"build": "NODE_ENV=production ./node_modules/webpack-cli/bin/cli.js --mode production --config webpack.prod.config.js",
"build": "./node_modules/webpack-cli/bin/cli.js --mode production --config webpack.prod.config.js",
"dev": "./node_modules/webpack-cli/bin/cli.js --mode development --config webpack.dev.config.js",
"watch": "./node_modules/webpack-cli/bin/cli.js --mode development --config webpack.dev.config.js --watch",
"test": "echo \"Warning: no test specified\" && exit 0"

View File

@@ -1,256 +0,0 @@
/*
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
import CommentCollection from '../legacy/commentcollection';
import CommentModel from '../legacy/commentmodel';
const DECK_ACTIVITY_TYPE_BOARD = 'deck_board';
const DECK_ACTIVITY_TYPE_CARD = 'deck_card';
/* global OC oc_requesttoken */
class ActivityService {
static get RESULT_PER_PAGE() { return 50; }
constructor ($rootScope, $filter, $http, $q) {
this.running = false;
this.runningNewer = false;
this.$filter = $filter;
this.$http = $http;
this.$q = $q;
this.$rootScope = $rootScope;
this.data = {};
this.data[DECK_ACTIVITY_TYPE_BOARD] = {};
this.data[DECK_ACTIVITY_TYPE_CARD] = {};
this.toEnhanceWithComments = [];
this.commentCollection = new CommentCollection();
this.commentCollection._limit = ActivityService.RESULT_PER_PAGE;
this.commentCollection.on('request', function() {
}, this);
this.commentCollection.on('sync', function(a) {
for (let index in this.toEnhanceWithComments) {
if (this.toEnhanceWithComments.hasOwnProperty(index)) {
let item = this.toEnhanceWithComments[index];
let commentId = Array.isArray(item.subject_rich[1].comment) ? item.subject_rich[1].comment.id : item.subject_rich[1].comment;
item.commentModel = this.commentCollection.get(commentId);
if (typeof item.commentModel !== 'undefined') {
this.toEnhanceWithComments = this.toEnhanceWithComments.filter((entry) => entry.activity_id !== item.activity_id);
}
}
}
var firstUnread = this.commentCollection.findWhere({isUnread: true});
if (typeof firstUnread !== 'undefined') {
this.commentCollection.updateReadMarker();
}
this.notify();
}, this);
this.commentCollection.on('add', function(model, collection, options) {
// we need to update the model, because it consists of client data
// only, but the server might add meta data, e.g. about mentions
model.fetch();
}, this);
this.since = {
deck_card: {
},
deck_board: {
},
};
}
/**
* We need a event here to properly update scope once the external data from
* the comments backbone js code has changed
*/
subscribe(scope, callback) {
let handler = this.$rootScope.$on('notify-comment-update', callback);
scope.$on('$destroy', handler);
}
notify() {
this.$rootScope.$emit('notify-comment-update');
}
static getUrl(type, id, since) {
if (type === DECK_ACTIVITY_TYPE_CARD) {
return OC.linkToOCS('apps/activity/api/v2/activity', 2) + 'filter?format=json&object_type=deck_card&object_id=' + id + '&limit=' + this.RESULT_PER_PAGE + '&since=' + since;
}
if (type === DECK_ACTIVITY_TYPE_BOARD) {
return OC.linkToOCS('apps/activity/api/v2/activity', 2) + 'deck?format=json&limit=' + this.RESULT_PER_PAGE + '&since=' + since;
}
}
fetchCardActivities(type, id, since) {
this.running = true;
this.checkData(type, id);
const self = this;
return this.$http.get(ActivityService.getUrl(type, id, since)).then(function (response) {
const objects = response.data.ocs.data;
for (let index in objects) {
if (objects.hasOwnProperty(index)) {
let item = objects[index];
self.addItem(type, id, item);
if (item.activity_id > self.since[type][id].latest) {
self.since[type][id].latest = item.activity_id;
}
}
}
self.data[type][id].sort(function(a, b) {
return b.activity_id - a.activity_id;
});
self.since[type][id].oldest = response.headers('X-Activity-Last-Given');
self.running = false;
return response;
}, function (error) {
if (error.status === 304 || error.status === 404) {
self.since[type][id].finished = true;
}
self.running = false;
});
}
fetchMoreActivities(type, id, success) {
const self = this;
this.checkData(type, id);
if (this.running === true) {
return this.runningPromise;
}
if (!this.since[type][id].finished) {
this.runningPromise = this.fetchCardActivities(type, id, this.since[type][id].oldest);
this.runningPromise.then(function() {
if (type === 'deck_card') {
self.commentCollection.fetchNext();
}
});
return this.runningPromise;
}
return Promise.reject();
}
checkData(type, id) {
if (!Array.isArray(this.data[type][id])) {
this.data[type][id] = [];
}
if (typeof this.since[type][id] === 'undefined') {
this.since[type][id] = {
latest: -1,
oldestCatchedUp: false,
oldest: '0',
finished: false,
};
}
}
addItem(type, id, item) {
const self = this;
const existingEntry = this.data[type][id].findIndex((entry) => { return entry.activity_id === item.activity_id; });
if (existingEntry !== -1) {
return;
}
/** check if the fetched item from all deck activities is actually related */
const isUnrelatedBoard = (item.object_type === DECK_ACTIVITY_TYPE_BOARD && item.object_id !== id);
const isUnrelatedCard = (item.object_type === DECK_ACTIVITY_TYPE_CARD && (
(item.subject_rich[1].board && item.subject_rich[1].board.id !== id) || (typeof item.subject_rich[1].board === 'undefined'))
);
if (type === DECK_ACTIVITY_TYPE_BOARD && (isUnrelatedBoard || isUnrelatedCard)) {
return;
}
item.timestamp = new Date(item.datetime).getTime();
item.type = 'activity';
if (item.subject_rich[1].comment) {
item.type = 'comment';
item.commentModel = this.commentCollection.get(item.subject_rich[1].comment);
if (typeof item.commentModel === 'undefined') {
this.toEnhanceWithComments.push(item);
}
}
this.data[type][id].push(item);
}
/**
* Fetch newer activities starting from the latest ones that are in cache
*
* @param type
* @param id
*/
fetchNewerActivities(type, id) {
if (this.since[type][id].latest === 0) {
return Promise.resolve();
}
let self = this;
return this.fetchNewer(type, id).then(function() {
return self.fetchNewerActivities(type, id);
});
}
fetchNewer(type, id) {
const deferred = this.$q.defer();
this.running = true;
this.runningNewer = true;
const self = this;
this.$http.get(ActivityService.getUrl(type, id, this.since[type][id].latest) + '&sort=asc').then(function (response) {
let objects = response.data.ocs.data;
let data = [];
for (let index in objects) {
if (objects.hasOwnProperty(index)) {
let item = objects[index];
self.addItem(type, id, item);
}
}
self.data[type][id].sort(function(a, b) {
return b.activity_id - a.activity_id;
});
self.since[type][id].latest = response.headers('X-Activity-Last-Given');
self.data[type][id] = data.concat(self.data[type][id]);
self.running = false;
self.runningNewer = false;
deferred.resolve(objects);
}, function (error) {
self.runningNewer = false;
self.running = false;
});
return deferred.promise;
}
getData(type, id) {
if (!Array.isArray(this.data[type][id])) {
return [];
}
return this.data[type][id];
}
loadComments(id) {
this.commentCollection.reset();
this.commentCollection.setObjectId(id);
}
}
app.service('ActivityService', ActivityService);
export default ActivityService;
export {DECK_ACTIVITY_TYPE_BOARD, DECK_ACTIVITY_TYPE_CARD};

View File

@@ -4,48 +4,35 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
import app from '../app/App.js';
/** global: oc_defaults */
app.factory('ApiService', function ($http, $q) {
var ApiService = function (http, endpoint) {
// Consider renaming endpoint to resource
this.endpoint = endpoint;
this.baseUrl = this.generateUrl(this.endpoint);
this.baseUrl = OC.generateUrl('/apps/deck/' + endpoint);
this.http = http;
this.q = $q;
this.data = {};
this.deleted = {};
this.id = null;
this.sorted = [];
};
ApiService.prototype.generateUrl = function(path) {
return OC.generateUrl('/apps/deck/' + path);
};
ApiService.prototype.tryAllThenDeleted = function(id) {
let object = this.data[id];
if (object === undefined) {
object = this.deleted[id];
}
return object;
};
ApiService.prototype.fetchAll = function () {
var deferred = $q.defer();
var self = this;
@@ -61,31 +48,6 @@ app.factory('ApiService', function ($http, $q) {
return deferred.promise;
};
ApiService.prototype.fetchDeleted = function (scopeId) {
var deferred = $q.defer();
var self = this;
self.deleted = {};
$http.get(this.generateUrl(scopeId + '/' + this.endpoint + '/deleted')).then(function (response) {
var objects = response.data;
objects.forEach(function (obj) {
if(self.deleted[obj.id] !== undefined) {
return;
}
self.deleted[obj.id] = obj;
if(self.afterFetch !== undefined) {
self.afterFetch(obj);
}
});
deferred.resolve(objects);
}, function (error) {
deferred.reject('Fetching ' + self.endpoint + ' failed');
});
return deferred.promise;
};
ApiService.prototype.fetchOne = function (id) {
this.id = id;
@@ -119,7 +81,7 @@ app.factory('ApiService', function ($http, $q) {
self.add(response.data);
deferred.resolve(response.data);
}, function (error) {
deferred.reject(error.data.message);
deferred.reject('Fetching' + self.endpoint + ' failed');
});
return deferred.promise;
};
@@ -131,7 +93,7 @@ app.factory('ApiService', function ($http, $q) {
self.add(response.data);
deferred.resolve(response.data);
}, function (error) {
deferred.reject(error.data.message);
deferred.reject('Updating ' + self.endpoint + ' failed');
});
return deferred.promise;
@@ -142,41 +104,21 @@ app.factory('ApiService', function ($http, $q) {
var self = this;
$http.delete(this.baseUrl + '/' + id).then(function (response) {
self.deleted[id] = self.data[id];
delete self.data[id];
let deletedAt = response.data.deletedAt;
if (deletedAt !== undefined) {
self.deleted[id].deletedAt = deletedAt;
}
self.remove(id);
deferred.resolve(response.data);
}, function (error) {
deferred.reject('Deleting ' + self.endpoint + ' failed');
});
return deferred.promise;
};
ApiService.prototype.undoDelete = function(entity) {
var self = this;
entity.deletedAt = 0;
var promise = this.update(entity);
promise.then(() => {
self.data[entity.id] = entity;
delete this.deleted[entity.id];
});
return promise;
};
// methods for managing data
ApiService.prototype.clear = function () {
this.data = {};
};
ApiService.prototype.add = function (entity) {
var element = this.data[entity.id];
if (element === undefined) {
@@ -190,7 +132,11 @@ app.factory('ApiService', function ($http, $q) {
element.status = {};
}
};
ApiService.prototype.remove = function (id) {
if (this.data[id] !== undefined) {
delete this.data[id];
}
};
ApiService.prototype.addAll = function (entities) {
var self = this;
angular.forEach(entities, function (entity) {

View File

@@ -4,23 +4,23 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
import app from '../app/App.js';
/* global app OC angular */
/* global app OC */
app.factory('BoardService', function (ApiService, $http, $q) {
var BoardService = function ($http, ep, $q) {
ApiService.call(this, $http, ep, $q);
@@ -59,7 +59,7 @@ app.factory('BoardService', function (ApiService, $http, $q) {
var searchData = {
format: 'json',
perPage: 4,
itemType: [0, 1, 7]
itemType: [0, 1]
};
if (search !== "") {
searchData.search = search;
@@ -79,7 +79,6 @@ app.factory('BoardService', function (ApiService, $http, $q) {
var users = response.ocs.data.exact.users.concat(response.ocs.data.users.slice(0, 4));
var groups = response.ocs.data.exact.groups.concat(response.ocs.data.groups.slice(0, 4));
var circles = response.ocs.data.exact.groups.concat(response.ocs.data.circles.slice(0, 4));
// filter out everyone who is already in the share list
angular.forEach(users, function (item) {
@@ -106,18 +105,6 @@ app.factory('BoardService', function (ApiService, $http, $q) {
self.sharees.push(acl);
}
});
angular.forEach(circles, function (item) {
var acl = self.generateAcl(OC.Share.SHARE_TYPE_CIRCLE, item);
var exists = false;
angular.forEach(self.getCurrent().acl, function (acl) {
if (acl.participant.primaryKey === item.value.shareWith) {
exists = true;
}
});
if (!exists) {
self.sharees.push(acl);
}
});
deferred.resolve(self.sharees);
}, function () {
@@ -138,8 +125,8 @@ app.factory('BoardService', function (ApiService, $http, $q) {
displayname: ocsItem.label
},
permissionEdit: true,
permissionManage: false,
permissionShare: false,
permissionManage: true,
permissionShare: true,
type: type
};
};
@@ -172,8 +159,7 @@ app.factory('BoardService', function (ApiService, $http, $q) {
var deferred = $q.defer();
var self = this;
$http.delete(this.baseUrl + '/' + acl.boardId + '/acl/' + acl.id).then(function (response) {
var index = board.acl.findIndex((item) => item.id === response.data.id);
delete board.acl[index];
delete board.acl[response.data.id];
if (response.data.type === OC.Share.SHARE_TYPE_USER) {
self._updateUsers();
} else {
@@ -193,8 +179,7 @@ app.factory('BoardService', function (ApiService, $http, $q) {
var self = this;
var _acl = acl;
$http.put(this.baseUrl + '/' + acl.boardId + '/acl', _acl).then(function (response) {
var index = board.acl.findIndex((item) => item.id === _acl.id);
board.acl[index] = response.data;
board.acl[_acl.id] = response.data;
if (response.data.type === OC.Share.SHARE_TYPE_USER) {
self._updateUsers();
} else {

View File

@@ -4,20 +4,20 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
import app from '../app/App.js';
@@ -106,11 +106,11 @@ app.factory('CardService', function (ApiService, $http, $q) {
CardService.prototype.assignUser = function (card, user) {
var deferred = $q.defer();
var self = this;
if (self.get(card.id).assignedUsers === null) {
self.get(card.id).assignedUsers = [];
if (self.getCurrent().assignedUsers === null) {
self.getCurrent().assignedUsers = [];
}
$http.post(this.baseUrl + '/' + card.id + '/assign', {'userId': user}).then(function (response) {
self.get(card.id).assignedUsers.push(response.data);
self.getCurrent().assignedUsers.push(response.data);
deferred.resolve(response.data);
}, function (error) {
deferred.reject('Error while update ' + self.endpoint);
@@ -123,7 +123,7 @@ app.factory('CardService', function (ApiService, $http, $q) {
var deferred = $q.defer();
var self = this;
$http.delete(this.baseUrl + '/' + card.id + '/assign/' + user, {}).then(function (response) {
self.get(card.id).assignedUsers = self.get(card.id).assignedUsers.filter(function (obj) {
self.getCurrent().assignedUsers = self.getCurrent().assignedUsers.filter(function (obj) {
return obj.participant.uid !== user;
});
deferred.resolve(response.data);
@@ -174,4 +174,4 @@ app.factory('CardService', function (ApiService, $http, $q) {
var service = new CardService($http, 'cards', $q);
return service;
});
});

View File

@@ -80,8 +80,8 @@ export default class FileService {
});
if (typeof existingFile !== 'undefined') {
OC.dialogs.confirm(
t('deck', `A file with the name ${fileItem.file.name} already exists. Do you want to overwrite it?`),
t('deck', 'File already exists'),
`A file with the name ${fileItem.file.name} already exists. Do you want to overwrite it?`,
'File already exists',
function (result) {
if (result) {
self.runUpload(fileItem, existingFile.id);

View File

@@ -4,20 +4,20 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
import app from '../app/App.js';
@@ -27,11 +27,6 @@ app.factory('StackService', function (ApiService, CardService, $http, $q) {
ApiService.call(this, $http, ep, $q);
};
StackService.prototype = angular.copy(ApiService.prototype);
StackService.prototype.afterFetch = function(stack) {
CardService.addAll(stack.cards);
};
StackService.prototype.fetchAll = function (boardId) {
var deferred = $q.defer();
var self = this;
@@ -134,6 +129,27 @@ app.factory('StackService', function (ApiService, CardService, $http, $q) {
}
};
// FIXME: Should not show popup but proper undo mechanism
StackService.prototype.delete = function (id) {
var deferred = $q.defer();
var self = this;
OC.dialogs.confirm(t('deck', 'Are you sure you want to delete the stack with all of its data?'), t('deck', 'Delete'), function(state) {
if (!state) {
return;
}
$http.delete(self.baseUrl + '/' + id).then(function (response) {
self.remove(id);
deferred.resolve(response.data);
}, function (error) {
deferred.reject('Deleting ' + self.endpoint + ' failed');
});
});
return deferred.promise;
};
var service = new StackService($http, 'stacks', $q);
return service;
});

View File

@@ -1,118 +0,0 @@
<!--
- @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<Modal @close="close">
<div id="modal-inner" :class="{ 'icon-loading': loading }">
<h1>{{ t('deck', 'Select the board to link to a project') }}</h1>
<ul v-if="!loading">
<li v-for="board in boards" v-if="!currentBoard || ''+board.id !== ''+currentBoard" @click="selectedBoard=board.id" :class="{'selected': (selectedBoard === board.id) }">
<span class="board-bullet" :style="{ 'backgroundColor': '#' + board.color }"></span>
<span>{{ board.title }}</span>
</li>
</ul>
<button v-if="!loading" @click="select" class="primary">{{ t('deck', 'Select board') }}</button>
</div>
</Modal>
</template>
<style scoped>
#modal-inner {
width: 90vw;
max-width: 400px;
padding: 20px;
}
ul {
min-height: 100px;
}
li {
padding: 6px;
border: 1px solid transparent;
}
li:hover, li:focus {
background-color: var(--color-background-dark);
}
li.selected {
border: 1px solid var(--color-primary);
}
.board-bullet {
display: inline-block;
width: 12px;
height: 12px;
border: none;
border-radius: 50%;
cursor: pointer;
}
li > span,
.avatar {
vertical-align: middle;
}
</style>
<script>
/* global OC */
import { Modal } from 'nextcloud-vue/dist/Components/Modal'
import { Avatar } from 'nextcloud-vue/dist/Components/Avatar'
import axios from 'nextcloud-axios'
export default {
name: 'CollaborationView',
components: {
Modal, Avatar
},
data() {
return {
boards: [],
selectedBoard: null,
loading: true,
currentBoard: null,
}
},
beforeMount() {
this.fetchBoards();
if (typeof angular !== 'undefined' && angular.element('#board')) {
try {
this.currentBoard = angular.element('#board').scope().boardservice.id || null;
} catch (e) {}
}
},
methods: {
fetchBoards() {
axios.get(OC.generateUrl('/apps/deck/boards')).then((response) => {
this.boards = response.data
this.loading = false
})
},
close() {
this.$root.$emit('close');
},
select() {
this.$root.$emit('select', this.selectedBoard)
}
},
computed: {
},
}
</script>

View File

@@ -1,56 +0,0 @@
<!--
- @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div>
<collection-list v-if="boardId" type="deck" :id="boardId" :name="boardTitle"></collection-list>
</div>
</template>
<script>
import { CollectionList } from 'nextcloud-vue-collections';
import Vue from 'vue';
import PopoverMenu from 'nextcloud-vue/dist/Components/PopoverMenu'
Vue.component('popover-menu', PopoverMenu);
export default {
name: 'CollaborationView',
computed: {
boardId() {
if (this.$root.model && this.$root.model.id) {
return '' + this.$root.model.id;
}
return null;
},
boardTitle() {
if (this.$root.model && this.$root.model.title) {
return '' + this.$root.model.title;
}
return '';
}
},
components: {
CollectionList: CollectionList
}
}
</script>

View File

@@ -1,76 +1,68 @@
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { VueLoaderPlugin } = require('vue-loader');
require('babel-polyfill');
module.exports = {
node: {
fs: 'empty',
},
entry: {
deck: ['./init.js'],
collections: ['./init-collections.js']
},
output: {
filename: '[name].js',
path: __dirname + '/build'
},
module: {
rules: [
{
test: /\.css$/,
use: ['vue-style-loader', 'style-loader', 'css-loader']
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-syntax-dynamic-import']
}
},
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
]
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'url-loader',
options: {
name: '[name].[ext]?[hash]'
}
},
]
},
/* use external jQuery from server */
externals: {
'jquery': 'jQuery'
},
resolve: {
alias: {
vue$: 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json'],
modules: [
path.resolve(__dirname),
path.join(__dirname, 'node_modules'),
'node_modules'
]
},
plugins: [
new VueLoaderPlugin(),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
node: {
fs: 'empty',
},
entry: {
deck: ['babel-polyfill', './init.js'],
},
output: {
filename: '[name].js',
path: __dirname + '/build'
},
resolve: {
modules: [path.resolve(__dirname), 'node_modules'],
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['env'],
}
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
/* separate vendor chunk for node_modules and legacy scripts */
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all'
},
legacy: {
test: /[\\/]legacy[\\/]/,
name: 'vendor',
chunks: 'all'
}
}
}
},
/* use external jQuery from server */
externals: {
'jquery': 'jQuery'
},
plugins: [
new MiniCssExtractPlugin('[name].css'),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
};

View File

@@ -2,6 +2,5 @@ const merge = require('webpack-merge');
const baseConfig = require('./webpack.config.js');
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'source-map',
mode: 'development'
});

View File

@@ -1,18 +1,15 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const baseConfig = require('./webpack.config.js');
const TerserPlugin = require('terser-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = merge(baseConfig, {
mode: 'production',
devtool: '#source-map',
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
test: /vendor\.js(\?.*)?$/i,
}),
],
},
});
new UglifyJsPlugin({
test: /(vendor\.js)+/i
})
]
}});

View File

@@ -1,4 +1,36 @@
[package]
exclude = [
"build/",
".git",
"js/node_modules",
"js/tests",
"js/legacy",
"js/controller",
"js/directive",
"js/filters",
"js/service",
"js/bower.json",
"js/.bowerrc",
"js/.jshintrc",
"js/Gruntfile.js",
"js/package.json",
"js/package-lock.json",
"tests",
".codecov.yml",
"composer.json",
"composer.lock",
"_config.yml",
".drone.yml",
".travis.yml",
".eslintignore",
".eslintrc.yml",
".gitignore",
"issue_template.md",
"krankerl.toml",
"Makefile",
"run-eslint.sh"
]
before_cmds = [
'make clean-build',
'make build'

View File

@@ -1,33 +0,0 @@
OC.L10N.register(
"deck",
{
"Hours" : "Uur",
"Minutes" : "Minute",
"File already exists" : "Lêer bestaan reeds",
"Personal" : "Persoonlik",
"Done" : "Gereed",
"The file was uploaded" : "Die lêer is opgelaai",
"The uploaded file exceeds the upload_max_filesize directive in php.ini" : "Die opgelaaide lêer oorskry die upload_max_filesize riglyn in php.ini",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Die opgelaaide lêer oorskry die MAX_FILE_SIZE riglyn wat in die HTML vorm gespesifiseer is",
"The file was only partially uploaded" : "Die lêer is slegs gedeeltelik op gelaai",
"No file was uploaded" : "Geen lêer is opgelaai",
"Missing a temporary folder" : "Ontbrekende tydelike gids",
"A PHP extension stopped the file upload" : "n PHP-uitbreiding het die oplaai gestaak",
"Actions" : "Aksies",
"Close" : "Sluit",
"Tags" : "Etikette",
"Group" : "Groep",
"Loading" : "Laai tans..",
"Edit" : "Wysig",
"Share" : "Deel",
"Create" : "Skep",
"Title" : "Titel",
"Cancel upload" : "Kanselleer oplaai",
"by" : "deur",
"Created:" : "Geskep:",
"Due date" : "Sperdatum",
"Description" : "Beskrywing",
"Saved" : "Bewaar",
"Settings" : "Instellings"
},
"nplurals=2; plural=(n != 1);");

View File

@@ -1,31 +0,0 @@
{ "translations": {
"Hours" : "Uur",
"Minutes" : "Minute",
"File already exists" : "Lêer bestaan reeds",
"Personal" : "Persoonlik",
"Done" : "Gereed",
"The file was uploaded" : "Die lêer is opgelaai",
"The uploaded file exceeds the upload_max_filesize directive in php.ini" : "Die opgelaaide lêer oorskry die upload_max_filesize riglyn in php.ini",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Die opgelaaide lêer oorskry die MAX_FILE_SIZE riglyn wat in die HTML vorm gespesifiseer is",
"The file was only partially uploaded" : "Die lêer is slegs gedeeltelik op gelaai",
"No file was uploaded" : "Geen lêer is opgelaai",
"Missing a temporary folder" : "Ontbrekende tydelike gids",
"A PHP extension stopped the file upload" : "n PHP-uitbreiding het die oplaai gestaak",
"Actions" : "Aksies",
"Close" : "Sluit",
"Tags" : "Etikette",
"Group" : "Groep",
"Loading" : "Laai tans..",
"Edit" : "Wysig",
"Share" : "Deel",
"Create" : "Skep",
"Title" : "Titel",
"Cancel upload" : "Kanselleer oplaai",
"by" : "deur",
"Created:" : "Geskep:",
"Due date" : "Sperdatum",
"Description" : "Beskrywing",
"Saved" : "Bewaar",
"Settings" : "Instellings"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}

View File

@@ -1,42 +0,0 @@
OC.L10N.register(
"deck",
{
"Hours" : "ساعات",
"Minutes" : "دقائق",
"File already exists" : "الملف موجود مسبقاً",
"Personal" : "شخصي",
"Finished" : "مكتملة",
"Done" : "تم",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "حجم الملف الذي تريد ترفيعه أعلى مما MAX_FILE_SIZE يسمح به في واجهة ال HTML.",
"No file was uploaded" : "لم يتم رفع أي ملف",
"Missing a temporary folder" : "المجلد المؤقت غير موجود",
"Actions" : "الإجراءات",
"Delete card" : "حذف البطاقة",
"Close" : "إغلاق",
"Sharing" : "المشاركة",
"Tags" : "الوسوم",
"Timeline" : "الخيط الزمني",
"Group" : "الفريق",
"Circle" : "حلقة",
"Loading" : "Loading",
"Edit" : "تعديل",
"Share" : "شارك",
"Update tag" : "تحديث الوسم",
"Edit tag" : "تعديل الوسم",
"Delete tag" : "حذف الوسم",
"Create" : "إنشاء",
"Status" : "الحالة",
"Title" : "العنوان",
"More actions" : "إجراءات أخرى",
"Cancel upload" : "إلغاء الرفع",
"by" : "من قبل",
"Delete attachment" : "حذف المرفق",
"Modified:" : "عُدل في :",
"Created:" : "تاريخ الإنشاء :",
"Description" : "الوصف",
"Attachments" : "المرفقات",
"Saved" : "تم الإحتفاظ به",
"Upload attachment" : "رفع المرفقات",
"Settings" : "الإعدادات"
},
"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;");

View File

@@ -1,40 +0,0 @@
{ "translations": {
"Hours" : "ساعات",
"Minutes" : "دقائق",
"File already exists" : "الملف موجود مسبقاً",
"Personal" : "شخصي",
"Finished" : "مكتملة",
"Done" : "تم",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "حجم الملف الذي تريد ترفيعه أعلى مما MAX_FILE_SIZE يسمح به في واجهة ال HTML.",
"No file was uploaded" : "لم يتم رفع أي ملف",
"Missing a temporary folder" : "المجلد المؤقت غير موجود",
"Actions" : "الإجراءات",
"Delete card" : "حذف البطاقة",
"Close" : "إغلاق",
"Sharing" : "المشاركة",
"Tags" : "الوسوم",
"Timeline" : "الخيط الزمني",
"Group" : "الفريق",
"Circle" : "حلقة",
"Loading" : "Loading",
"Edit" : "تعديل",
"Share" : "شارك",
"Update tag" : "تحديث الوسم",
"Edit tag" : "تعديل الوسم",
"Delete tag" : "حذف الوسم",
"Create" : "إنشاء",
"Status" : "الحالة",
"Title" : "العنوان",
"More actions" : "إجراءات أخرى",
"Cancel upload" : "إلغاء الرفع",
"by" : "من قبل",
"Delete attachment" : "حذف المرفق",
"Modified:" : "عُدل في :",
"Created:" : "تاريخ الإنشاء :",
"Description" : "الوصف",
"Attachments" : "المرفقات",
"Saved" : "تم الإحتفاظ به",
"Upload attachment" : "رفع المرفقات",
"Settings" : "الإعدادات"
},"pluralForm" :"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;"
}

View File

@@ -1,50 +1,30 @@
OC.L10N.register(
"deck",
{
"Delete" : "Desaniciar",
"Hours" : "Hores",
"Minutes" : "Minutos",
"File already exists" : "Yá esiste'l ficheru",
"Deck" : "Deck",
"Personal" : "Personal",
"Finished" : "Finó",
"Action needed" : "Precísase aición",
"Later" : "Más sero",
"Done" : "Fecho",
"The file was uploaded" : "Xubióse'l ficheru",
"The uploaded file exceeds the upload_max_filesize directive in php.ini" : "El ficheru xubíu perpasa la direutiva de xuba upload_max_filesize en php.ini",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "El ficheru xubíu perpasa la direutiva \"MAX_FILE_SIZE\" especificada nel formulariu HTML",
"No file was uploaded" : "Nun se xubieron fichjeros",
"Missing a temporary folder" : "Falta un direutoriu temporal",
"Could not write file to disk" : "Nun pudo escribise nel discu'l ficheru",
"A PHP extension stopped the file upload" : "Una estensión de PHP paró la xuba de ficheros",
"Deck" : "Deck",
"Submit" : "Unviar",
"Show archived cards" : "Amosar tarxetes archivaes",
"Actions" : "Aiciones",
"Close" : "Zarrar",
"Sharing" : "Compartiendo",
"Tags" : "Etiquetes",
"Select users or groups to share with" : "Esbilla usuarios o grupos colos que compartir",
"Group" : "Group",
"Circle" : "Círculu",
"No matching user or group found." : "Nun s'alcontró dengún usuariu o grupu que concasara.",
"Loading" : "Cargando",
"Edit" : "Editar",
"Share" : "Compartir",
"Edit" : "Editar",
"Manage" : "Xestionar",
"Discard share" : "Escartar compartición",
"Create" : "Crear",
"Status" : "Estáu",
"Title" : "Títulu",
"Members" : "Miembros",
"More actions" : "Más aiciones",
"Cancel upload" : "Encaboxar xuba",
"by" : "por",
"Modified:" : "Modificáu:",
"Created:" : "Creóse'l",
"Click to set" : "Primi p'afitar",
"Description" : "Descripción",
"Attachments" : "Axuntos",
"Saved" : "Guardóse",
"Settings" : "Settings"
"Saved" : "Guardóse"
},
"nplurals=2; plural=(n != 1);");

View File

@@ -1,48 +1,28 @@
{ "translations": {
"Delete" : "Desaniciar",
"Hours" : "Hores",
"Minutes" : "Minutos",
"File already exists" : "Yá esiste'l ficheru",
"Deck" : "Deck",
"Personal" : "Personal",
"Finished" : "Finó",
"Action needed" : "Precísase aición",
"Later" : "Más sero",
"Done" : "Fecho",
"The file was uploaded" : "Xubióse'l ficheru",
"The uploaded file exceeds the upload_max_filesize directive in php.ini" : "El ficheru xubíu perpasa la direutiva de xuba upload_max_filesize en php.ini",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "El ficheru xubíu perpasa la direutiva \"MAX_FILE_SIZE\" especificada nel formulariu HTML",
"No file was uploaded" : "Nun se xubieron fichjeros",
"Missing a temporary folder" : "Falta un direutoriu temporal",
"Could not write file to disk" : "Nun pudo escribise nel discu'l ficheru",
"A PHP extension stopped the file upload" : "Una estensión de PHP paró la xuba de ficheros",
"Deck" : "Deck",
"Submit" : "Unviar",
"Show archived cards" : "Amosar tarxetes archivaes",
"Actions" : "Aiciones",
"Close" : "Zarrar",
"Sharing" : "Compartiendo",
"Tags" : "Etiquetes",
"Select users or groups to share with" : "Esbilla usuarios o grupos colos que compartir",
"Group" : "Group",
"Circle" : "Círculu",
"No matching user or group found." : "Nun s'alcontró dengún usuariu o grupu que concasara.",
"Loading" : "Cargando",
"Edit" : "Editar",
"Share" : "Compartir",
"Edit" : "Editar",
"Manage" : "Xestionar",
"Discard share" : "Escartar compartición",
"Create" : "Crear",
"Status" : "Estáu",
"Title" : "Títulu",
"Members" : "Miembros",
"More actions" : "Más aiciones",
"Cancel upload" : "Encaboxar xuba",
"by" : "por",
"Modified:" : "Modificáu:",
"Created:" : "Creóse'l",
"Click to set" : "Primi p'afitar",
"Description" : "Descripción",
"Attachments" : "Axuntos",
"Saved" : "Guardóse",
"Settings" : "Settings"
"Saved" : "Guardóse"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}

View File

@@ -1,29 +0,0 @@
OC.L10N.register(
"deck",
{
"Hours" : "Saatlar",
"Minutes" : "Dəqiqələr",
"Personal" : "Şəxsi",
"Done" : "Done",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Yüklənilən faylın həcmi HTML formasinda olan MAX_FILE_SIZE direktivində təyin dilmiş həcmi aşır.",
"No file was uploaded" : "Heç bir fayl yüklənilmədi",
"Missing a temporary folder" : "Müvəqqəti qovluq çatışmır",
"Actions" : "İşlər",
"Close" : "Bağla",
"Sharing" : "Paylaşılır",
"Tags" : "Işarələr",
"Group" : "Qrup",
"Loading" : "Loading",
"Edit" : "Dəyişiklik et",
"Share" : "Paylaş",
"Create" : "Yarat",
"Title" : "Başlıq",
"Cancel upload" : "Yüklənməni dayandır",
"by" : "onunla",
"Modified:" : "Dəyişdirildi:",
"Created:" : "Yaradıldı:",
"Description" : "Açıqlanma",
"Saved" : "Saxlanıldı",
"Settings" : "Quraşdırmalar"
},
"nplurals=2; plural=(n != 1);");

View File

@@ -1,27 +0,0 @@
{ "translations": {
"Hours" : "Saatlar",
"Minutes" : "Dəqiqələr",
"Personal" : "Şəxsi",
"Done" : "Done",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Yüklənilən faylın həcmi HTML formasinda olan MAX_FILE_SIZE direktivində təyin dilmiş həcmi aşır.",
"No file was uploaded" : "Heç bir fayl yüklənilmədi",
"Missing a temporary folder" : "Müvəqqəti qovluq çatışmır",
"Actions" : "İşlər",
"Close" : "Bağla",
"Sharing" : "Paylaşılır",
"Tags" : "Işarələr",
"Group" : "Qrup",
"Loading" : "Loading",
"Edit" : "Dəyişiklik et",
"Share" : "Paylaş",
"Create" : "Yarat",
"Title" : "Başlıq",
"Cancel upload" : "Yüklənməni dayandır",
"by" : "onunla",
"Modified:" : "Dəyişdirildi:",
"Created:" : "Yaradıldı:",
"Description" : "Açıqlanma",
"Saved" : "Saxlanıldı",
"Settings" : "Quraşdırmalar"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}

View File

@@ -1,56 +0,0 @@
OC.L10N.register(
"deck",
{
"Hours" : "Часове",
"Minutes" : "Минути",
"File already exists" : "Файлът вече съществува",
"Personal" : "Лични",
"Finished" : "Готово",
"To review" : "За преглед",
"Action needed" : "Необходимо е действие",
"Later" : "По-късно",
"Done" : "Готово",
"The file was uploaded" : "Файлът е качен",
"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" : "Липсва временна папка",
"Submit" : "Изпращане",
"Actions" : "Действия",
"Close" : "Затваряне",
"Sharing" : "Споделяне",
"Tags" : "Етикети",
"Select users or groups to share with" : "Споделяне с потребители или групи",
"Group" : "Група",
"Circle" : "Кръг",
"No matching user or group found." : "Не са намерени съвпадащи потребители или групи",
"Loading" : "Зареждане",
"Edit" : "Редакция",
"Share" : "Сподели",
"Manage" : "Управление",
"Discard share" : "Отхвърляне на споделяне",
"Delete tag" : "Изтрий таг",
"Create" : "Създай",
"Create a new tag" : "Създаване на нов етикет",
"Status" : "Състояние",
"Title" : "Име",
"Members" : "Членове",
"More actions" : "Още действия",
"Cancel upload" : "Откажи качването",
"by" : "от",
"Modified:" : "Променена:",
"Created:" : "Създадена:",
"Choose a tag" : "Изберете етикет",
"Add a tag" : "Етикети",
"Select tags" : "Изберете етикети",
"Assign users" : "Зачисляване на потребител",
"Choose a user to assign" : "Изберете потребител на който да бъде зачислена",
"Due date" : "Крайна дата",
"Remove due date" : "Премахни крайната дата",
"Description" : "Описание",
"Attachments" : "Прикачени файлове",
"Saved" : "Запазено",
"Upload attachment" : "Качване",
"Settings" : "Настройки"
},
"nplurals=2; plural=(n != 1);");

View File

@@ -1,54 +0,0 @@
{ "translations": {
"Hours" : "Часове",
"Minutes" : "Минути",
"File already exists" : "Файлът вече съществува",
"Personal" : "Лични",
"Finished" : "Готово",
"To review" : "За преглед",
"Action needed" : "Необходимо е действие",
"Later" : "По-късно",
"Done" : "Готово",
"The file was uploaded" : "Файлът е качен",
"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" : "Липсва временна папка",
"Submit" : "Изпращане",
"Actions" : "Действия",
"Close" : "Затваряне",
"Sharing" : "Споделяне",
"Tags" : "Етикети",
"Select users or groups to share with" : "Споделяне с потребители или групи",
"Group" : "Група",
"Circle" : "Кръг",
"No matching user or group found." : "Не са намерени съвпадащи потребители или групи",
"Loading" : "Зареждане",
"Edit" : "Редакция",
"Share" : "Сподели",
"Manage" : "Управление",
"Discard share" : "Отхвърляне на споделяне",
"Delete tag" : "Изтрий таг",
"Create" : "Създай",
"Create a new tag" : "Създаване на нов етикет",
"Status" : "Състояние",
"Title" : "Име",
"Members" : "Членове",
"More actions" : "Още действия",
"Cancel upload" : "Откажи качването",
"by" : "от",
"Modified:" : "Променена:",
"Created:" : "Създадена:",
"Choose a tag" : "Изберете етикет",
"Add a tag" : "Етикети",
"Select tags" : "Изберете етикети",
"Assign users" : "Зачисляване на потребител",
"Choose a user to assign" : "Изберете потребител на който да бъде зачислена",
"Due date" : "Крайна дата",
"Remove due date" : "Премахни крайната дата",
"Description" : "Описание",
"Attachments" : "Прикачени файлове",
"Saved" : "Запазено",
"Upload attachment" : "Качване",
"Settings" : "Настройки"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}

View File

@@ -1,29 +0,0 @@
OC.L10N.register(
"deck",
{
"Hours" : "ঘন্টা",
"Minutes" : "মিনিট",
"Personal" : "ব্যক্তিগত",
"Done" : "Done",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "আপলোড করা ফাইলটি HTML ফর্মে উল্লিখিত MAX_FILE_SIZE নির্ধারিত ফাইলের সর্বোচ্চ আকার অতিক্রম করতে চলেছে ",
"No file was uploaded" : "কোন ফাইল আপলোড করা হয় নি",
"Missing a temporary folder" : "অস্থায়ী ফোল্ডারটি হারানো গিয়েছে",
"Actions" : "পদক্ষেপসমূহ",
"Close" : "বন্ধ",
"Sharing" : "ভাগাভাগিরত",
"Tags" : "ট্যাগ",
"Group" : "গোষ্ঠীসমূহ",
"Loading" : "Loading",
"Edit" : "সম্পাদনা",
"Share" : "ভাগাভাগি কর",
"Create" : "তৈরী কর",
"Title" : "শিরোনাম",
"Cancel upload" : "আপলোড বাতিল কর",
"by" : "কর্তৃক",
"Modified:" : "পরিবর্তিতঃ",
"Created:" : "তৈরীর নির্ঘন্টঃ",
"Description" : "বিবরণ",
"Saved" : "সংরক্ষণ করা হলো",
"Settings" : "সেটিংস"
},
"nplurals=2; plural=(n != 1);");

View File

@@ -1,27 +0,0 @@
{ "translations": {
"Hours" : "ঘন্টা",
"Minutes" : "মিনিট",
"Personal" : "ব্যক্তিগত",
"Done" : "Done",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "আপলোড করা ফাইলটি HTML ফর্মে উল্লিখিত MAX_FILE_SIZE নির্ধারিত ফাইলের সর্বোচ্চ আকার অতিক্রম করতে চলেছে ",
"No file was uploaded" : "কোন ফাইল আপলোড করা হয় নি",
"Missing a temporary folder" : "অস্থায়ী ফোল্ডারটি হারানো গিয়েছে",
"Actions" : "পদক্ষেপসমূহ",
"Close" : "বন্ধ",
"Sharing" : "ভাগাভাগিরত",
"Tags" : "ট্যাগ",
"Group" : "গোষ্ঠীসমূহ",
"Loading" : "Loading",
"Edit" : "সম্পাদনা",
"Share" : "ভাগাভাগি কর",
"Create" : "তৈরী কর",
"Title" : "শিরোনাম",
"Cancel upload" : "আপলোড বাতিল কর",
"by" : "কর্তৃক",
"Modified:" : "পরিবর্তিতঃ",
"Created:" : "তৈরীর নির্ঘন্টঃ",
"Description" : "বিবরণ",
"Saved" : "সংরক্ষণ করা হলো",
"Settings" : "সেটিংস"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}

View File

@@ -1,30 +0,0 @@
OC.L10N.register(
"deck",
{
"Hours" : "Sati",
"Minutes" : "Minute",
"Maximum file size of {size} exceeded" : "Maksimalna veličina datoteke prekoračena",
"Personal" : "Osobno",
"Done" : "Done",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Učitana datoteka premašuje maksimalnu dopuštenu veličinu datoteke MAX_FILE_SIZE navedenu u HTML formi",
"No file was uploaded" : "Nijedna datoteka nije učitana.",
"Missing a temporary folder" : "Nedostaje privremeni direktorij.",
"Actions" : "Radnje",
"Close" : "Zatvori",
"Sharing" : "Dijeljenje",
"Group" : "Grupa",
"Circle" : "Krug",
"Loading" : "Loading",
"Edit" : "Izmjeni",
"Share" : "Podjeli",
"Create" : "Ustvari",
"Status" : "Status",
"Title" : "Naslov",
"Members" : "Članovi",
"Cancel upload" : "Prekini učitavanje",
"by" : "od strane",
"Description" : "Opis",
"Saved" : "Spremljeno",
"Settings" : "Podešavanje"
},
"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

@@ -1,28 +0,0 @@
{ "translations": {
"Hours" : "Sati",
"Minutes" : "Minute",
"Maximum file size of {size} exceeded" : "Maksimalna veličina datoteke prekoračena",
"Personal" : "Osobno",
"Done" : "Done",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "Učitana datoteka premašuje maksimalnu dopuštenu veličinu datoteke MAX_FILE_SIZE navedenu u HTML formi",
"No file was uploaded" : "Nijedna datoteka nije učitana.",
"Missing a temporary folder" : "Nedostaje privremeni direktorij.",
"Actions" : "Radnje",
"Close" : "Zatvori",
"Sharing" : "Dijeljenje",
"Group" : "Grupa",
"Circle" : "Krug",
"Loading" : "Loading",
"Edit" : "Izmjeni",
"Share" : "Podjeli",
"Create" : "Ustvari",
"Status" : "Status",
"Title" : "Naslov",
"Members" : "Članovi",
"Cancel upload" : "Prekini učitavanje",
"by" : "od strane",
"Description" : "Opis",
"Saved" : "Spremljeno",
"Settings" : "Podešavanje"
},"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

@@ -1,201 +1,54 @@
OC.L10N.register(
"deck",
{
"Please provide a content for your comment." : "Proporcioneu un contingut per al vostre comentari.",
"Posting the comment failed." : "No s'ha pogut publicar el comentari.",
"The comment has been deleted" : "S'ha suprimit el comentari",
"The associated stack is deleted as well, it will be restored as well." : "La pila associada també se suprimeix, també es restaurarà.",
"Restore associated stack" : "Restaura la pila associada",
"Remove user from card" : "Suprimeix l'usuari de la targeta",
"Delete" : "Esborra",
"Remove user from card" : "Esborra usuari de la targeta",
"Hours" : "Hores",
"Minutes" : "Minuts",
"Link to a board" : "Enllaça a un tauler",
"Maximum file size of {size} exceeded" : "S'ha superat la mida màxima per fitxer de {size}",
"File already exists" : "El fitxer ja existeix",
"You have created a new board {board}" : "Heu creat el nou tauler {board}",
"{user} has created a new board {board}" : "{user} ha creat el nou tauler {board}",
"You have deleted the board {board}" : "Heu suprimit el tauler {board}",
"{user} has deleted the board {board}" : "{user} ha suprimit el tauler {board}",
"You have restored the board {board}" : "Heu restaurat el tauler {board}",
"{user} has restored the board {board}" : "{user} ha restaurat el tauler {board}",
"You have shared the board {board} with {acl}" : "Heu compartit el tauler {board} amb {acl}",
"{user} has shared the board {board} with {acl}" : "{user} ha compartit el tauler {board} amb {acl}",
"You have removed {acl} from the board {board}" : "Heu suprimit {acl} del tauler {board}",
"{user} has removed {acl} from the board {board}" : "{user} ha suprimit {acl} del tauler {board}",
"You have renamed the board {before} to {board}" : "Heu reanomenat el tauler {before} a {board}",
"{user} has renamed the board {before} to {board}" : "{user} ha reanomenat el tauler {before} a {board}",
"You have archived the board {board}" : "Heu arxivat el tauler {board}",
"{user} has archived the board {before}" : "{user} ha arxivat el tauler {before}",
"You have unarchived the board {board}" : "Heu desarxivat el tauler {board}",
"{user} has unarchived the board {before}" : "{user} ha desarxivat el tauler {before}",
"You have created a new stack {stack} on board {board}" : "Heu creat una nova pila {stack} al tauler {board}",
"{user} has created a new stack {stack} on board {board}" : "{user} ha creat una nova pila {stack} al tauler {board}",
"You have renamed stack {before} to {stack} on board {board}" : "Heu reanomenat la pila {before} a {stack} al tauler {board}",
"{user} has renamed stack {before} to {stack} on board {board}" : "{user} ha reanomenat la pila {before} a {stack} al tauler {board}",
"You have deleted stack {stack} on board {board}" : "Heu suprimit la pila {stack} al tauler {board}",
"{user} has deleted stack {stack} on board {board}" : "{user} ha suprimit la pila {stack} al tauler {board}",
"You have created card {card} in stack {stack} on board {board}" : "Heu creat la targeta {card} a la pila {stack} al tauler {board}",
"{user} has created card {card} in stack {stack} on board {board}" : "{user} ha creat la targeta {card} a la pila {stack} al tauler {board}",
"You have deleted card {card} in stack {stack} on board {board}" : "Heu suprimit la targeta {card} a la pila {stack} al tauler {board}",
"{user} has deleted card {card} in stack {stack} on board {board}" : "{user} ha suprimit la targeta {card} a la pila {stack} al tauler {board}",
"You have renamed the card {before} to {card}" : "Heu reanomenat la targeta {before} a {card}",
"{user} has renamed the card {before} to {card}" : "{user} ha reanomenat la targeta {before} a {card}",
"You have added a description to card {card} in stack {stack} on board {board}" : "Heu afegit una descripció a la targeta {card} a la pila {stack} al tauler {board}",
"{user} has added a description to card {card} in stack {stack} on board {board}" : "{user} ha afegit una descripció a la targeta {card} a la pila {stack} al tauler {board}",
"You have updated the description of card {card} in stack {stack} on board {board}" : "Heu actualitzat la descripció de la targeta {card} a la pila {stack} al tauler {board}",
"{user} has updated the description of the card {card} in stack {stack} on board {board}" : "{user} ha actualitzat la descripció de la targeta {card} a la pila {stack} al tauler {board}",
"You have archived card {card} in stack {stack} on board {board}" : "Heu arxivat la targeta {card} a la pila {stack} al tauler {board}",
"{user} has archived card {card} in stack {stack} on board {board}" : "{user} ha arxivat la targeta {card} a la pila {stack} al tauler {board}",
"You have unarchived card {card} in stack {stack} on board {board}" : "Heu desarxivat la targeta {card} a la pila {stack} al tauler {board}",
"{user} has unarchived card {card} in stack {stack} on board {board}" : "{user} ha arxivat la targeta {card} a la pila {stack} al tauler {board}",
"You have removed the due date of card {card}" : "Heu suprimit la data de venciment de la targeta {targeta}",
"{user} has removed the due date of card {card}" : "{user} ha suprimit la data de venciment de la targeta {targeta}",
"You have set the due date of card {card} to {after}" : "Heu establert la data de venciment de la targeta {card} a {after}",
"{user} has set the due date of card {card} to {after}" : "{user} ha establert la data de venciment de la targeta {card} a {after}",
"You have updated the due date of card {card} to {after}" : "Heu actualitzat la data de venciment de la targeta {card} a {after}",
"{user} has updated the due date of card {card} to {after}" : "{user} ha actualitzat la data de venciment de la targeta {card} a {after}",
"You have added the tag {label} to card {card} in stack {stack} on board {board}" : "Heu afegit l'etiqueta {label} a la targeta {card} a la pila {stack} al tauler {board}",
"{user} has added the tag {label} to card {card} in stack {stack} on board {board}" : "{user} ha afegit l'etiqueta {label} a la targeta {card} a la pila {stack} al tauler {board}",
"You have removed the tag {label} from card {card} in stack {stack} on board {board}" : "Heu suprimit l'etiqueta {label} de la targeta {card} a la pila {stack} al tauler {board}",
"{user} has removed the tag {label} from card {card} in stack {stack} on board {board}" : "{user} ha suprimit l'etiqueta {label} de la targeta {card} a la pila {stack} al tauler {board}",
"You have assigned {assigneduser} to card {card} on board {board}" : "Heu assignat a {assigneduser} a la targeta {card} al tauler {board}",
"{user} has assigned {assigneduser} to card {card} on board {board}" : "{user} ha assignat a {assigneduser} la targeta {card} del tauler {board}",
"You have unassigned {assigneduser} from card {card} on board {board}" : "Heu desassignat {assigneduser} de la targeta {card} al tauler {board}",
"{user} has unassigned {assigneduser} from card {card} on board {board}" : "{user} ha desassignat {assigneduser} de la targeta {card} al tauler {board}",
"You have moved the card {card} from stack {stackBefore} to {stack}" : "Heu mogut la targeta {card} de la pila {stackBefore} a {stack}",
"{user} has moved the card {card} from stack {stackBefore} to {stack}" : "{user} ha mogut la targeta {card} de la pila {stackBefore} a {stack}",
"You have added the attachment {attachment} to card {card}" : "Heu afegit l'adjunt {attachment} a la targeta {card}",
"{user} has added the attachment {attachment} to card {card}" : "{user} ha afegit l'adjunt {attachment} a la targeta {card}",
"You have updated the attachment {attachment} on card {card}" : "Heu actualitzat l'adjunt {attachment} a la targeta {card}",
"You have deleted the attachment {attachment} from card {card}" : "Heu suprimit l'adjunt {attachment} de la targeta {card}",
"You have restored the attachment {attachment} to card {card}" : "Heu restaurat l'adjunt {attachment} a la targeta {card}",
"{user} has restored the attachment {attachment} to card {card}" : "{user} ha restaurat l'adjunt {attachment} a la targeta {card}",
"You have commented on card {card}" : "Heu comentat la targeta {card}",
"{user} has commented on card {card}" : "{user} ha comentat la targeta {card}",
"A <strong>card description</strong> inside the Deck app has been changed" : "S'ha canviat una <strong>descripció de targeta</strong> a l'aplicació Tauler",
"Deck" : "Tauler",
"Changes in the <strong>Deck app</strong>" : "Hi ha canvis a l'<strong>aplicació Tauler</strong>",
"A <strong>comment</strong> was created on a card" : "S'ha afegit un <strong>comentari</strong> a una targeta",
"Personal" : "Personal",
"The card \"%s\" on \"%s\" has been assigned to you by %s." : "La targeta \"%s\" sobre \"%s\" se us ha assignat per %s.",
"{user} has assigned the card \"%s\" on \"%s\" to you." : "{user} us ha assignat la targeta \"%s\" sobre \"%s\".",
"The card \"%s\" on \"%s\" has reached its due date." : "La targeta \"%s\" sobre \"%s\" ha assolit la seva data de venciment.",
"%s has mentioned you in a comment on \"%s\"." : "%s us ha anomenat en un comentari sobre \"%s\".",
"{user} has mentioned you in a comment on \"%s\"." : "{user} us ha anomenat en un comentari sobre \"%s\".",
"The board \"%s\" has been shared with you by %s." : "El tauler \"%s\" se us ha compartit per %s.",
"{user} has shared the board %s with you." : "{user} us ha compartit el tauler %s.",
"No data was provided to create an attachment." : "No sha proporcionat cap dada per crear un fitxer adjunt.",
"Finished" : "Acabat",
"To review" : "Per revisar",
"Action needed" : "Acció necessària",
"Action needed" : "Acció necessaria",
"Later" : "Més tard",
"To do" : "Pendents",
"Doing" : "En procés",
"Done" : "Finalitzades",
"Example Task 3" : "Tasca d'exemple 3",
"Example Task 2" : "Tasca d'exemple 2",
"Example Task 1" : "Tasca d'exemple 1",
"The file was uploaded" : "S'ha carregat el fitxer",
"The uploaded file exceeds the upload_max_filesize directive in php.ini" : "El fitxer carregat excedeix la directiva upload_max_filesize dins de php.ini",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "El fitxer carregat excedeix la directiva MAX_FILE_SIZE que hi ha especificada al formulari d'HTML",
"The file was only partially uploaded" : "El fitxer s'ha carregat només parcialment",
"No file was uploaded" : "No s'ha carregat cap fitxer",
"Missing a temporary folder" : "Falta una carpeta temporal",
"Could not write file to disk" : "No sha pogut escriure el fitxer al disc",
"A PHP extension stopped the file upload" : "Una extensió del PHP ha aturat la carregada del fitxer",
"No file uploaded or file size exceeds maximum of %s" : "No s'ha carregat cap fitxer o la mida del fitxer sobrepassa el màxim de %s",
"A kanban style project and personal management tool for Nextcloud" : "Un projecte destil kanban i una eina de gestió personal per a Nextcloud",
"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" : "Tauler és una eina d'organització a l'estil kanban dirigida a la planificació personal i a l'organització de projectes per equips integrada a Nextcloud.\n\n\n- 📥 Afegiu les tasques en targetes i poseu-les en ordre\n- 📄 Apunteu notes addicionals en markdown\n- 🔖 Assigneu etiquetes per una organització encara millor\n- 👥 Compartiu amb el vostre equip, família o amics\n- 📎 Adjunteu fitxers i encasteu-los en la descripció en markdown\n- 💬 Debateu amb el vostre equip fent servir comentaris\n- ⚡ Mantingueu el seguiment de canvis al flux d'activitat\n- 🚀 Tingueu el vostre projecte organitzat",
"Select the board to link to a project" : "Selecciona el tauler per enllaçar a un projecte",
"Select board" : "Selecciona un tauler",
"Deck" : "Coberta",
"Add a new stack" : "Afegeix una nova pila",
"Submit" : "Envia",
"Show archived cards" : "Mostra les targetes arxivades",
"Hide archived cards" : "Amaga les targetes arxivades",
"Toggle compact mode" : "Commuta el mode compacte",
"Show board details" : "Mostra els detalls del tauler",
"All Boards" : "Tots els Taulers",
"Board details" : "Detalls de la junta",
"All Boards" : "Totes les juntes",
"Archived boards" : "Taulers arxivats",
"Share board" : "Comparteix tauler",
"Archived cards" : "Targetes arxivades",
"Actions" : "Accions",
"Drop your files here to upload it to the card" : "Deixeu anar els fitxers aquí per carregar-los a la targeta",
"Assign card to me" : "Assigna'm la targeta",
"Unassign card from me" : "Desassigna'm la targeta",
"Archive card" : "Arxiva la targeta",
"Unarchive card" : "Desarxiva targeta",
"Delete card" : "Suprimeix targeta",
"Enter a card title" : "Introduïu un títol a la targeta",
"Add card" : "Afegeix una targeta",
"Close" : "Tanca",
"Sharing" : "S'està compartint",
"Archive" : "Arxiu",
"Unarchive" : "Desbloquejar",
"Enter a card title" : "Introduïu un títol de la targeta",
"Sharing" : "Compartir",
"Tags" : "Etiquetes",
"Deleted items" : "Elements suprimits",
"Timeline" : "Línia de temps",
"Select users or groups to share with" : "Seleccioneu usuaris o grups amb els qui compartir",
"Group" : "Grup",
"Circle" : "Cercle",
"Select users or groups to share with" : "Seleccioneu usuaris o grups per compartir",
"No matching user or group found." : "No s'ha trobat cap usuari o grup coincident.",
"Loading" : "S'està carregant",
"Edit" : "Edita",
"Share" : "Comparteix",
"Manage" : "Gestiona",
"Edit" : "Edita",
"Manage" : "Gestor",
"Discard share" : "Descarta la compartició",
"Sharing has been disabled for your account." : "La compartició s'ha desactivat per al vostre compte.",
"Update tag" : "Actualitza etiqueta",
"Edit tag" : "Edita etiqueta",
"Delete tag" : "Suprimeix etiqueta",
"Create" : "Crea",
"Create a new tag" : "Crea una nova etiqueta",
"Deleted stacks" : "Piles suprimides",
"Deleted cards" : "Targetes suprimides",
"Status" : "Estat",
"No archived boards to display" : "No hi ha cap tauler arxivat per mostrar",
"No shared boards to display" : "No hi ha cap tauler compartit per mostrar",
"Title" : "Títol",
"Title" : "Title",
"Members" : "Membres",
"More actions" : "Més accions",
"Edit board" : "Edita el tauler",
"Archive board" : "Arxiva tauler",
"Unarchive board" : "Desarxiva tauler",
"Delete board" : "Suprimeix tauler",
"Update board" : "Actualitza tauler",
"Reset board" : "Reinicialitza tauler",
"Undo board deletion - Otherwise the board will be deleted during the next cronjob run." : "Desfeu la supressió del tauler - Altrament el tauler serà suprimit durant la següent execució de treball del cron.",
"Create new board" : "Crea un nou tauler",
"New board title" : "Títol del nou tauler",
"Create board" : "Crea tauler",
"Select an attachment" : "Selecciona un adjunt",
"Cancel upload" : "Cancel·la la càrrega",
"Archive board" : "Arxiu",
"Unarchive board" : "Desbloquejar",
"Delete board" : "Eliminat",
"Create new board" : "Crear nova",
"New board title" : "Nou títol",
"by" : "per",
"Undo file deletion - Otherwise the file will be deleted during the next cronjob run." : "Desfeu la supressió del fitxer - Altrament el fitxer serà suprimit durant la següent execució de treball del cron.",
"Undo file deletion" : "Desfés la supressió del fitxer",
"Insert the file into the description" : "Insereix el fitxer a la descripció",
"Delete attachment" : "Suprimeix l'adjunt",
"Modified:" : "Modificat:",
"Created:" : "Creat:",
"Choose a tag" : "Trieu una etiqueta",
"Add a tag" : "Afegeix una etiqueta",
"Select tags" : "Selecciona etiquetes",
"Assign users" : "Assigna usuaris",
"Choose a user to assign" : "Tria un usuari per assignar",
"Assign this card to a user" : "Assigna aquesta targeta a un usuari",
"Due date" : "Per la data",
"Click to set" : "Feu clic per configurar",
"Remove due date" : "Elimina la data de venciment",
"Description" : "Descripció",
"Attachments" : "Adjunts",
"Saved" : "Desat",
"Unsaved changes" : "Canvis no desats",
"Insert attachment" : "Insereix l'adjunt",
"Formatting help" : "Format d'ajuda",
"Upload attachment" : "Carrega l'adjunt",
"Add a card description…" : "Afegeix una descripció de la targeta ...",
"Shared boards" : "Taulers compartits",
"Move board to archive" : "Mou tauler a l'arxiu",
"Create a new board" : "Crea un nou tauler",
"Settings" : "Paràmetres",
"Limit deck to groups" : "Limita el tauler per grups",
"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." : "Limitant el Tauler bloquejarà la creació de taulers als usuaris que no són part d'aquests grups. Els usuaris podran seguir treballant en els taulers que hagin estat compartits amb ells."
"Move board to archive" : "Mou la placa a l'arxiu",
"Create a new board" : "Crear nova"
},
"nplurals=2; plural=(n != 1);");

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