Compare commits

..

50 Commits

Author SHA1 Message Date
Julius Härtl
105ffd597b Bump version to 0.3.1
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-07 17:46:16 +01:00
Julius Härtl
ae9d02121b Test stable-0.3 against NC13
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-07 17:36:54 +01:00
Nextcloud bot
61ff25106b [tx-robot] updated from transifex 2018-03-07 17:34:59 +01:00
Nextcloud bot
c449967382 [tx-robot] updated from transifex 2018-03-07 17:34:51 +01:00
Nextcloud bot
b6ad790006 [tx-robot] updated from transifex 2018-03-07 17:34:42 +01:00
Julius Härtl
f8ec7202c8 Fix translations of the delete confirmation
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-07 17:34:32 +01:00
Julius Härtl
a0ffc3a47f Make 0.3.1-rc1
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-02 19:33:02 +01:00
Julius Härtl
bb3b31f38f No typehinting in deck yet
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-02 19:31:43 +01:00
Julius Härtl
cae999ec80 Fix tests
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-02 19:31:43 +01:00
Julius Härtl
9273d15865 Update view when removing sharees and track by correct property
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-02 19:31:43 +01:00
Julius Härtl
5d40ecad25 Remove deleted users from assignments
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-02 19:31:42 +01:00
Nextcloud bot
5e4b3b9218 [tx-robot] updated from transifex 2018-03-02 18:01:21 +01:00
Nextcloud bot
9f154e6ab1 [tx-robot] updated from transifex 2018-03-02 18:01:14 +01:00
Julius Härtl
cdb3193d9c Disable integration tests for now
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-02 17:59:24 +01:00
Julius Härtl
0200179a76 Do not overwrite emtpy values
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-02 17:59:24 +01:00
Julius Härtl
b1863980d6 Track by correct property
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-02 17:59:23 +01:00
Julius Härtl
087d60776d Only return updated properties not resolvable ones
fixes #406

Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-02 17:59:23 +01:00
Julius Härtl
e859c6aa56 Fix popover position of assigned users
fixes #385

Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-02 17:59:23 +01:00
Julius Härtl
6a098dd981 Add confirmation dialog to delete options
this is only a temporary solution but it will probably safe some users from deleting their data by accident

Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-03-02 17:58:09 +01:00
Julius Härtl
7b47414a54 Add changes to CHANGELOG.md
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-02-25 11:07:17 +01:00
Julius Härtl
07ec3fd7d0 Add 0.3.1 version/dependencies
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-02-25 11:02:25 +01:00
Nextcloud bot
0af3c87d91 [tx-robot] updated from transifex 2018-02-25 11:01:10 +01:00
Nextcloud bot
c789a6356a [tx-robot] updated from transifex 2018-02-25 11:01:10 +01:00
Nextcloud bot
09c3f4e46d [tx-robot] updated from transifex 2018-02-25 11:01:09 +01:00
Nextcloud bot
a72a11584d [tx-robot] updated from transifex 2018-02-25 11:01:09 +01:00
Nextcloud bot
c850e8b1c2 [tx-robot] updated from transifex 2018-02-25 11:01:08 +01:00
Nextcloud bot
6b06f4e5e8 [tx-robot] updated from transifex 2018-02-25 11:01:08 +01:00
Nextcloud bot
52db1dc37c [tx-robot] updated from transifex 2018-02-25 11:01:07 +01:00
Nextcloud bot
8837d89503 [tx-robot] updated from transifex 2018-02-25 11:01:07 +01:00
Nextcloud bot
640db050bd [tx-robot] updated from transifex 2018-02-25 11:01:06 +01:00
Nextcloud bot
0c4ae1b913 [tx-robot] updated from transifex 2018-02-25 11:01:06 +01:00
Nextcloud bot
4b5ac63d2b [tx-robot] updated from transifex 2018-02-25 11:01:05 +01:00
Nextcloud bot
1a9c5225e1 [tx-robot] updated from transifex 2018-02-25 11:01:05 +01:00
Nextcloud bot
b74916d1f2 [tx-robot] updated from transifex 2018-02-25 11:01:04 +01:00
Nextcloud bot
86bcedd3ca [tx-robot] updated from transifex 2018-02-25 11:01:04 +01:00
Nextcloud bot
e998c3d927 [tx-robot] updated from transifex 2018-02-25 11:01:04 +01:00
Nextcloud bot
43241921e5 [tx-robot] updated from transifex 2018-02-25 11:01:03 +01:00
Nextcloud bot
13cf7fe7f2 [tx-robot] updated from transifex 2018-02-25 11:01:02 +01:00
Julius Härtl
475ecc3c2a Force limit of users that do not match exactly
fixes #422

Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-02-25 10:57:50 +01:00
Julius Härtl
c1425f7a4b Update user list when acl is changed
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-02-25 10:57:20 +01:00
Julius Härtl
111ff4ae7f Use actual acl list for assigning users
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-02-25 10:57:20 +01:00
Julius Härtl
b7ac7067ff Fix bug when user selection was staying hidden
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-02-25 10:57:20 +01:00
Julius Härtl
3d634ffdcb Only digest on card update not for every interval
and add various track by statements

Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-02-25 10:54:56 +01:00
Julius Härtl
30eeaf8ffd Add Repo as homepage (fixes #389)
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-02-25 10:53:45 +01:00
Julius Härtl
6594a3f5da Merge branch 'fix-app-compatibility' into stable-0.3 2018-02-25 10:53:16 +01:00
Julius Härtl
58df42ea9c Bump drone images (#391)
* Bump drone images

Signed-off-by: Julius Härtl <jus@bitgrid.net>

* Use travis for codecov

Signed-off-by: Julius Härtl <jus@bitgrid.net>

* Just test against master in travis

Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-01-13 12:29:29 +01:00
cloud2018
169175ab55 translate attributes in templates/part.card.php
translate attributes title and placeholder in class "section-content card-details-assign-users" in templates/part.card.php
2018-01-13 12:28:47 +01:00
Julius Härtl
f2b94f0933 Just test against master in travis
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-01-13 11:48:21 +01:00
Julius Härtl
2fece55b0e Use travis for codecov
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-01-13 11:37:48 +01:00
Julius Härtl
f34fae640d Bump drone images
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2018-01-13 10:39:40 +01:00
356 changed files with 3027 additions and 36022 deletions

View File

@@ -1,92 +1,113 @@
kind: pipeline
name: checkers
steps:
- name: compatibility
image: nextcloudci/php7.1:php7.1-16
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.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/ .
- name: 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/ .
- 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/ .
trigger:
branch:
- master
- stable*
event:
- pull_request
- push
---
kind: pipeline
name: unit-php7.1
steps:
- name: php7.1
image: nextcloudci/php7.1:php7.1-16
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
- APP_NAME=deck
- CORE_BRANCH=stable13
- 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
- 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
signed-off-check:
image: nextcloudci/php7.0:php7.0-17
environment:
- APP_NAME=deck
- CORE_BRANCH=stable13
- DB=sqlite
commands:
- wget https://raw.githubusercontent.com/nextcloud/server/master/build/signed-off-checker.php
- php ./signed-off-checker.php
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
- 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.2
steps:
- name: php7.2
- ./vendor/bin/parallel-lint --exclude ./vendor/ .
when:
matrix:
TESTS: syntax-php5.6
syntax-php7.0:
image: nextcloudci/php7.0:php7.0-17
environment:
- APP_NAME=deck
- CORE_BRANCH=stable13
- 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=stable13
- 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
- APP_NAME=deck
- CORE_BRANCH=stable13
- 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=stable13
- DB=sqlite
commands:
# Pre-setup steps
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
@@ -94,26 +115,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-2
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=stable13
- DB=sqlite
commands:
# Pre-setup steps
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
@@ -121,26 +134,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.1:php7.1-16
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=stable13
- 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=stable13
- DB=sqlite
commands:
# Pre-setup steps
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
@@ -149,32 +171,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
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
- apk add --no-cache git
- cd js
- npm install --deps
- ./node_modules/.bin/bower --allow-root install
when:
matrix:
TESTS: jsbuild
matrix:
include:
- TESTS: check-app-compatbility
- 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

@@ -1,6 +1,5 @@
/js/tests/*
/js/vendor/*
/js/legacy/*
/js/node_modules/*
/js/public/*
/karma.conf.js

View File

@@ -1,15 +1,14 @@
root: true
extends:
- eslint:recommended
env:
browser: true
amd: true
es6: true
globals:
global: false
app: false
angular: false
$: false
@@ -22,10 +21,6 @@ globals:
Clipboard: false
oc_defaults: false
parserOptions:
ecmaVersion: 6
sourceType: "module"
rules:
curly: error
eqeqeq: ["error", "smart"]
@@ -34,7 +29,6 @@ rules:
no-fallthrough: error
no-mixed-spaces-and-tabs: error
no-unused-vars: warn
no-useless-escape: warn
no-use-before-define: error
semi: ["error", "always"]
indent:

View File

@@ -1,17 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

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.

5
.gitignore vendored
View File

@@ -1,13 +1,10 @@
js/node_modules/*
js/vendor/
js/public/
js/build/
js/package-lock.json
build/
css/style.css
css/vendor.css
tests/integration/vendor/
tests/integration/composer.lock
vendor/
*.lock
\.idea/

View File

@@ -2,17 +2,17 @@ language: php
services:
- mysql
php:
- 7.0
- 7.1
- 7.2
- 7.3
env:
- CORE_BRANCH=master DB=mysql
- CORE_BRANCH=stable13 DB=mysql
before_install:
- wget https://phar.phpunit.de/phpunit-6.5.phar
- chmod +x phpunit-6.5.phar
- wget https://phar.phpunit.de/phpunit-5.7.phar
- chmod +x phpunit-5.7.phar
- mkdir bin
- mv phpunit-6.5.phar bin/phpunit
- 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
@@ -24,7 +24,6 @@ before_script:
- cd apps/deck
script:
- composer install
- make test-unit
after_success:

View File

@@ -1,145 +1,17 @@
# Changelog
All notable changes to this project will be documented in this file.
## 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
## 0.3.1 - 2018-03-07
### Fixed
- Bump security related dependencies
- Fix performance issue with large LDAP instances
- Fix assigning users to newly created boards
- Various performance improvements
- Fix some untranslated strings (@cloud2018)
- Add confirmation dialog to delete options
- Fix issues with assigned users list when saving a card
- Delete assignments if users get removed
## 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
### 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
### Fixed
- Accesibility improvements
- Don't allow empty card titles
- Improved checkbox handling in markdown
## 0.4.0 - 2018-07-11
### Added
- Attach files to cards
- Embed attachments into the card description
- Color picker to use any color value for board and labels
- Support for checkboxes inside the description
- occ command to export user data as JSON
### Fixed
- Improve frontend data management
- Fix bug the user list being empty on some occasions
## 0.3.0 - 2018-01-12

View File

@@ -12,30 +12,26 @@ sign_dir=$(build_dir)/sign
cert_dir=$(HOME)/.nextcloud/certificates
default: package
default: build
clean-build:
rm -rf $(build_dir)
clean-dist:
rm -rf js/node_modules
rm -rf js/vendor
install-deps: install-deps-js
composer install
install-deps:
cd js && npm install --deps
cd js && ./node_modules/.bin/bower install
install-deps-js:
cd js && npm install
build: build-js
build: install-deps build-js
build-js: install-deps-js
cd js && npm run build
build-js-dev: install-deps
cd js && npm run dev
build-js: install-deps
cd js && ./node_modules/.bin/grunt build
watch:
cd js && npm run watch
cd js && ./node_modules/.bin/grunt watch
# appstore: clean install-deps
appstore: clean-build build
@@ -98,5 +94,3 @@ test-integration:
test-js: install-deps
cd js && run test
package:
krankerl package

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) [![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/58ad558f4ca76f004ed475b3/badge.svg?style=flat)](https://www.versioneye.com/user/projects/58ad558f4ca76f004ed475b3) [![#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,22 @@ 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)
### Planned features
- :file_folder: Attach files directly from your Nextcloud
- :earth_africa: Share boards with the public
- :calendar: Integration with Nextcloud calendar and other apps
- :speech_balloon: Comments integration
- :exclamation: Checkout the project milestones for more ...
## Installation/Update
This app is supposed to work on the two latest Nextcloud versions.
This app is supposed to work on Nextcloud version 11 or later.
### Install latest release
@@ -34,10 +38,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
@@ -51,22 +55,21 @@ Nothing to prepare, just dig into the code.
### JavaScript
Deck requires running a `make build-js` to install npm dependencies and build the JavaScript code using webpack. While developing you can also use `make watch` to rebuild everytime the code changes.
When `'debug'=>true` is set in your config.php files will get loaded automatically. Otherwise you need to ensure that `public/app.js` is generated by running `make` or `make watch` to regenerate it on every change.
Make sure you have installed the dependencies with ```make install-deps```.
### Running tests
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

2
_config.yml Normal file
View File

@@ -0,0 +1,2 @@
theme: jekyll-theme-cayman
site: https://deck-app.com

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>
@@ -239,6 +198,12 @@
<autoincrement>1</autoincrement>
<length>4</length>
</field>
<field>
<name>title</name>
<type>text</type>
<notnull>true</notnull>
<length>100</length>
</field>
<field>
<name>card_id</name>
<type>integer</type>
@@ -253,12 +218,12 @@
</field>
<field>
<name>data</name>
<type>text</type>
<type>clob</type>
</field>
<field>
<name>last_modified</name>
<type>integer</type>
<default/>
<default></default>
<length>8</length>
<notnull>false</notnull>
<unsigned>true</unsigned>
@@ -266,21 +231,7 @@
<field>
<name>created_at</name>
<type>integer</type>
<default/>
<length>8</length>
<notnull>false</notnull>
<unsigned>true</unsigned>
</field>
<field>
<name>created_by</name>
<type>text</type>
<notnull>true</notnull>
<length>64</length>
</field>
<field>
<name>deleted_at</name>
<type>integer</type>
<default>0</default>
<default></default>
<length>8</length>
<notnull>false</notnull>
<unsigned>true</unsigned>
@@ -317,13 +268,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,62 +11,30 @@
- 📄 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.7.0</version>
<version>0.3.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="17" />
<nextcloud min-version="12" max-version="13" />
</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>
<step>OCA\Deck\Migration\UnknownUsers</step>
</post-migration>
</repair-steps>
<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'],
@@ -65,62 +59,10 @@ return [
['name' => 'card#assignUser', 'url' => '/cards/{cardId}/assign', 'verb' => 'POST'],
['name' => 'card#unassignUser', 'url' => '/cards/{cardId}/assign/{userId}', 'verb' => 'DELETE'],
['name' => 'attachment#getAll', 'url' => '/cards/{cardId}/attachments', 'verb' => 'GET'],
['name' => 'attachment#create', 'url' => '/cards/{cardId}/attachment', 'verb' => 'POST'],
['name' => 'attachment#display', 'url' => '/cards/{cardId}/attachment/{attachmentId}', 'verb' => 'GET'],
['name' => 'attachment#update', 'url' => '/cards/{cardId}/attachment/{attachmentId}', 'verb' => 'PUT'],
// also allow to use POST for updates so we can properly access files when using application/x-www-form-urlencoded
['name' => 'attachment#update', 'url' => '/cards/{cardId}/attachment/{attachmentId}', 'verb' => 'POST'],
['name' => 'attachment#delete', 'url' => '/cards/{cardId}/attachment/{attachmentId}', 'verb' => 'DELETE'],
['name' => 'attachment#restore', 'url' => '/cards/{cardId}/attachment/{attachmentId}/restore', 'verb' => 'GET'],
// labels
['name' => 'label#create', 'url' => '/labels', 'verb' => 'POST'],
['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,12 +8,9 @@
"email": "jus@bitgrid.net"
}
],
"require": {
"cogpowered/finediff": "0.3.*"
},
"require": {},
"require-dev": {
"roave/security-advisories": "dev-master",
"christophwurst/nextcloud": "^16.0",
"jakub-onderka/php-parallel-lint": "^1.0.0"
"christophwurst/nextcloud": "^12.0",
"jakub-onderka/php-parallel-lint": "^0.9.2"
}
}

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;
}

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

@@ -4,8 +4,6 @@
* @author Julius Härtl <jus@bitgrid.net>
* @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 +22,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 {
@@ -84,26 +69,6 @@ input.input-inline {
cursor: text;
}
/**
* Generic app layout
*/
#content {
height: 100%;
min-height: initial;
}
.app.app-deck {
width: 100%;
height: 100%;
display: flex;
}
#app-content {
display: flex;
flex-direction: column;
}
/**
* Navigation sidebar
*/
@@ -136,11 +101,8 @@ input.input-inline {
}
}
.app-navigation-entry-edit {
.colorselect {
div, label {
height: 32px;
width: auto;
}
.colorselect div{
height: 32px;
}
form {
display: flex;
@@ -149,22 +111,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 +119,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 {
@@ -193,19 +139,16 @@ input.input-inline {
.card {
opacity: 1;
&.file-drop {
}
}
&.card-selected {
.card {
box-shadow: 0px 0px 7px 0px var(--color-background-darker, $color-grey);
opacity: 0.7;
&.current {
box-shadow: 0px 0px 7px 0px var(--color-text-lighter, $color-darkgrey);
opacity: 1.0;
box-shadow: 0px 0px 7px 0px $color-darkgrey;
}
}
}
@@ -230,16 +173,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 +214,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;
@@ -302,7 +231,7 @@ input.input-inline {
}
}
#app-navigation-toggle-custom {
#app-navigation-toggle {
width: 44px;
height: 44px;
cursor: pointer;
@@ -328,6 +257,10 @@ input.input-inline {
margin-right: 6px;
}
}
> button {
padding: 16px 20px;
}
}
.filter-select {
@@ -352,7 +285,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 +330,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 +355,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 +372,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 +406,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 +419,9 @@ input.input-inline {
opacity: 1;
}
.icon-description {
.icon-filetype-text {
margin: 10px;
margin-left: 0px;
opacity: 0.5;
}
.due {
@@ -511,7 +439,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,21 +457,6 @@ input.input-inline {
}
}
.card-tasks, .card-files, .card-comments {
border-radius: 3px;
margin: 4px 4px 4px 0px;
padding: 0 2px;
font-size: 90%;
opacity: 0.5;
display: flex;
align-items: center;
.icon {
background-size: contain;
margin-right: 2px;
}
}
button {
padding: 22px;
margin: 0;
@@ -558,39 +471,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 +566,7 @@ input.input-inline {
min-height: 16px;
}
.popovermenu:not(.action-item__menu) {
.popovermenu {
z-index: 999;
opacity: 1;
display: block;
@@ -688,19 +599,25 @@ input.input-inline {
/**
* App sidebar
*/
#app-sidebar {
right: -500px;
max-width: 100%;
width: 500px;
display:flex;
flex-direction: column;
&.details-visible {
right: 0;
}
}
#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 +625,6 @@ input.input-inline {
}
}
#card-dates {
font-size: 80%;
opacity: 0.5;
padding-left: 15px;
}
.icon-close {
position: absolute;
top: 0px;
@@ -724,37 +635,11 @@ input.input-inline {
}
}
.drop-indicator {
display: none;
}
.card .nv-file-over,
.drop-indicator.nv-file-over {
display: block;
position: absolute;
width: 100%;
height: 100%;
background-color: var(--color-main-background, $color-main-background);
z-index: 100;
opacity: 0.9;
text-align: center;
p {
width: calc(100% - 20px);
height: calc(100% - 20px);
position: absolute;
padding: 20px;
border: 1px dashed #AAA;
margin: 10px;
border-radius: 5px;
}
}
#card-meta { // TODO: use .card-block instead?
height: 100%;
display: flex;
flex-direction: column;
padding: 0 15px;
padding: 15px;
.duedate {
display: flex;
@@ -794,41 +679,21 @@ input.input-inline {
}
}
.section-header-tabbed {
margin-top: 10px;
margin-bottom: 5px;
flex-shrink: 0;
display: flex;
.tabHeaders {
margin: 0;
flex-grow: 1;
}
}
.tabDetails {
display: flex;
height: 40px;
align-items: center;
justify-content: center;
input[type=button] {
width: 32px;
}
}
.save-indicator {
border-radius: 3px;
margin: 5px;
float: right;
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 +703,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 {
@@ -861,101 +732,6 @@ input.input-inline {
}
}
.icon-upload.icon-loading-small {
background-image: none;
}
.attachment-list-wrapper {
position: fixed;
width: 100%;
height: 100%;
background-color: rgba($color-darkgrey, 0.5);
left: 0;
top: 0;
z-index: 300;
}
.attachment-list {
&.selector {
padding: 10px;
position: absolute;
width: 30%;
max-width: 500px;
min-width: 200px;
max-height: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: var(--color-main-background, $color-main-background);
z-index: 2;
border-radius: 3px;
box-shadow: 0 0 3px var(--color-background-dark, $color-darkgrey);
overflow: scroll;
}
h3.attachment-selector {
margin: 0 0 10px;
padding: 0;
.icon-close {
display: inline-block;
float: right;
}
}
li.attachment {
display: flex;
padding: 3px;
&.deleted {
opacity: .5;
}
.fileicon {
display: inline-block;
min-width: 32px;
width: 32px;
height: 32px;
background-size: contain;
}
.details {
flex-grow: 1;
flex-shrink: 1;
min-width: 0;
flex-basis: 50%;
line-height: 110%;
padding: 2px;
}
.filename {
width: 70%;
display: flex;
.basename {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-bottom: 2px;
}
.extension {
opacity: 0.7;
}
}
.filesize, .filedate {
font-size: 90%;
color: $color-darkgrey;
}
.app-popover-menu-utils {
position: relative;
right: -10px;
button {
height: 32px;
width: 42px;
}
}
button.icon-history {
width: 44px;
}
progress {
margin-top: 3px;
}
}
}
.card-description {
&.section-header {
.save-indicator {
@@ -981,80 +757,30 @@ 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;
}
#card-attachments {
ul {
margin: 5px;
}
& > img {
opacity: 0.7;
.details {
font-size: 8pt;
padding-left: 15px;
}
}
.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;
}
}
#app-content {
overflow: hidden;
display: flex;
flex-direction: column;
#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;
margin-bottom: 5px;
background-position: 10px;
&.details-visible {
margin-right: 500px;
}
}
@@ -1088,10 +814,6 @@ input.input-inline {
border: none;
}
label.color {
flex-grow: 1;
}
.selected {
background-image: url('../../../core/img/actions/checkmark.svg');
background-position: center center;
@@ -1101,22 +823,6 @@ input.input-inline {
background-image: url('../../../core/img/actions/checkmark-white.svg');
}
}
.colorselect-label, .colorselect-label-white {
background-image: url('../img/color_picker-dark.svg');
background-position: center center;
background-repeat: no-repeat;
opacity: 1;
input {
position: absolute;
visibility: hidden;
height: 32px;
width: 40px;
}
}
.colorselect-label-white {
background-image: url('../img/color_picker.svg');
}
}
.labels {
@@ -1182,8 +888,7 @@ input.input-inline {
.colorselect {
flex-grow: 1;
div,
label {
div {
min-width: 32px;
}
}
@@ -1267,7 +972,6 @@ input.input-inline {
display: inline-block;
overflow: hidden;
vertical-align: middle;
flex-grow: 1;
}
.icon-delete {
@@ -1293,7 +997,7 @@ input.input-inline {
width: 32px;
height: 32px;
.icon-group, .icon {
.icon-group {
padding: 16px;
opacity: 0.5;
}
@@ -1307,22 +1011,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 +1056,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 +1072,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;
@@ -1424,15 +1101,7 @@ input.input-inline {
border: 0 !important;
overflow: hidden;
}
.select2-search-field {
margin-right: -10px;
flex-grow: 1;
input {
width: 100% !important;
}
}
}
.select2-choice {
height: auto;
}
@@ -1467,8 +1136,6 @@ input.input-inline {
*/
#markdown {
width: 100% !important;
min-height: 40px;
cursor: text;
p {
margin-bottom: 15px;
@@ -1522,7 +1189,7 @@ input.input-inline {
}
pre {
background-color: var(--color-background-dark, $color-lightgrey);
background-color: $color-lightgrey;
padding: 3px;
overflow: auto;
@@ -1530,65 +1197,8 @@ input.input-inline {
white-space: pre;
}
}
img {
max-width: 100%;
max-height: 50vh;
margin: auto;
display: block;
}
input[type=checkbox] {
margin: 0px 10px 0px 0px;
line-height: 10px;
font-size: 10px;
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;
}
}
}
.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
*/
@@ -1633,27 +1243,3 @@ input.input-inline {
.ui-select-dropdown.select2-drop-active {
opacity: 1 !important;
}
/**
* Custom app sidebar handling
*/
body:not(.snapjs-left) {
.app-navigation-hide {
#app-content {
margin-left: 0 !important; /* overwrite margin since we want the translateX to handle it*/
}
#app-navigation {
transform: translateX(-300px);
}
}
}
@media only screen and (max-width: 768px) {
#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,897 +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.
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, 5 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 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
### 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" width="15" height="15" viewBox="0 0 100 100"><path d="M91.645 8.355c-4.474-4.474-11.727-4.474-16.2 0l-13.5 13.501-3.727-3.727a5.015 5.015 0 1 0-7.093 7.093l3.727 3.727-41.51 41.508a11.411 11.411 0 0 0-3.329 7.324c-.073 1.087-.347 3.105-.675 5.292a1.748 1.748 0 0 1-.487.983l-3.105 3.106a2.546 2.546 0 0 0 0 3.6l3.493 3.493a2.546 2.546 0 0 0 3.6 0l3.106-3.105c.277-.275.622-.433.981-.486 2.187-.329 4.205-.602 5.293-.675a11.412 11.412 0 0 0 7.325-3.33l41.508-41.508 3.727 3.727a5.015 5.015 0 1 0 7.093-7.093L69.507 29.419l9.697 7.577 12.44-12.441c4.475-4.473 4.474-11.726.001-16.2zM65.051 42.749l-20.53 20.53a2.546 2.546 0 0 1-3.6 0l-3.27-3.27a2.545 2.545 0 0 0-3.599.001l-.616.616-.002-.002-14.728 14.727c-.337.337-.819.401-1.076.143s-.194-.74.143-1.076l23.841-23.841.004.004 15.633-15.633a2.546 2.546 0 0 1 3.6 0l4.2 4.201a2.546 2.546 0 0 1 0 3.6z"/></svg>

Before

Width:  |  Height:  |  Size: 897 B

View File

@@ -1 +0,0 @@
<svg width="15" height="15" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path d="M91.645 8.355c-4.474-4.474-11.727-4.474-16.2 0l-13.5 13.501-3.727-3.727a5.015 5.015 0 1 0-7.093 7.093l3.727 3.727-41.51 41.508a11.411 11.411 0 0 0-3.329 7.324c-.073 1.087-.347 3.105-.675 5.292a1.748 1.748 0 0 1-.487.983l-3.105 3.106a2.546 2.546 0 0 0 0 3.6l3.493 3.493a2.546 2.546 0 0 0 3.6 0l3.106-3.105c.277-.275.622-.433.981-.486 2.187-.329 4.205-.602 5.293-.675a11.412 11.412 0 0 0 7.325-3.33l41.508-41.508 3.727 3.727a5.015 5.015 0 1 0 7.093-7.093L69.507 29.419l9.697 7.577 12.44-12.441c4.475-4.473 4.474-11.726.001-16.2zM65.051 42.749l-20.53 20.53a2.546 2.546 0 0 1-3.6 0l-3.27-3.27a2.545 2.545 0 0 0-3.599.001l-.616.616-.002-.002-14.728 14.727c-.337.337-.819.401-1.076.143s-.194-.74.143-1.076l23.841-23.841.004.004 15.633-15.633a2.546 2.546 0 0 1 3.6 0l4.2 4.201a2.546 2.546 0 0 1 0 3.6z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 910 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,97 +1,79 @@
---
name: Bug report
about: Create a report to help us improve
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Client details:**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
- Device: [e.g. iPhone6, desktop]
<details>
<summary>Server details</summary>
<!--
You can use the Issue Template application to prefill most of the required information: https://apps.nextcloud.com/apps/issuetemplate
-->
**Operating system**:
**Web server:**
**Database:**
**PHP version:**
**Nextcloud version:** (see Nextcloud admin page)
**Where did you install Nextcloud from:**
**Signing status:**
```
Login as admin user into your Nextcloud and access
http://example.com/index.php/settings/integrity/failed
paste the results here.
```
**List of activated apps:**
```
If you have access to your command line run e.g.:
sudo -u www-data php occ app:list
from within your Nextcloud installation folder
```
**Nextcloud configuration:**
```
If you have access to your command line run e.g.:
sudo -u www-data php occ config:list system
from within your Nextcloud installation folder
or
Insert your config.php content here
Make sure to remove all sensitive content such as passwords. (e.g. database password, passwordsalt, secret, smtp password, …)
```
**Are you using an external user-backend, if yes which one:** LDAP/ActiveDirectory/Webdav/...
</details>
<details>
<summary>Logs</summary>
#### Nextcloud log (data/nextcloud.log)
```
Insert your Nextcloud log here
```
#### Browser log
```
Insert your browser log here, this could for example include:
a) The javascript console log
b) The network log
c) ...
```
</details>
### Steps to reproduce
1.
2.
3.
### Expected behaviour
Tell us what should happen
### Actual behaviour
Tell us what happens instead
### Server configuration
<!--
You can use the Issue Template application to prefill most of the required information: https://apps.nextcloud.com/apps/issuetemplate
-->
**Operating system**:
**Web server:**
**Database:**
**PHP version:**
**Nextcloud version:** (see Nextcloud admin page)
**Where did you install Nextcloud from:**
**Signing status:**
```
Login as admin user into your Nextcloud and access
http://example.com/index.php/settings/integrity/failed
paste the results here.
```
**List of activated apps:**
```
If you have access to your command line run e.g.:
sudo -u www-data php occ app:list
from within your Nextcloud installation folder
```
**Nextcloud configuration:**
```
If you have access to your command line run e.g.:
sudo -u www-data php occ config:list system
from within your Nextcloud installation folder
or
Insert your config.php content here
Make sure to remove all sensitive content such as passwords. (e.g. database password, passwordsalt, secret, smtp password, …)
```
**Are you using an external user-backend, if yes which one:** LDAP/ActiveDirectory/Webdav/...
### Client configuration
**Browser:**
**Operating system:**
### Logs
#### Nextcloud log (data/nextcloud.log)
```
Insert your Nextcloud log here
```
#### Browser log
```
Insert your browser log here, this could for example include:
a) The javascript console log
b) The network log
c) ...
```

View File

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

3
js/.bowerrc Normal file
View File

@@ -0,0 +1,3 @@
{
"directory": "vendor"
}

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,

124
js/Gruntfile.js Normal file
View File

@@ -0,0 +1,124 @@
/*
* @copyright Copyright (c) 2016 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 module */
module.exports = function(grunt) {
'use strict';
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-wrap');
grunt.loadNpmTasks('grunt-karma');
grunt.loadNpmTasks('grunt-phpunit');
grunt.initConfig({
meta: {
pkg: grunt.file.readJSON('package.json'),
version: '<%= meta.pkg.version %>',
configJS: 'config/',
buildJS: [
'app/**/*.js',
'controller/**/*.js',
'filters/**/*.js',
'directive/**/*.js',
'service/**/*.js'
],
productionJS: 'public/',
testsJS: '../tests/js/'
},
concat: {
options: {
stripBanners: true
},
dist: {
src: ['<%= meta.buildJS %>'],
dest: '<%= meta.productionJS %>app.js'
}
},
wrap: {
app: {
src: ['<%= meta.productionJS %>app.js'],
dest: '<%= meta.productionJS %>app.js',
option: {
wrapper: [
'(function(angular, $, oc_requesttoken, undefined){\n\n\'use strict\';\n\n',
'\n})(angular, jQuery, oc_requesttoken);'
]
}
}
},
jshint: {
files: [
'Gruntfile.js',
'<%= meta.buildJS %>**/*.js',
'<%= meta.testsJS %>**/*.js'
],
options: {
jshintrc: '.jshintrc',
reporter: require('jshint-stylish')
}
},
watch: {
concat: {
files: ['<%=meta.buildJS%>'],
options: {
livereload: true
},
tasks: ['build']
}
},
phpunit: {
classes: {
dir: '../tests/unit'
},
options: {
bootstrap: '../tests/bootstrap.php',
colors: true
}
},
karma: {
unit: {
configFile: '<%= meta.testsJS %>config/karma.js'
},
continuous: {
configFile: '<%= meta.testsJS %>config/karma.js',
browsers: ['Firefox'],
singleRun: true,
reporters: ['progress']
}
},
});
// make tasks available under simpler commands
grunt.registerTask('build', ['jshint', 'concat', 'wrap']);
grunt.registerTask('js-unit', ['karma:continuous']);
};

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 */
@@ -41,24 +41,13 @@ angular.module('markdown', [])
};
}]);
import uirouter from '@uirouter/angularjs';
import ngsanitize from 'angular-sanitize';
import angularuiselect from 'ui-select';
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
'ngRoute',
'ngSanitize',
'ui.router',
'ui.select',
'as.sortable',
'mdMarkdownIt',
'ngAnimate'
]);
export default app;

View File

@@ -22,27 +22,18 @@
/* global app oc_requesttoken markdownitLinkTarget */
import app from './App.js';
import md from 'angular-markdown-it';
import markdownitLinkTarget from 'markdown-it-link-target';
import markdownitCheckbox from 'legacy/markdown-it-checkbox.js';
app.config(function ($provide, $interpolateProvider, $httpProvider, $urlRouterProvider, $stateProvider, $compileProvider, markdownItConverterProvider) {
app.config(function ($provide, $routeProvider, $interpolateProvider, $httpProvider, $urlRouterProvider, $stateProvider, $compileProvider, markdownItConverterProvider) {
'use strict';
$httpProvider.defaults.headers.common.requesttoken = oc_requesttoken;
$compileProvider.debugInfoEnabled(true);
// This should fix adding "unsafe:" prefix to ui-select href links containing javascript
// inline JS is blocked by CSP anyway and filtered out by our markdown renderer as well
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|javascript):/);
markdownItConverterProvider.config({
breaks: true,
linkify: true,
xhtmlOut: true
});
markdownItConverterProvider.use(markdownitLinkTarget).use(markdownitCheckbox);
markdownItConverterProvider.use(markdownitLinkTarget);
$urlRouterProvider.otherwise('/');
@@ -71,47 +62,19 @@ app.config(function ($provide, $interpolateProvider, $httpProvider, $urlRouterPr
tab: {value: 0, dynamic: true},
},
views: {
'sidebarView@': {
templateUrl: '/board.sidebarView.html',
controller: 'BoardController'
'sidebarView': {
templateUrl: '/board.sidebarView.html'
}
}
})
.state('board.card', {
url: '/card/:cardId',
params: {
tab: {value: 0, dynamic: true},
},
views: {
'sidebarView@': {
'sidebarView': {
templateUrl: '/card.sidebarView.html',
controller: 'CardController'
}
}
});
$provide.decorator('nvFileOverDirective', function ($delegate) {
var directive = $delegate[0],
link = directive.link;
directive.compile = function () {
return function (scope, element, attrs) {
var overClass = attrs.overClass || 'nv-file-over';
link.apply(this, arguments);
let counter = 0;
element.on('dragenter', function (event) {
counter++;
});
element.on('dragleave', function (event) {
counter--;
if (counter <= 0) {
$('.' + overClass).removeClass(overClass);
}
});
};
};
return $delegate;
});
});
});

View File

@@ -4,27 +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.js';
/* global Snap */
app.run(function ($document, $rootScope, $transitions, BoardService) {
'use strict';
$document.click(function (event) {
$rootScope.$broadcast('documentClicked', event);
});
@@ -56,6 +54,26 @@ app.run(function ($document, $rootScope, $transitions, BoardService) {
OC.filePath('deck', 'img', 'app-512.png')
);
$('#app-navigation-toggle').off('click');
// App sidebar on mobile
var snapper = new Snap({
element: document.getElementById('app-content'),
disable: 'right',
maxPosition: 250,
touchToDrag: false
});
$('#app-navigation-toggle').click(function () {
if ($(window).width() > 768) {
$('#app-navigation').toggle('hidden');
} else {
if (snapper.state().state === 'left') {
snapper.close();
} else {
snapper.open('left');
}
}
});
// Select all elements with data-toggle="tooltips" in the document
$('body').tooltip({
selector: '[data-toggle="tooltip"]'

29
js/bower.json Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "deck",
"version": "0.0.1",
"dependencies": {
"angular": "~1.6.1",
"angular-route": "~1.6.1",
"angular-mocks": "~1.6.1",
"angular-sanitize": "~1.6.1",
"angular-animate": "~1.6.1",
"ng-sortable": "1.3.8",
"jquery": "3.2.x",
"es6-shim": "~0.*",
"js-url": "~2.*",
"angular-ui-select": "~0.19.6",
"angular-markdown-it": "~0.6.1",
"angular-ui-router": "~1.0.0",
"markdown-it-link-target": "~1.0.1",
"jquery-timepicker": "883bb2cd94"
},
"license": "AGPL-3.0",
"private": true,
"ignore": [
"'**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

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,41 +4,27 @@
* @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 */
app.controller('AppController', function ($scope, $location, $http, $log, $rootScope, $attrs) {
/** global: OC */
app.controller('AppController', function ($scope, $location, $http, $route, $log, $rootScope) {
$rootScope.sidebar = {
show: false
};
$scope.sidebar = $rootScope.sidebar;
$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.toggleSidebar = function() {
if ($(window).width() > 768) {
$log.debug($scope.appNavigationHide);
$scope.appNavigationHide = !$scope.appNavigationHide;
localStorage.setItem('deck.appNavigationHide', JSON.stringify($scope.appNavigationHide));
}
};
});
});

View File

@@ -1,78 +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 */
class AttachmentListController {
constructor ($scope, CardService, FileService) {
'ngInject';
this.cardservice = CardService;
this.fileservice = FileService;
this.attachments = CardService.getCurrent().attachments;
}
mimetypeForAttachment(attachment) {
let url = OC.MimeType.getIconUrl(attachment.extendedData.mimetype);
let styles = {
'background-image': `url("${url}")`,
};
return styles;
}
attachmentUrl(attachment) {
let cardId = this.cardservice.getCurrent().id;
let attachmentId = attachment.id;
return OC.generateUrl(`/apps/deck/cards/${cardId}/attachment/${attachmentId}`);
}
getAttachmentMarkdown(attachment) {
const inlineMimetypes = ['image/png', 'image/jpg', 'image/jpeg'];
let url = this.attachmentUrl(attachment);
let filename = attachment.data;
let insertText = `[📎 ${filename}](${url})`;
if (inlineMimetypes.indexOf(attachment.extendedData.mimetype) > -1) {
insertText = `![📎 ${filename}](${url})`;
}
return insertText;
}
select(attachment) {
this.onSelect({attachment: this.getAttachmentMarkdown(attachment)});
}
abort() {
this.onAbort();
}
}
let attachmentListComponent = {
templateUrl: '/card.attachments.html',
controller: AttachmentListController,
bindings: {
isFileSelector: '<',
attachments: '=',
onSelect: '&',
onAbort: '&'
}
};
export default attachmentListComponent;

View File

@@ -4,33 +4,24 @@
* @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) {
$scope.sidebar = $rootScope.sidebar;
@@ -48,49 +39,6 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
$scope.labelservice = LabelService;
$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() {
@@ -98,24 +46,8 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
}, function (params) {
$scope.params = params;
}, true);
$scope.params = $state.params;
$scope.params = $state;
/**
* Check for markdown checkboxes in description to render the counter
*
* This should probably be moved to the backend at some point
*
* @param text
* @returns array of [finished, total] checkboxes
*/
$scope.getCheckboxes = function(text) {
const regTotal = /\[(X|\s|\_|\-)\]/igm;
const regFinished = /\[(X|\_|\-)\]/igm;
return [
((text || '').match(regFinished) || []).length,
((text || '').match(regTotal) || []).length
];
};
$scope.search = function (searchText) {
$scope.searchText = searchText;
@@ -156,36 +88,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 () {
@@ -250,102 +152,36 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
// Create a new Stack
$scope.createStack = function () {
StackService.create($scope.newStack).then(function (data) {
$scope.newStack.title = '';
$scope.newStack.title = "";
});
};
$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,58 +192,20 @@ 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) {
$scope.newStack.title = '';
$scope.newStack.title = "";
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 +213,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 +232,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 +243,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 +259,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);
@@ -479,7 +273,7 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
// auto scroll on drag
dragMove: function (itemPosition, containment, eventObj) {
if (eventObj) {
var container = $('#board');
var container = $("#board");
var offset = container.offset();
var targetX = eventObj.pageX - (offset.left || container.scrollLeft());
var targetY = eventObj.pageY - (offset.top || container.scrollTop());
@@ -514,7 +308,7 @@ app.controller('BoardController', function ($rootScope, $scope, $element, $state
containment: '#innerBoard',
dragMove: function (itemPosition, containment, eventObj) {
if (eventObj) {
var container = $('#board');
var container = $("#board");
var offset = container.offset();
var targetX = eventObj.pageX - (offset.left || container.scrollLeft());
var targetY = eventObj.pageY - (offset.top || container.scrollTop());
@@ -542,27 +336,4 @@ 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;
}
return card.attachmentCount;
};
$scope.unreadCommentCount = function(card) {
return card.commentsUnread;
};
$scope.isTimelineEnabled = function() {
return OCP.Comments && OCA.Activity;
};
});

View File

@@ -4,137 +4,53 @@
* @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 */
import app from '../app/App.js';
/* global app moment */
app.controller('CardController', function ($scope, $rootScope, $sce, $location, $stateParams, $state, $interval, $timeout, $filter, BoardService, CardService, StackService, StatusService, markdownItConverter, FileService) {
app.controller('CardController', function ($scope, $rootScope, $routeParams, $location, $stateParams, $interval, $timeout, $filter, BoardService, CardService, StackService, StatusService) {
$scope.sidebar = $rootScope.sidebar;
$scope.status = {
renameTitle: '',
lastEdit: 0,
lastSave: Date.now()
};
$scope.cardservice = CardService;
$scope.fileservice = FileService;
$scope.cardId = $stateParams.cardId;
$scope.statusservice = StatusService.getInstance();
$scope.boardservice = BoardService;
$scope.isArray = angular.isArray;
// workaround for $stateParams changes not being propagated
$scope.$watch(function() {
return $state.params;
}, function (params) {
$scope.params = params;
$scope.fileservice.reset();
}, true);
$scope.params = $state.params;
$scope.addAttachmentToDescription = function(insertText) {
let el = document.querySelectorAll('textarea')[0];
let start = el.selectionStart;
let end = el.selectionEnd;
let text = $scope.status.edit.description || '';
let before = text.substring(0, start);
let after = text.substring(end, text.length);
let newText = before + '\n' + insertText + '\n' + after;
$scope.status.edit.description = newText;
el.selectionStart = el.selectionEnd = start + newText.length;
el.focus();
$scope.status.continueEdit = false;
$scope.cardEditDescriptionChanged();
$scope.status.selectAttachment = false;
};
$scope.abortAttachmentSelection = function() {
$scope.status.continueEdit = false;
$scope.status.selectAttachment = false;
let el = document.querySelectorAll('textarea')[0];
el.focus();
};
$scope.statusservice.retainWaiting();
$scope.description = function() {
return $scope.rendered;
};
$scope.updateMarkdown = function(content) {
// only trust the html from markdown-it-checkbox
$scope.rendered = $sce.trustAsHtml(markdownItConverter.render(content || ''));
};
CardService.fetchOne($scope.cardId).then(function (data) {
$scope.statusservice.releaseWaiting();
$scope.archived = CardService.getCurrent().archived;
$scope.updateMarkdown(CardService.getCurrent().description);
}, function (error) {
});
$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;
}
};
$scope.toggleCheckbox = function (id) {
$('#markdown input[type=checkbox]').attr('disabled', true);
$scope.status.edit = angular.copy(CardService.getCurrent());
var reg = /\[(X|\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 (match.match(/^\[\s\]/i)) {
result = match.replace(/\[\s\]/i, '[x]');
}
if (match.match(/^\[x\]/i)) {
result = match.replace(/\[x\]/i, '[ ]');
}
return result;
}
return match;
});
CardService.update($scope.status.edit).then(function (data) {
var header = $('.tabDetails');
header.find('.save-indicator.unsaved').hide();
header.find('.save-indicator.saved').fadeIn(250).fadeOut(1000);
});
$('#markdown input[type=checkbox]').removeAttr('disabled');
};
$scope.clickCardDescription = function ($event) {
var checkboxId = $($event.target).data('id');
if ($event.target.tagName === 'LABEL') {
$scope.toggleCheckbox(checkboxId);
$event.stopPropagation();
return false;
}
if ($event.target.tagName === 'INPUT') {
$event.stopPropagation();
return;
}
$scope.cardEditDescriptionShow = function ($event) {
if (BoardService.isArchived() || CardService.getCurrent().archived) {
return false;
}
@@ -147,68 +63,52 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
};
$scope.cardEditDescriptionChanged = function ($event) {
$scope.status.lastEdit = Date.now();
var header = $('.tabDetails');
var header = $('.section-header.card-description');
header.find('.save-indicator.unsaved').show();
header.find('.save-indicator.saved').hide();
};
$interval(function() {
var currentTime = Date.now();
var timeSinceEdit = currentTime-$scope.status.lastEdit;
if (timeSinceEdit > 1000 && $scope.status.lastEdit > $scope.status.lastSave && !$scope.status.saving) {
if (timeSinceEdit > 1000 && $scope.status.lastEdit > $scope.status.lastSave) {
$scope.status.lastSave = currentTime;
$scope.status.saving = true;
var header = $('.tabDetails');
var header = $('.section-header.card-description');
header.find('.save-indicator.unsaved').fadeIn(500);
CardService.update($scope.status.edit).then(function (data) {
var header = $('.tabDetails');
var header = $('.section-header.card-description');
header.find('.save-indicator.unsaved').hide();
header.find('.save-indicator.saved').fadeIn(250).fadeOut(1000);
$scope.status.saving = false;
StackService.updateCard($scope.status.edit);
});
}
}, 500, 0, false);
// 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) {
StackService.updateCard(card);
$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-content.card-description');
header.find('.save-indicator.unsaved').hide();
header.find('.save-indicator.saved').fadeIn(500).fadeOut(1000);
StackService.updateCard(card);
});
};
$scope.labelAssign = function (element, model) {
CardService.assignLabel($scope.cardId, element.id).then(function (data) {
StackService.updateCard(CardService.getCurrent());
});
};
$scope.labelRemove = function (element, model) {
CardService.removeLabel($scope.cardId, element.id).then(function (data) {
StackService.updateCard(CardService.getCurrent());
});
};
@@ -223,6 +123,7 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
newDate.year(duedate.year());
element.duedate = newDate.toISOString();
CardService.update(element);
StackService.updateCard(element);
};
$scope.setDuedateTime = function (time) {
var element = CardService.getCurrent();
@@ -234,14 +135,16 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
newDate.minute(time.minute());
element.duedate = newDate.toISOString();
CardService.update(element);
StackService.updateCard(element);
};
$scope.resetDuedate = function () {
var element = CardService.getCurrent();
element.duedate = null;
CardService.update(element);
StackService.updateCard(element);
};
/**
* Show ui-select field when clicking the add button
*/
@@ -263,12 +166,14 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
$scope.addAssignedUser = function(item) {
CardService.assignUser(CardService.getCurrent(), item.uid).then(function (data) {
StackService.updateCard(CardService.getCurrent());
});
$scope.status.showAssignUser = false;
};
$scope.removeAssignedUser = function(uid) {
CardService.unassignUser(CardService.getCurrent(), uid).then(function (data) {
StackService.updateCard(CardService.getCurrent());
});
};
@@ -279,8 +184,4 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
};
};
$scope.isTimelineEnabled = function() {
return OCP.Comments && OCA.Activity;
};
});
});

View File

@@ -1,44 +0,0 @@
/*
* @copyright Copyright (c) 2018 Oskar Kurz <oskar.kurz@gmail.com>
*
* @author Oskar Kurz <oskar.kurz@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
/* global oc_defaults OC */
app.controller('ColorPickerController', ['$scope', function ($scope) {
$scope.hashedColor = '';
$scope.setColor = function (object, color) {
object.color = color;
object.hashedColor = '#' + color;
return object;
};
$scope.setHashedColor = function (object) {
object.color = object.hashedColor.substr(1);
return object;
};
$scope.getCustomBackground = function (color) {
return {'background-color': color};
};
}]);

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) {
app.controller('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') {
@@ -250,6 +193,5 @@ var ListController = function ($scope, $location, $filter, BoardService, $elemen
});
};
};
});
export default ListController;

View File

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
app.directive('appPopoverMenuUtils', function () {
'use strict';

View File

@@ -20,7 +20,6 @@
*
*/
import app from '../app/App.js';
// OwnCloud Click Handling
// https://doc.owncloud.org/server/8.0/developer_manual/app/css.html
app.directive('appNavigationEntryUtils', function () {

View File

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
app.directive('autofocusOnInsert', function () {
'use strict';

View File

@@ -4,22 +4,21 @@
* @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('avatar', function() {
'use strict';
@@ -32,10 +31,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 +43,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

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
app.directive('contactsmenudelete', function() {
'use strict';

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

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
/* global app */
/* gloabl t */
@@ -30,10 +29,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

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
// original idea from blockloop: http://stackoverflow.com/a/24090733
app.directive('elastic', [

View File

@@ -4,22 +4,21 @@
* @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('search', function ($document, $location) {
'use strict';
@@ -30,32 +29,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

@@ -19,9 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
import '../legacy/jquery.ui.timepicker.js';
import 'legacy/jquery.ui.timepicker.css';
/* global app */
/* global t */
@@ -32,7 +29,7 @@ app.directive('timepicker', function() {
return {
restrict: 'A',
link: function(scope, elm, attr) {
return $(elm).timepicker({
return elm.timepicker({
onSelect: function(date, inst) {
scope.setDuedateTime(moment('2000-01-01 ' + date));
scope.$apply();

View File

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
app.filter('boardFilterAcl', function() {
return function(boards) {

View File

@@ -1,37 +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.filter('bytes', function () {
return function (bytes, precision) {
if (isNaN(parseFloat(bytes, 10)) || !isFinite(bytes)) {
return '-';
}
if (typeof precision === 'undefined') {
precision = 2;
}
var units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'],
number = Math.floor(Math.log(bytes) / Math.log(1024));
return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number];
};
});

View File

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
// usage | cardFilter({ member: 'admin'})

View File

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
app.filter('cardSearchFilter', function() {
return function(cards, searchString) {

View File

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
/* global app */
/* global OC */
@@ -46,8 +45,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

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
app.filter('iconWhiteFilter', function () {
return function (hex) {
@@ -32,7 +31,7 @@ app.filter('iconWhiteFilter', function () {
b: parseInt(result[3], 16)
} : null;
if (result === null) {
return '';
return "";
}
var r = color.r / 255;
var g = color.g / 255;
@@ -59,9 +58,9 @@ app.filter('iconWhiteFilter', function () {
h /= 6;
}
if (l < 0.5) {
return '-white';
return "-white";
} else {
return '';
return "";
}
};
});

View File

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
app.filter('lightenColorFilter', function() {
return function (hex) {
@@ -30,9 +29,9 @@ app.filter('lightenColorFilter', function() {
b: parseInt(result[3], 16)
} : null;
if (result !== null) {
return 'rgba(' + color.r + ',' + color.g + ',' + color.b + ',0.7)';
return "rgba(" + color.r + "," + color.g + "," + color.b + ",0.7)";
} else {
return '#' + hex;
return "#" + hex;
}
};
});
});

View File

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
app.filter('orderObjectBy', function(){
return function(input, attribute) {

View File

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
app.filter('textColorFilter', function () {
return function (hex) {
@@ -57,12 +56,12 @@ app.filter('textColorFilter', function () {
h /= 6;
}
if (l < 0.5) {
return '#ffffff';
return "#ffffff";
} else {
return '#000000';
return "#000000";
}
} else {
return '#000000';
return "#000000";
}
};

View File

@@ -19,7 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import app from '../app/App.js';
/* global app */
/* global angular */

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,38 +0,0 @@
'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';
import angular from 'angular';
import markdownit from 'markdown-it';
global.markdownit = markdownit;
import app from './app/App.js';
import './app/Config.js';
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
var context = require.context('.', true, /(controller|service|filters|directive)\/(.*)\.js$/);
context.keys().forEach(function (key) {
context(key);
});

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

@@ -1,57 +0,0 @@
/*
* Timepicker stylesheet
* Highly inspired from datepicker
* FG - Nov 2010 - Web3R
*
* version 0.0.3 : Fixed some settings, more dynamic
* version 0.0.4 : Removed width:100% on tables
* version 0.1.1 : set width 0 on tables to fix an ie6 bug
*/
.ui-timepicker-inline { display: inline; }
#ui-timepicker-div { padding: 0.2em; }
.ui-timepicker-table { display: inline-table; width: 0; }
.ui-timepicker-table table { margin:0.15em 0 0 0; border-collapse: collapse; }
.ui-timepicker-hours, .ui-timepicker-minutes { padding: 0.2em; }
.ui-timepicker-table .ui-timepicker-title { line-height: 1.8em; text-align: center; }
.ui-timepicker-table td { padding: 0.1em; width: 2.2em; }
.ui-timepicker-table th.periods { padding: 0.1em; width: 2.2em; }
/* span for disabled cells */
.ui-timepicker-table td span {
display:block;
padding:0.2em 0.3em 0.2em 0.5em;
width: 1.2em;
text-align:right;
text-decoration:none;
}
/* anchors for clickable cells */
.ui-timepicker-table td a {
display:block;
padding:0.2em 0.3em 0.2em 0.5em;
width: 1.2em;
cursor: pointer;
text-align:right;
text-decoration:none;
}
/* buttons and button pane styling */
.ui-timepicker .ui-timepicker-buttonpane {
background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0;
}
.ui-timepicker .ui-timepicker-buttonpane button { margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
/* The close button */
.ui-timepicker .ui-timepicker-close { float: right }
/* the now button */
.ui-timepicker .ui-timepicker-now { float: left; }
/* the deselect button */
.ui-timepicker .ui-timepicker-deselect { float: left; }

File diff suppressed because it is too large Load Diff

View File

@@ -1,120 +0,0 @@
/**
* Original source code from https://github.com/mcecot/markdown-it-checkbox
* © 2015 Markus Cecot
* licenced under MIT
* https://github.com/mcecot/markdown-it-checkbox/blob/master/LICENSE
*/
var checkboxReplace;
checkboxReplace = function(md, options, Token) {
"use strict";
var arrayReplaceAt, createTokens, defaults, lastId, pattern, splitTextToken;
arrayReplaceAt = md.utils.arrayReplaceAt;
lastId = 0;
defaults = {
divWrap: false,
divClass: 'checkbox',
idPrefix: 'checkbox'
};
options = Object.assign(defaults, options);
pattern = /(.*?)(\[(X|\s|\_|\-)\])(.*)/igm;
createTokens = function(checked, label, Token, before) {
var id, idNumeric, nodes, token;
nodes = [];
token = new Token("text", "", 0);
token.content = before;
nodes.push(token);
/**
* <div class="checkbox">
*/
if (options.divWrap) {
token = new Token("checkbox_open", "div", 1);
token.attrs = [["class", options.divClass]];
nodes.push(token);
}
/**
* <input type="checkbox" id="checkbox{n}" checked="true">
*/
id = options.idPrefix + lastId;
idNumeric = lastId;
lastId += 1;
token = new Token("checkbox_input", "input", 0);
token.attrs = [["type", "checkbox"], ["id", id], ["data-id", idNumeric]];
if (checked === true) {
token.attrs.push(["checked", "true"]);
}
token.attrs.push(["class", "checkbox"]);
nodes.push(token);
/**
* <label for="checkbox{n}">
*/
token = new Token("label_open", "label", 1);
token.attrs = [["for", id], ["data-id", idNumeric]];
nodes.push(token);
/**
* content of label tag
*/
token = new Token("text", "", 0);
token.content = label;
nodes.push(token);
/**
* closing tags
*/
nodes.push(new Token("label_close", "label", -1));
if (options.divWrap) {
nodes.push(new Token("checkbox_close", "div", -1));
}
return nodes;
};
splitTextToken = function(original, Token) {
var checked, label, matches, text, value, before;
text = original.content;
matches = pattern.exec(text);
if (matches === null) {
return original;
}
checked = false;
before = matches[1];
value = matches[3];
label = matches[4];
if (value === "X" || value === "x") {
checked = true;
}
return createTokens(checked, label, Token, before);
};
return function(state) {
lastId = 0;
var blockTokens, i, j, l, token, tokens;
blockTokens = state.tokens;
j = 0;
l = blockTokens.length;
while (j < l) {
if (blockTokens[j].type !== "inline") {
j++;
continue;
}
tokens = blockTokens[j].children;
i = 0;
while (i < tokens.length) {
token = tokens[i];
blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, splitTextToken(token, state.Token));
i++;
}
j++;
}
};
};
/*global module */
module.exports = function(md, options) {
"use strict";
md.core.ruler.push("checkbox", checkboxReplace(md, options));
};

9243
js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,56 +1,29 @@
{
"name": "deck",
"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.22",
"angular": "^1.7.8",
"angular-animate": "^1.7.8",
"angular-file-upload": "^2.5.0",
"angular-markdown-it": "^0.6.1",
"angular-sanitize": "^1.7.8",
"babel-polyfill": "^6.26.0",
"markdown-it": "^9.1.0",
"markdown-it-link-target": "^1.0.2",
"nextcloud-axios": "^0.2.0",
"nextcloud-vue": "^0.12.1",
"nextcloud-vue-collections": "^0.5.6",
"ng-infinite-scroll": "^1.3.0",
"ng-sortable": "^1.3.8",
"ui-select": "^0.19.8",
"vue": "^2.6.10",
"vuex": "^3.1.1"
},
"dependencies": {},
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/polyfill": "^7.4.4",
"@babel/preset-env": "^7.5.5",
"babel-loader": "^8.0.6",
"css-loader": "^3.2.0",
"karma": "^4.2.0",
"mini-css-extract-plugin": "^0.8.0",
"style-loader": "^1.0.0",
"uglifyjs-webpack-plugin": "^2.2.0",
"url-loader": "^2.1.0",
"vue-loader": "^15.7.1",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.39.2",
"webpack-cli": "^3.3.6",
"webpack-merge": "^4.2.1"
"bower": "^1.8.0",
"grunt": "^1.0.1",
"grunt-contrib-concat": "^1.0.1",
"grunt-contrib-jshint": "^1.1.0",
"grunt-contrib-watch": "^1.0.0",
"grunt-karma": "^2.0.0",
"grunt-phpunit": "^0.3.6",
"grunt-wrap": "^0.3.0",
"jshint-stylish": "^2.2.1",
"karma": "^1.4.1",
"node-sass": "^4.5.3"
},
"scripts": {
"build": "NODE_ENV=production ./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"
},
"author": "",
"license": "AGPL-3.0",
"keywords": []
"keywords": [],
"description": ""
}

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};

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