Compare commits
6 Commits
v1.3.0-bet
...
backport/1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e34a57245a | ||
|
|
8334a410cd | ||
|
|
88aec57200 | ||
|
|
7bf903f586 | ||
|
|
9550c2eb2f | ||
|
|
12fb5f0a1f |
180
.drone.yml
Normal file
@@ -0,0 +1,180 @@
|
||||
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
|
||||
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/
|
||||
- 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: unit-php7.2
|
||||
steps:
|
||||
- name: php7.2
|
||||
image: nextcloudci/php7.2:php7.2-9
|
||||
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/
|
||||
- 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: unit-php7.3
|
||||
steps:
|
||||
- name: php7.3
|
||||
image: nextcloudci/php7.3:php7.3-2
|
||||
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/
|
||||
- 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
|
||||
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/
|
||||
- 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
|
||||
image: nextcloudci/eslint:eslint-1
|
||||
commands:
|
||||
- ./run-eslint.sh
|
||||
- name: 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
|
||||
@@ -1,9 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{js,vue}]
|
||||
indent_style = tab
|
||||
@@ -1,8 +0,0 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'@nextcloud'
|
||||
],
|
||||
rules: {
|
||||
'valid-jsdoc': ['off'],
|
||||
}
|
||||
}
|
||||
43
.eslintrc.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
root: true
|
||||
|
||||
extends:
|
||||
- eslint:recommended
|
||||
|
||||
env:
|
||||
browser: true
|
||||
amd: true
|
||||
es6: true
|
||||
|
||||
globals:
|
||||
global: false
|
||||
app: false
|
||||
angular: false
|
||||
$: false
|
||||
escapeHTML: false
|
||||
OC: false
|
||||
OCA: false
|
||||
t: false
|
||||
oc_current_user: false
|
||||
oc_requesttoken: false
|
||||
Clipboard: false
|
||||
oc_defaults: false
|
||||
|
||||
parserOptions:
|
||||
ecmaVersion: 6
|
||||
sourceType: "module"
|
||||
|
||||
rules:
|
||||
curly: error
|
||||
eqeqeq: ["error", "smart"]
|
||||
guard-for-in: error
|
||||
no-console: off
|
||||
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:
|
||||
- error
|
||||
- tab
|
||||
- SwitchCase: 1
|
||||
23
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -4,29 +4,6 @@ about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thanks for reporting issues back!
|
||||
|
||||
Guidelines for submitting issues:
|
||||
|
||||
* Please search the existing issues first, it's likely that your issue was already reported or even fixed.
|
||||
|
||||
* SECURITY: Report any potential security bug to us via our HackerOne page (https://hackerone.com/nextcloud) following our security policy (https://nextcloud.com/security/) instead of filing an issue in our bug tracker.
|
||||
|
||||
* The issues in other components should be reported in their respective repositories: You will find them in our GitHub Organization (https://github.com/nextcloud/)
|
||||
|
||||
* You can also use the Issue Template app to prefill most of the required information: https://apps.nextcloud.com/apps/issuetemplate
|
||||
-->
|
||||
|
||||
<!--- Please keep this note for other contributors -->
|
||||
|
||||
### How to use GitHub
|
||||
|
||||
* Please use the 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to show that you are affected by the same issue.
|
||||
* Please don't comment if you have no relevant information to add. It's just extra noise for everyone subscribed to this issue.
|
||||
* Subscribe to receive notifications on status change and new comments.
|
||||
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
|
||||
22
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
@@ -4,28 +4,6 @@ about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thanks for reporting issues back!
|
||||
|
||||
Guidelines for submitting issues:
|
||||
|
||||
* Please search the existing issues first, it's likely that your issue was already reported or even fixed.
|
||||
|
||||
* SECURITY: Report any potential security bug to us via our HackerOne page (https://hackerone.com/nextcloud) following our security policy (https://nextcloud.com/security/) instead of filing an issue in our bug tracker.
|
||||
|
||||
* The issues in other components should be reported in their respective repositories: You will find them in our GitHub Organization (https://github.com/nextcloud/)
|
||||
|
||||
* You can also use the Issue Template app to prefill most of the required information: https://apps.nextcloud.com/apps/issuetemplate
|
||||
-->
|
||||
|
||||
<!--- Please keep this note for other contributors -->
|
||||
|
||||
### How to use GitHub
|
||||
|
||||
* Please use the 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to show that you are affected by the same issue.
|
||||
* Please don't comment if you have no relevant information to add. It's just extra noise for everyone subscribed to this issue.
|
||||
* Subscribe to receive notifications on status change and new comments.
|
||||
|
||||
**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 [...]
|
||||
|
||||
|
||||
41
.github/dependabot.yml
vendored
@@ -1,41 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
target-branch: "master"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: saturday
|
||||
time: "03:00"
|
||||
timezone: Europe/Paris
|
||||
open-pull-requests-limit: 10
|
||||
reviewers:
|
||||
- juliushaertl
|
||||
- jakobroehrl
|
||||
#- package-ecosystem: npm
|
||||
# directory: "/"
|
||||
# target-branch: "stable1.1"
|
||||
# schedule:
|
||||
# interval: weekly
|
||||
# day: saturday
|
||||
# time: "03:00"
|
||||
# timezone: Europe/Paris
|
||||
# open-pull-requests-limit: 10
|
||||
# reviewers:
|
||||
# - juliushaertl
|
||||
# - jakobroehrl
|
||||
- package-ecosystem: composer
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: saturday
|
||||
time: "03:00"
|
||||
timezone: Europe/Paris
|
||||
open-pull-requests-limit: 10
|
||||
reviewers:
|
||||
- juliushaertl
|
||||
ignore:
|
||||
- dependency-name: christophwurst/nextcloud
|
||||
versions:
|
||||
- "< 16"
|
||||
- ">= 15.a"
|
||||
55
.github/workflows/app-code-check.yml
vendored
@@ -1,55 +0,0 @@
|
||||
name: Nextcloud app code check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- stable*
|
||||
|
||||
env:
|
||||
APP_NAME: deck
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.4']
|
||||
server-versions: ['master', 'stable18', 'stable19', 'stable20']
|
||||
|
||||
name: AppCode check php${{ matrix.php-versions }}-${{ matrix.server-versions }}
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: nextcloud/server
|
||||
ref: ${{ matrix.server-versions }}
|
||||
|
||||
- name: Checkout submodules
|
||||
shell: bash
|
||||
run: |
|
||||
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
|
||||
git submodule sync --recursive
|
||||
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
|
||||
|
||||
- name: Checkout app
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@v1
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpunit
|
||||
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite
|
||||
|
||||
- name: Checkout app
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: App code check
|
||||
run: php occ app:check-code ${{ env.APP_NAME }}
|
||||
38
.github/workflows/appbuild.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Package build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v1
|
||||
with:
|
||||
php-version: '7.4'
|
||||
tools: composer
|
||||
- name: install dependencies
|
||||
run: |
|
||||
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.12.2/krankerl_0.12.2_amd64.deb
|
||||
sudo dpkg -i krankerl_0.12.2_amd64.deb
|
||||
- name: package
|
||||
run: |
|
||||
uname -a
|
||||
RUST_BACKTRACE=1 krankerl --version
|
||||
RUST_BACKTRACE=1 krankerl package
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Deck app tarball
|
||||
path: build/artifacts/deck.tar.gz
|
||||
91
.github/workflows/integration.yml
vendored
@@ -1,91 +0,0 @@
|
||||
name: Integration tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- stable*
|
||||
|
||||
env:
|
||||
APP_NAME: deck
|
||||
|
||||
jobs:
|
||||
integration:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['7.4']
|
||||
databases: ['sqlite', 'mysql', 'pgsql']
|
||||
server-versions: ['master']
|
||||
|
||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
ports:
|
||||
- 4445:5432/tcp
|
||||
env:
|
||||
POSTGRES_USER: root
|
||||
POSTGRES_PASSWORD: rootpassword
|
||||
POSTGRES_DB: nextcloud
|
||||
options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5
|
||||
mysql:
|
||||
image: mariadb
|
||||
ports:
|
||||
- 4444:3306/tcp
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: rootpassword
|
||||
options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 5
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: nextcloud/server
|
||||
ref: ${{ matrix.server-versions }}
|
||||
|
||||
- name: Checkout submodules
|
||||
shell: bash
|
||||
run: |
|
||||
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
|
||||
git submodule sync --recursive
|
||||
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
|
||||
|
||||
- name: Checkout app
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpunit
|
||||
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql,
|
||||
coverage: none
|
||||
|
||||
- name: Set up PHPUnit
|
||||
working-directory: apps/${{ env.APP_NAME }}
|
||||
run: composer i
|
||||
|
||||
- name: Set up Nextcloud
|
||||
run: |
|
||||
if [ "${{ matrix.databases }}" = "mysql" ]; then
|
||||
export DB_PORT=4444
|
||||
elif [ "${{ matrix.databases }}" = "pgsql" ]; then
|
||||
export DB_PORT=4445
|
||||
fi
|
||||
mkdir data
|
||||
./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
|
||||
cat config/config.php
|
||||
./occ user:list
|
||||
./occ app:enable --force ${{ env.APP_NAME }}
|
||||
php -S localhost:8080 &
|
||||
|
||||
- name: Run behat
|
||||
working-directory: apps/${{ env.APP_NAME }}/tests/integration
|
||||
run: ./run.sh
|
||||
83
.github/workflows/lint.yml
vendored
@@ -1,83 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- stable*
|
||||
|
||||
jobs:
|
||||
php:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.2', '7.3', '7.4']
|
||||
|
||||
name: php${{ matrix.php-versions }} lint
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up php${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@v1
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: none
|
||||
- name: Lint
|
||||
run: composer run lint
|
||||
|
||||
php-cs-fixer:
|
||||
name: php-cs check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@master
|
||||
with:
|
||||
php-version: 7.4
|
||||
coverage: none
|
||||
- name: Install dependencies
|
||||
run: composer i
|
||||
- name: Run coding standards check
|
||||
run: composer run cs:check
|
||||
|
||||
node:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use node ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: ESLint
|
||||
run: npm run lint
|
||||
|
||||
stylelint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-versions: [12.x]
|
||||
|
||||
name: stylelint node${{ matrix.node-versions }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up node ${{ matrix.node-versions }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-versions: ${{ matrix.node-versions }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run stylelint
|
||||
62
.github/workflows/nightly.yml
vendored
@@ -1,62 +0,0 @@
|
||||
name: Package nightly
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- nightly
|
||||
schedule:
|
||||
- cron: '0 1 * * *' # run at 2 AM UTC
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v1
|
||||
with:
|
||||
php-version: '7.4'
|
||||
tools: composer
|
||||
- name: install dependencies
|
||||
run: |
|
||||
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.12.2/krankerl_0.12.2_amd64.deb
|
||||
sudo dpkg -i krankerl_0.12.2_amd64.deb
|
||||
- name: package
|
||||
run: |
|
||||
uname -a
|
||||
RUST_BACKTRACE=1 krankerl --version
|
||||
RUST_BACKTRACE=1 krankerl package
|
||||
- name: Set git config
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git tag -f nightly
|
||||
- name: Push tag
|
||||
uses: juliushaertl/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tags: true
|
||||
force: true
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: juliushaertl/action-release@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: nightly
|
||||
files: ./build/artifacts/deck.tar.gz
|
||||
name: Nightly build
|
||||
body: |
|
||||
Nightly release of deck
|
||||
draft: false
|
||||
prerelease: true
|
||||
overwrite: true
|
||||
32
.github/workflows/nodejs.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: Node CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: install dependencies
|
||||
run: |
|
||||
npm ci
|
||||
- name: build
|
||||
env:
|
||||
RELATIVE_CI_KEY: ${{ secrets.RELATIVE_CI_KEY }}
|
||||
RELATIVE_CI_SLUG: nextcloud/deck
|
||||
run: |
|
||||
mkdir -p js
|
||||
npm run build --if-present -- --profile --json | tail -n +6 > js/webpack-stats.json
|
||||
npx relative-ci-agent
|
||||
|
||||
|
||||
94
.github/workflows/phpunit.yml
vendored
@@ -1,94 +0,0 @@
|
||||
name: PHPUnit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- stable*
|
||||
|
||||
env:
|
||||
APP_NAME: deck
|
||||
|
||||
|
||||
jobs:
|
||||
integration:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['7.3', '7.4']
|
||||
databases: ['sqlite', 'mysql', 'pgsql']
|
||||
server-versions: ['master']
|
||||
|
||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
ports:
|
||||
- 4445:5432/tcp
|
||||
env:
|
||||
POSTGRES_USER: root
|
||||
POSTGRES_PASSWORD: rootpassword
|
||||
POSTGRES_DB: nextcloud
|
||||
options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5
|
||||
mysql:
|
||||
image: mariadb
|
||||
ports:
|
||||
- 4444:3306/tcp
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: rootpassword
|
||||
options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 5
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: nextcloud/server
|
||||
ref: ${{ matrix.server-versions }}
|
||||
|
||||
- name: Checkout submodules
|
||||
shell: bash
|
||||
run: |
|
||||
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
|
||||
git submodule sync --recursive
|
||||
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
|
||||
|
||||
- name: Checkout app
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpunit
|
||||
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql
|
||||
coverage: none
|
||||
|
||||
- name: Set up PHPUnit
|
||||
working-directory: apps/${{ env.APP_NAME }}
|
||||
run: composer i
|
||||
|
||||
- name: Set up Nextcloud
|
||||
run: |
|
||||
if [ "${{ matrix.databases }}" = "mysql" ]; then
|
||||
export DB_PORT=4444
|
||||
elif [ "${{ matrix.databases }}" = "pgsql" ]; then
|
||||
export DB_PORT=4445
|
||||
fi
|
||||
mkdir data
|
||||
./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
|
||||
./occ app:enable --force ${{ env.APP_NAME }}
|
||||
php -S localhost:8080 &
|
||||
|
||||
- name: PHPUnit
|
||||
working-directory: apps/${{ env.APP_NAME }}
|
||||
run: ./vendor/phpunit/phpunit/phpunit -c tests/phpunit.xml
|
||||
|
||||
- name: PHPUnit integration
|
||||
working-directory: apps/${{ env.APP_NAME }}
|
||||
run: ./vendor/phpunit/phpunit/phpunit -c tests/phpunit.integration.xml
|
||||
31
.github/workflows/static-analysis.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: Static analysis
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- stable*
|
||||
|
||||
jobs:
|
||||
static-psalm-analysis:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
ocp-version: [ 'dev-master' ]
|
||||
name: Nextcloud ${{ matrix.ocp-version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@master
|
||||
with:
|
||||
php-version: 7.4
|
||||
tools: composer:v1
|
||||
coverage: none
|
||||
- name: Install dependencies
|
||||
run: composer i
|
||||
- name: Install dependencies
|
||||
run: composer require --dev christophwurst/nextcloud:${{ matrix.ocp-version }}
|
||||
- name: Run coding standards check
|
||||
run: composer run psalm
|
||||
10
.gitignore
vendored
@@ -1,11 +1,13 @@
|
||||
node_modules/*
|
||||
js/
|
||||
js/node_modules/*
|
||||
js/vendor/
|
||||
js/public/
|
||||
js/build/
|
||||
build/
|
||||
css/style.css
|
||||
css/vendor.css
|
||||
tests/integration/vendor/
|
||||
tests/integration/composer.lock
|
||||
tests/.phpunit.result.cache
|
||||
vendor/
|
||||
.php_cs.cache
|
||||
*.lock
|
||||
|
||||
\.idea/
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/build/
|
||||
/.git
|
||||
/.github
|
||||
/docs/
|
||||
/tests
|
||||
/babel.config.js
|
||||
/.editorconfig
|
||||
/.eslintrc.js
|
||||
/.nextcloudignore
|
||||
/webpack.*.js
|
||||
/.codecov.yml
|
||||
/composer.json
|
||||
/composer.lock
|
||||
/_config.yml
|
||||
/.drone.yml
|
||||
/.travis.yml
|
||||
/.eslintignore
|
||||
/.eslintrc.yml
|
||||
/.gitignore
|
||||
/issue_template.md
|
||||
/krankerl.toml
|
||||
/Makefile
|
||||
/mkdocs.yml
|
||||
/run-eslint.sh
|
||||
/package.json
|
||||
/package-lock.json
|
||||
/node_modules/
|
||||
/src/
|
||||
19
.php_cs.dist
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once './vendor/autoload.php';
|
||||
|
||||
use Nextcloud\CodingStandard\Config;
|
||||
|
||||
$config = new Config();
|
||||
$config
|
||||
->getFinder()
|
||||
// ->ignoreVCSIgnored(true)
|
||||
->notPath('build')
|
||||
->notPath('l10n')
|
||||
->notPath('src')
|
||||
->notPath('node_modules')
|
||||
->notPath('vendor')
|
||||
->in(__DIR__);
|
||||
return $config;
|
||||
37
.travis.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
language: php
|
||||
services:
|
||||
- mysql
|
||||
php:
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
env:
|
||||
- CORE_BRANCH=master DB=mysql
|
||||
|
||||
before_install:
|
||||
- wget https://phar.phpunit.de/phpunit-6.5.phar
|
||||
- chmod +x phpunit-6.5.phar
|
||||
- mkdir bin
|
||||
- mv phpunit-6.5.phar bin/phpunit
|
||||
- export PATH="$PWD/bin:$PATH"
|
||||
- phpunit --version
|
||||
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
|
||||
- bash ./before_install.sh deck $CORE_BRANCH $DB
|
||||
- cd ../server
|
||||
- ./occ app:enable deck
|
||||
|
||||
before_script:
|
||||
- cd apps/deck
|
||||
|
||||
script:
|
||||
- composer install
|
||||
- make test-unit
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
after_failure:
|
||||
- cat ../../data/nextcloud.log
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
250
CHANGELOG.md
@@ -1,257 +1,9 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 1.3.0 - unreleased
|
||||
|
||||
### Added
|
||||
* [#2638](https://github.com/nextcloud/deck/pull/2638) Sharing files to cards
|
||||
* [#2683](https://github.com/nextcloud/deck/pull/2683) Handle clicks on calendar entries
|
||||
* Nextcloud 21 compatiblity
|
||||
|
||||
### Fixed
|
||||
* [#2622](https://github.com/nextcloud/deck/pull/2622) Fix gradient and stack header spacing for safari
|
||||
* [#2626](https://github.com/nextcloud/deck/pull/2626) Adding a description icon to cards when they contain a description without any checkmarks @MonkeySon
|
||||
* [#2659](https://github.com/nextcloud/deck/pull/2659) Matching color of description cursor with text color @JonFStr
|
||||
* [#2676](https://github.com/nextcloud/deck/pull/2676) Only load filter view when shown
|
||||
* [#2680](https://github.com/nextcloud/deck/pull/2680) Do not try to add change data if it doesn't exist
|
||||
* [#2681](https://github.com/nextcloud/deck/pull/2681) Filter out deleted stacks from results
|
||||
* [#2685](https://github.com/nextcloud/deck/pull/2685) Show all boards in move card dialog @jakobroehrl
|
||||
* [#2687](https://github.com/nextcloud/deck/pull/2687) 3dots no opacity @jakobroehrl
|
||||
* [#2688](https://github.com/nextcloud/deck/pull/2688) Title > boardname @jakobroehrl
|
||||
* [#2689](https://github.com/nextcloud/deck/pull/2689) Modal > bigger view wording @jakobroehrl
|
||||
|
||||
## 1.3.0-beta2
|
||||
|
||||
### Fixed
|
||||
* [#2700](https://github.com/nextcloud/deck/pull/2700) Attempt to copy file on dropping it to deck @juliushaertl
|
||||
* [#2701](https://github.com/nextcloud/deck/pull/2701) Fix uploading files by drag and drop @juliushaertl
|
||||
* [#2707](https://github.com/nextcloud/deck/pull/2707) L10n: Change to a capital letter @Valdnet
|
||||
* [#2712](https://github.com/nextcloud/deck/pull/2712) Docs: Fix table in section "GET /api/v1.0/config" @das-g
|
||||
* [#2716](https://github.com/nextcloud/deck/pull/2716) Remove repair step which is no longer needed as we cleanup properly @juliushaertl
|
||||
* [#2723](https://github.com/nextcloud/deck/pull/2723) Pad random color with leading zeroes @PVince81
|
||||
* [#2729](https://github.com/nextcloud/deck/pull/2729) Remove invalid activity parameters @nickvergessen
|
||||
* [#2750](https://github.com/nextcloud/deck/pull/2750) Fix deck activity emails not being translated @nickvergessen
|
||||
* [#2751](https://github.com/nextcloud/deck/pull/2751) Properly set author for activity events that are triggered by cron @juliushaertl
|
||||
|
||||
|
||||
## 1.2.2 - 2020-11-24
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#2584](https://github.com/nextcloud/deck/pull/2584) Fix updating checkbox state and avoid issues due to duplicate sidebar element
|
||||
* [#2586](https://github.com/nextcloud/deck/pull/2586) Fix card details button
|
||||
* [#2587](https://github.com/nextcloud/deck/pull/2587) Move modal top spacing to the header to avoid side-effect when scrolling
|
||||
* [#2588](https://github.com/nextcloud/deck/pull/2588) Do not render images in editor
|
||||
* [#2609](https://github.com/nextcloud/deck/pull/2609) Fix issue with depenendency causing newline comments to not show
|
||||
* [#2611](https://github.com/nextcloud/deck/pull/2611) Fix paragraph styling in comments
|
||||
|
||||
## 1.2.1 - 2020-11-18
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#2570](https://github.com/nextcloud/deck/pull/2570) [#2571](https://github.com/nextcloud/deck/pull/2571) Fix error when deleting users @ksteinb
|
||||
* [#2573](https://github.com/nextcloud/deck/pull/2573) Fix issue where card description was changed on the wrong card when switching cards
|
||||
|
||||
## 1.2.0 - 2020-11-16
|
||||
|
||||
### Added
|
||||
|
||||
* [#2430](https://github.com/nextcloud/deck/pull/2430) Due date notification setting per board
|
||||
* [#2230](https://github.com/nextcloud/deck/pull/2230) Implement scrolling per stack
|
||||
* [#1396](https://github.com/nextcloud/deck/pull/1396) API: Expose canCreateBoards through capabilities
|
||||
* [#2245](https://github.com/nextcloud/deck/pull/2245) API: ETag support for API endpoints
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#2330](https://github.com/nextcloud/deck/pull/2330) Enhanced undo handling for deletions @jakobroehrl
|
||||
* [#2336](https://github.com/nextcloud/deck/pull/2336) Run unit tests on github actions
|
||||
* [#2358](https://github.com/nextcloud/deck/pull/2358) Properly check if FTSEvent has an argument set
|
||||
* [#2359](https://github.com/nextcloud/deck/pull/2359) Also exclude deleted items from calendar boards
|
||||
* [#2361](https://github.com/nextcloud/deck/pull/2361) Comments do not depend on the comments app @jakobroehrl
|
||||
* [#2363](https://github.com/nextcloud/deck/pull/2363) Use uid instead of displayname for sharee results
|
||||
* [#2367](https://github.com/nextcloud/deck/pull/2367) Properly handle multiple shares in a row and refactor sharee loading
|
||||
* [#2404](https://github.com/nextcloud/deck/pull/2404) Update Controls.vue @Flamenco
|
||||
* [#2433](https://github.com/nextcloud/deck/pull/2433) Fix scrollable titles with Dyslexia font
|
||||
* [#2434](https://github.com/nextcloud/deck/pull/2434) Move most destructive actions in drop down menus to the bottom @Nienzu
|
||||
* [#2435](https://github.com/nextcloud/deck/pull/2435) Do not open the dialog automatically upon card creation, only upon click
|
||||
* [#2437](https://github.com/nextcloud/deck/pull/2437) Only remove card padding for editable cards
|
||||
* [#2440](https://github.com/nextcloud/deck/pull/2440) Move navigation toggle handling to @nextcloud/vue native one
|
||||
* [#2463](https://github.com/nextcloud/deck/pull/2463) Changed triple dots to ellipsis @rakekniven
|
||||
* [#2500](https://github.com/nextcloud/deck/pull/2500) Move details and description to dedicated component
|
||||
* [#2517](https://github.com/nextcloud/deck/pull/2517) Filter out duplicate cards in overview
|
||||
* [#2502](https://github.com/nextcloud/deck/pull/2502) Assignment code refactoring
|
||||
* [#2519](https://github.com/nextcloud/deck/pull/2519) Fix invisibility bug on modal component @wrox
|
||||
* [#2520](https://github.com/nextcloud/deck/pull/2520) Add placeholder for the description input
|
||||
* [#2521](https://github.com/nextcloud/deck/pull/2521) Add migration step to make table layout consistent
|
||||
* [#2524](https://github.com/nextcloud/deck/pull/2524) Only try to extract first part of the explode result
|
||||
* [#2531](https://github.com/nextcloud/deck/pull/2531) Add proper type to boolean parameter
|
||||
* [#2532](https://github.com/nextcloud/deck/pull/2532) Fix handling of notifications if a board does no longer exist
|
||||
* [#2536](https://github.com/nextcloud/deck/pull/2536) Only set flex layout on the active tab
|
||||
* [#2538](https://github.com/nextcloud/deck/pull/2538) Do not reset filter when staying on the same board
|
||||
* [#2539](https://github.com/nextcloud/deck/pull/2539) Apply proper checks for menu items
|
||||
* [#2540](https://github.com/nextcloud/deck/pull/2540) Only build one main bundle
|
||||
* [#2562](https://github.com/nextcloud/deck/pull/2562) Only try to extract first part of the explode result (Part 2)
|
||||
|
||||
|
||||
## 1.1.0 - 2020-10-03
|
||||
|
||||
### Features
|
||||
|
||||
* [#2115](https://github.com/nextcloud/deck/pull/2115) Dashboard widget for Nextcloud 20
|
||||
* [#1545](https://github.com/nextcloud/deck/pull/1545) Show cards in calendar/tasks app and make them available though CalDAV
|
||||
* [#2200](https://github.com/nextcloud/deck/pull/2200) Unified search implementation for Nextcloud 20
|
||||
* [#1934](https://github.com/nextcloud/deck/pull/1934) Upcoming cards overview @jakobroehrl
|
||||
* [#2047](https://github.com/nextcloud/deck/pull/2047) Show card details in modal @jakobroehrl
|
||||
* [#1853](https://github.com/nextcloud/deck/pull/1853) Archive all cards from stack @jakobroehrl
|
||||
* [#1865](https://github.com/nextcloud/deck/pull/1865) Add stack button on empty board @jakobroehrl
|
||||
* [#1926](https://github.com/nextcloud/deck/pull/1926) New filter: unassigned cards @jakobroehrl
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* [#2035](https://github.com/nextcloud/deck/pull/2035) Attach files in description @jakobroehrl
|
||||
* [#2123](https://github.com/nextcloud/deck/pull/2123) Fix control tooltip @jakobroehrl
|
||||
* [#2144](https://github.com/nextcloud/deck/pull/2144) Fix nextcloud if install with dev dependencies @matchish
|
||||
* [#2158](https://github.com/nextcloud/deck/pull/2158) Fix description in dark mode
|
||||
* [#2188](https://github.com/nextcloud/deck/pull/2188) CardBadges: Count checkboxes started with "+ [ ]" @joreiff
|
||||
* [#2206](https://github.com/nextcloud/deck/pull/2206) Fix read-only sidebar (fixes #2033)
|
||||
* [#2208](https://github.com/nextcloud/deck/pull/2208) Fix design, dark mode and keyboard navigation of the board list
|
||||
* [#2210](https://github.com/nextcloud/deck/pull/2210) Fix an incorrect/misleading message in lib/Service/BoardService.php @jordanbancino
|
||||
* [#2243](https://github.com/nextcloud/deck/pull/2243) Various smaller styling fixes
|
||||
* [#2244](https://github.com/nextcloud/deck/pull/2244) Toggle filter on clicking card labels
|
||||
* [#2117](https://github.com/nextcloud/deck/pull/2117) Activity fixes
|
||||
* [#2255](https://github.com/nextcloud/deck/pull/2255) Use unified search events to apply on board filtering
|
||||
* [#2271](https://github.com/nextcloud/deck/pull/2271) Sort tags in filter @jakobroehrl
|
||||
* [#2318](https://github.com/nextcloud/deck/pull/2318) Card title: prevent space and no text @jakobroehrl
|
||||
* [#2319](https://github.com/nextcloud/deck/pull/2319) Move style loading to BeforeTemplateRenderedEvent
|
||||
* [#2320](https://github.com/nextcloud/deck/pull/2320) Consistent naming @jakobroehrl
|
||||
* [#2252](https://github.com/nextcloud/deck/pull/2252) Fix double slash in the deck activity links @baraksoa
|
||||
* [#2270](https://github.com/nextcloud/deck/pull/2270) Fix empty content view to align with other widgets
|
||||
* [#2275](https://github.com/nextcloud/deck/pull/2275) Wait for services to be registered before performing further setup that requires services
|
||||
* [#2278](https://github.com/nextcloud/deck/pull/2278) Fix wrong SQL queries @Chartman123
|
||||
* [#2279](https://github.com/nextcloud/deck/pull/2279) L10n:add translation to card placeholder @mjanssens
|
||||
* [#2282](https://github.com/nextcloud/deck/pull/2282) Duedate picker localization
|
||||
* [#2283](https://github.com/nextcloud/deck/pull/2283) Do not handle exceptions from page controller in the ExceptionMiddleware
|
||||
* [#2298](https://github.com/nextcloud/deck/pull/2298) Use absolute URLs for the search @nickvergessen
|
||||
|
||||
|
||||
|
||||
## 1.0.5 - 2020-07-15
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
* [#2116](https://github.com/nextcloud/deck/pull/2116) Fix navigation layout issues @juliushaertl
|
||||
* [#2118](https://github.com/nextcloud/deck/pull/2118) Use proper parameter when handling attachments @juliushaertl
|
||||
|
||||
## 1.0.4 - 2020-06-26
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#2062](https://github.com/nextcloud/deck/pull/2062) Fix saving card description after toggling checkboxes @juliushaertl
|
||||
* [#2065](https://github.com/nextcloud/deck/pull/2065) Adding CSS rule for Markdown Blockquotes @reox
|
||||
* [#2059](https://github.com/nextcloud/deck/pull/2059) Fix fetching attachments on card change @juliushaertl
|
||||
* [#2060](https://github.com/nextcloud/deck/pull/2060) Use mixing for relative date in card sidebar @juliushaertl
|
||||
|
||||
|
||||
## 1.0.3 - 2020-06-19
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#2019](https://github.com/nextcloud/deck/pull/2019) Remove old global css rule @juliushaertl
|
||||
* [#2020](https://github.com/nextcloud/deck/pull/2020) Fix navigation issue with leftover nodes @juliushaertl
|
||||
* [#2021](https://github.com/nextcloud/deck/pull/2021) Fix description issues @juliushaertl
|
||||
* [#2022](https://github.com/nextcloud/deck/pull/2022) Fix replyto issues with the comments API @juliushaertl
|
||||
* [#2027](https://github.com/nextcloud/deck/pull/2027) Allow to unassign current user from card @juliushaertl
|
||||
* [#2029](https://github.com/nextcloud/deck/pull/2029) Fix wording : stack -> list @cloud2018
|
||||
* [#2032](https://github.com/nextcloud/deck/pull/2032) Force order by id as second sorting key @juliushaertl
|
||||
* [#2045](https://github.com/nextcloud/deck/pull/2045) Improve label styling @juliushaertl
|
||||
* [#2010](https://github.com/nextcloud/deck/pull/2010) User documentation fixes @Nyco
|
||||
* [#1998](https://github.com/nextcloud/deck/pull/1998) Add Checklist explaination to the doc @4rnoP
|
||||
|
||||
|
||||
## 1.0.2 - 2020-06-03
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#1774](https://github.com/nextcloud/deck/pull/1774) Remove deprecated global API calls
|
||||
* [#1918](https://github.com/nextcloud/deck/pull/1918) Save compact mode on localstorage @jakobroehrl
|
||||
* [#1919](https://github.com/nextcloud/deck/pull/1919) Show sidebar after card creation @jakobroehrl
|
||||
* [#1924](https://github.com/nextcloud/deck/pull/1924) Boards ordered in main page @jakobroehrl
|
||||
* [#1925](https://github.com/nextcloud/deck/pull/1925) Fix generated fronted urls
|
||||
* [#1944](https://github.com/nextcloud/deck/pull/1944) Move navigation to @nextcloud/vue components
|
||||
* [#1945](https://github.com/nextcloud/deck/pull/1945) Fix datetime picker
|
||||
* [#1946](https://github.com/nextcloud/deck/pull/1946) Fix translations
|
||||
* [#1976](https://github.com/nextcloud/deck/pull/1976) Delete boards that users own once they are deleted
|
||||
* [#1977](https://github.com/nextcloud/deck/pull/1977) Redirect from previously used routes to the current ones
|
||||
|
||||
## 1.0.1 - 2020-05-15
|
||||
|
||||
### Fixed
|
||||
|
||||
* Removes debug filter output
|
||||
* Labels are now sorted
|
||||
* Stack title doesn't break up
|
||||
* Fix move card modal
|
||||
* Sort boards in navigation
|
||||
* Fixes the attachment modal
|
||||
* Handle deleted boards better
|
||||
* User can only clone a board on canManage permissions
|
||||
* Fix modal imports
|
||||
* Show menu in compact mode
|
||||
* Added a filter reset button
|
||||
* Add hover effect to board list
|
||||
* New filter icon
|
||||
* Improve hovering response in board
|
||||
* Enable linkify in description renderer @icewind1991
|
||||
* Enhance board selector
|
||||
* Fix issue if card description might be null
|
||||
* Revert markdown styles from old frontend
|
||||
* Do not scroll cards into view
|
||||
* Fix reodering performance
|
||||
|
||||
## 1.0.0 - 2020-05-06
|
||||
|
||||
### Added
|
||||
|
||||
- Completly rewritten frontend
|
||||
- Better maintainability
|
||||
- Various small fixes
|
||||
- Unified user interface with Nextcloud
|
||||
- Separate comment and activity timelines
|
||||
- Add ability to reply to comments #1537
|
||||
- Filter cards on board #1507 @jakobroehrl
|
||||
- Add cards to projects #1294 @jakobroehrl
|
||||
- Move cards to other boards #1242 @jakobroehrl
|
||||
- Clone boards with existing stacks and labels #1221 @jakobroehrl
|
||||
- Upload multiple files at once and in parallel
|
||||
|
||||
A huge thangs goes to our awesome community that put enourmous effort into the frontend migration:
|
||||
|
||||
Special thanks for contributing huge parts of the Vue.js migration:
|
||||
@jakobroehrl @weeman1337 @nicolad
|
||||
|
||||
Testers/reporters:
|
||||
@cloud2018 @putt1ck @bpcurse
|
||||
|
||||
Android app team for helping to improve our REST API:
|
||||
@desperateCoder @stefan-niedermann
|
||||
|
||||
## 0.8.0 - 2020-01-16
|
||||
|
||||
### Added
|
||||
- Case insensitive search (@matchish)
|
||||
|
||||
### Fixed
|
||||
- Fix reversed permissions for reordering stacks (@JLueke)
|
||||
- Fix reversed visibility of 'add stack' field (@JLueke)
|
||||
- Fix occ export command
|
||||
- Fix error causing cron execution to fail
|
||||
- Fix activity entry on moving cards
|
||||
- Proper wording in activity timeline (@a11exandru)
|
||||
|
||||
## 0.7.0 - 2019-08-20
|
||||
|
||||
### Added
|
||||
## Added
|
||||
- Make deck compatible to Nextcloud 17
|
||||
- Allow to set the description when creating cards though the REST API
|
||||
|
||||
|
||||
69
Makefile
@@ -12,32 +12,70 @@ sign_dir=$(build_dir)/sign
|
||||
cert_dir=$(HOME)/.nextcloud/certificates
|
||||
|
||||
|
||||
default: build
|
||||
default: package
|
||||
|
||||
clean-build:
|
||||
rm -rf $(build_dir)
|
||||
|
||||
clean-dist:
|
||||
rm -rf node_modules/
|
||||
rm -rf js/node_modules
|
||||
|
||||
install-deps: install-deps-js
|
||||
composer install
|
||||
|
||||
install-deps-nodev: install-deps-js
|
||||
composer install --no-dev
|
||||
|
||||
install-deps-js:
|
||||
npm ci
|
||||
cd js && npm install
|
||||
|
||||
build: clean-dist install-deps build-js
|
||||
|
||||
release: clean-dist install-deps-nodev build-js
|
||||
build: install-deps build-js
|
||||
|
||||
build-js: install-deps-js
|
||||
npm run build
|
||||
cd js && npm run build
|
||||
|
||||
build-js-dev: install-deps
|
||||
npm run dev
|
||||
cd js && npm run dev
|
||||
|
||||
watch:
|
||||
npm run watch
|
||||
cd js && npm run watch
|
||||
|
||||
# appstore: clean install-deps
|
||||
appstore: clean-build build
|
||||
rm -rf $(appstore_build_directory)
|
||||
mkdir -p $(appstore_build_directory)
|
||||
tar cvzf $(appstore_package_name).tar.gz \
|
||||
--exclude="../$(app_name)/build" \
|
||||
--exclude="../$(app_name)/tests" \
|
||||
--exclude="../$(app_name)/Makefile" \
|
||||
--exclude="../$(app_name)/*.log" \
|
||||
--exclude="../$(app_name)/phpunit*xml" \
|
||||
--exclude="../$(app_name)/composer.*" \
|
||||
--exclude="../$(app_name)/js/node_modules" \
|
||||
--exclude="../$(app_name)/js/tests" \
|
||||
--exclude="../$(app_name)/js/test" \
|
||||
--exclude="../$(app_name)/js/*.log" \
|
||||
--exclude="../$(app_name)/js/package-lock.json" \
|
||||
--exclude="../$(app_name)/js/package.json" \
|
||||
--exclude="../$(app_name)/js/bower.json" \
|
||||
--exclude="../$(app_name)/js/karma.*" \
|
||||
--exclude="../$(app_name)/js/protractor.*" \
|
||||
--exclude="../$(app_name)/package.json" \
|
||||
--exclude="../$(app_name)/bower.json" \
|
||||
--exclude="../$(app_name)/karma.*" \
|
||||
--exclude="../$(app_name)/protractor\.*" \
|
||||
--exclude="../$(app_name)/.*" \
|
||||
--exclude="../$(app_name)/*.lock" \
|
||||
--exclude="../$(app_name)/run-eslint.sh" \
|
||||
--exclude="../$(app_name)/js/.*" \
|
||||
--exclude="../$(app_name)/vendor" \
|
||||
--exclude-vcs \
|
||||
../$(app_name)
|
||||
|
||||
|
||||
@if [ -f $(cert_dir)/$(app_name).key ]; then \
|
||||
echo "Signing package…"; \
|
||||
openssl dgst -sha512 -sign $(cert_dir)/$(app_name).key $(build_dir)/$(app_name).tar.gz | openssl base64; \
|
||||
fi
|
||||
|
||||
echo $(appstore_package_name).tar.gz
|
||||
|
||||
test: test-unit test-integration
|
||||
|
||||
@@ -46,7 +84,7 @@ test-unit:
|
||||
ifeq (, $(shell which phpunit 2> /dev/null))
|
||||
@echo "No phpunit command available, downloading a copy from the web"
|
||||
mkdir -p $(build_tools_directory)
|
||||
curl -sSL https://phar.phpunit.de/phpunit-8.2.phar -o $(build_tools_directory)/phpunit.phar
|
||||
curl -sSL https://phar.phpunit.de/phpunit-5.7.phar -o $(build_tools_directory)/phpunit.phar
|
||||
php $(build_tools_directory)/phpunit.phar -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
|
||||
php $(build_tools_directory)/phpunit.phar -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
|
||||
else
|
||||
@@ -58,4 +96,7 @@ test-integration:
|
||||
cd tests/integration && ./run.sh
|
||||
|
||||
test-js: install-deps
|
||||
npm run test
|
||||
cd js && run test
|
||||
|
||||
package:
|
||||
krankerl package
|
||||
|
||||
46
README.md
@@ -5,21 +5,18 @@
|
||||
|
||||
Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.
|
||||
|
||||
- Add your tasks to cards and put them in order
|
||||
- Write down additional notes in markdown
|
||||
- Assign labels for even better organization
|
||||
- Share with your team, friends or family
|
||||
- Integrates with the [Circles](https://github.com/nextcloud/circles) app!
|
||||
- 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
|
||||
- :inbox_tray: Add your tasks to cards and put them in order
|
||||
- :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
|
||||
|
||||
### Mobile apps
|
||||
|
||||
- The [Nextcloud Deck app for Android](https://github.com/stefan-niedermann/nextcloud-deck) is available in the [Google Play Store](https://play.google.com/store/apps/details?id=it.niedermann.nextcloud.deck.play)
|
||||
|
||||

|
||||

|
||||
|
||||
## Installation/Update
|
||||
|
||||
@@ -44,7 +41,7 @@ Please make sure you have installed the following dependencies: `make, which, ta
|
||||
|
||||
### Install the nightly builds
|
||||
|
||||
Instead of setting everything up manually, you can just [download the nightly build](https://github.com/nextcloud/deck/releases/tag/nightly) instead. These builds are updated every 24 hours, and are pre-configured with all the needed dependencies.
|
||||
Instead of setting everything up manually, you can just [download the nightly builds](https://download.bitgrid.net/nextcloud/deck/nightly/) instead. These builds are updated every 24 hours, and are pre-configured with all the needed dependencies.
|
||||
|
||||
## Developing
|
||||
|
||||
@@ -56,27 +53,6 @@ Nothing to prepare, just dig into the code.
|
||||
|
||||
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.
|
||||
|
||||
#### Hot reloading
|
||||
|
||||
Enable debug mode in your config.php `'debug' => true,`
|
||||
|
||||
Without SSL:
|
||||
```
|
||||
npx webpack-dev-server --config webpack.hot.js \
|
||||
--public localhost:3000 \
|
||||
--output-public-path 'http://localhost:3000/js/'
|
||||
```
|
||||
|
||||
With SSL:
|
||||
```
|
||||
npx webpack-dev-server --config webpack.dev.js --https \
|
||||
--cert ~/repos/nextcloud/nc-dev/data/ssl/nextcloud.local.crt \
|
||||
--key ~/repos/nextcloud/nc-dev/data/ssl/nextcloud.local.key \
|
||||
--public nextcloud.local:3000 \
|
||||
--output-public-path 'https://nextcloud.local:3000/js/'
|
||||
```
|
||||
|
||||
|
||||
### Running tests
|
||||
You can use the provided Makefile to run all tests by using:
|
||||
|
||||
|
||||
29
SECURITY.md
@@ -1,29 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Nextcloud version | Supported |
|
||||
| ------- | ----------------- | ------------------ |
|
||||
| 1.0.x | 18, 19 | :white_check_mark: |
|
||||
| 0.8.x | 17 | :white_check_mark: |
|
||||
| <0.7.x | - | :x: |
|
||||
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Security is very important to us. If you have discovered a security issue with Nextcloud,
|
||||
please read our responsible disclosure guidelines and contact us at [hackerone.com/nextcloud](https://hackerone.com/nextcloud).
|
||||
Your report should include:
|
||||
|
||||
- Product version
|
||||
- A vulnerability description
|
||||
- Reproduction steps
|
||||
|
||||
A member of the security team will confirm the vulnerability, determine its impact, and develop a fix.
|
||||
The fix will be applied to the master branch, tested, and packaged in the next security release.
|
||||
The vulnerability will be publicly announced after the release. Finally, your name will be added
|
||||
to the [hall of fame](https://hackerone.com/nextcloud/thanks) as a thank you from the entire Nextcloud community. Note our
|
||||
[threat model](https://nextcloud.com/security/threat-model) to know what is expected behavior.
|
||||
|
||||
|
||||
Please visit https://nextcloud.com/security/ for further information about security.
|
||||
35
appinfo/app.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
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');
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
namespace OCA\Deck\AppInfo;
|
||||
|
||||
use OCP\AppFramework\App;
|
||||
|
||||
/**
|
||||
* Additional autoloader registration, e.g. registering composer autoloaders
|
||||
*/
|
||||
|
||||
482
appinfo/database.xml
Normal file
@@ -0,0 +1,482 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<database>
|
||||
<name>*dbname*</name>
|
||||
<create>true</create>
|
||||
<overwrite>false</overwrite>
|
||||
<charset>utf8</charset>
|
||||
<table>
|
||||
<name>*dbprefix*deck_boards</name>
|
||||
<declaration>
|
||||
<field>
|
||||
<name>id</name>
|
||||
<type>integer</type>
|
||||
<default>0</default>
|
||||
<notnull>true</notnull>
|
||||
<autoincrement>1</autoincrement>
|
||||
<length>4</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>title</name>
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>100</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>owner</name>
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>color</name>
|
||||
<type>text</type>
|
||||
<length>6</length>
|
||||
<notnull>false</notnull>
|
||||
</field>
|
||||
<field>
|
||||
<name>archived</name>
|
||||
<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>
|
||||
<field>
|
||||
<name>last_modified</name>
|
||||
<type>integer</type>
|
||||
<default></default>
|
||||
<notnull>false</notnull>
|
||||
<unsigned>true</unsigned>
|
||||
</field>
|
||||
</declaration>
|
||||
</table>
|
||||
<table>
|
||||
<name>*dbprefix*deck_stacks</name>
|
||||
<declaration>
|
||||
<field>
|
||||
<name>id</name>
|
||||
<type>integer</type>
|
||||
<default>0</default>
|
||||
<notnull>true</notnull>
|
||||
<autoincrement>1</autoincrement>
|
||||
<length>4</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>title</name>
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>100</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>board_id</name>
|
||||
<type>integer</type>
|
||||
<length>8</length>
|
||||
<notnull>true</notnull>
|
||||
</field>
|
||||
<field>
|
||||
<name>order</name>
|
||||
<type>integer</type>
|
||||
<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>
|
||||
<name>board_id</name>
|
||||
</field>
|
||||
</index>
|
||||
<index>
|
||||
<name>deck_stacks_order_index</name>
|
||||
<field>
|
||||
<name>order</name>
|
||||
</field>
|
||||
</index>
|
||||
</declaration>
|
||||
</table>
|
||||
<table>
|
||||
<name>*dbprefix*deck_cards</name>
|
||||
<declaration>
|
||||
<field>
|
||||
<name>id</name>
|
||||
<type>integer</type>
|
||||
<default>0</default>
|
||||
<notnull>true</notnull>
|
||||
<autoincrement>1</autoincrement>
|
||||
<length>4</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>title</name>
|
||||
<type>text</type>
|
||||
<length>100</length>
|
||||
<notnull>true</notnull>
|
||||
</field>
|
||||
<field>
|
||||
<name>description</name>
|
||||
<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>
|
||||
<length>8</length>
|
||||
<notnull>true</notnull>
|
||||
</field>
|
||||
<field>
|
||||
<name>type</name>
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>64</length>
|
||||
<default>plain</default>
|
||||
</field>
|
||||
<field>
|
||||
<name>last_modified</name>
|
||||
<type>integer</type>
|
||||
<default></default>
|
||||
<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>
|
||||
<default></default>
|
||||
<notnull>false</notnull>
|
||||
<unsigned>true</unsigned>
|
||||
</field>
|
||||
<field>
|
||||
<name>owner</name>
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>order</name>
|
||||
<type>integer</type>
|
||||
<length>8</length>
|
||||
<notnull>false</notnull>
|
||||
</field>
|
||||
<field>
|
||||
<name>archived</name>
|
||||
<type>boolean</type>
|
||||
<default>false</default>
|
||||
</field>
|
||||
<field>
|
||||
<name>duedate</name>
|
||||
<type>timestamp</type>
|
||||
<default>0</default>
|
||||
</field>
|
||||
<field>
|
||||
<name>notified</name>
|
||||
<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>
|
||||
<name>stack_id</name>
|
||||
</field>
|
||||
</index>
|
||||
<index>
|
||||
<name>deck_cards_order_index</name>
|
||||
<field>
|
||||
<name>order</name>
|
||||
</field>
|
||||
</index>
|
||||
<index>
|
||||
<name>deck_cards_archived_index</name>
|
||||
<field>
|
||||
<name>archived</name>
|
||||
</field>
|
||||
</index>
|
||||
</declaration>
|
||||
</table>
|
||||
<table>
|
||||
<name>*dbprefix*deck_attachment</name>
|
||||
<declaration>
|
||||
<field>
|
||||
<name>id</name>
|
||||
<type>integer</type>
|
||||
<default>0</default>
|
||||
<notnull>true</notnull>
|
||||
<autoincrement>1</autoincrement>
|
||||
<length>4</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>card_id</name>
|
||||
<type>integer</type>
|
||||
<length>8</length>
|
||||
<notnull>true</notnull>
|
||||
</field>
|
||||
<field>
|
||||
<name>type</name>
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>data</name>
|
||||
<type>text</type>
|
||||
</field>
|
||||
<field>
|
||||
<name>last_modified</name>
|
||||
<type>integer</type>
|
||||
<default/>
|
||||
<length>8</length>
|
||||
<notnull>false</notnull>
|
||||
<unsigned>true</unsigned>
|
||||
</field>
|
||||
<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>
|
||||
<length>8</length>
|
||||
<notnull>false</notnull>
|
||||
<unsigned>true</unsigned>
|
||||
</field>
|
||||
</declaration>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<name>*dbprefix*deck_labels</name>
|
||||
<declaration>
|
||||
<field>
|
||||
<name>id</name>
|
||||
<type>integer</type>
|
||||
<default>0</default>
|
||||
<notnull>true</notnull>
|
||||
<autoincrement>1</autoincrement>
|
||||
<length>4</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>title</name>
|
||||
<type>text</type>
|
||||
<notnull>false</notnull>
|
||||
<length>100</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>color</name>
|
||||
<type>text</type>
|
||||
<length>6</length>
|
||||
<notnull>false</notnull>
|
||||
</field>
|
||||
<field>
|
||||
<name>board_id</name>
|
||||
<type>integer</type>
|
||||
<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>
|
||||
<name>board_id</name>
|
||||
</field>
|
||||
</index>
|
||||
</declaration>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<name>*dbprefix*deck_assigned_labels</name>
|
||||
<declaration>
|
||||
<field>
|
||||
<name>id</name>
|
||||
<type>integer</type>
|
||||
<default>0</default>
|
||||
<notnull>true</notnull>
|
||||
<autoincrement>1</autoincrement>
|
||||
<length>4</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>label_id</name>
|
||||
<type>integer</type>
|
||||
<default>0</default>
|
||||
<notnull>true</notnull>
|
||||
<length>4</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>card_id</name>
|
||||
<type>integer</type>
|
||||
<default>0</default>
|
||||
<notnull>true</notnull>
|
||||
<length>4</length>
|
||||
</field>
|
||||
<index>
|
||||
<name>deck_assigned_labels_idx_i</name>
|
||||
<field>
|
||||
<name>label_id</name>
|
||||
</field>
|
||||
</index>
|
||||
<index>
|
||||
<name>deck_assigned_labels_idx_c</name>
|
||||
<field>
|
||||
<name>card_id</name>
|
||||
</field>
|
||||
</index>
|
||||
</declaration>
|
||||
</table>
|
||||
<table>
|
||||
<name>*dbprefix*deck_assigned_users</name>
|
||||
<declaration>
|
||||
<field>
|
||||
<name>id</name>
|
||||
<type>integer</type>
|
||||
<default>0</default>
|
||||
<notnull>true</notnull>
|
||||
<autoincrement>1</autoincrement>
|
||||
<length>4</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>participant</name>
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>card_id</name>
|
||||
<type>integer</type>
|
||||
<default>0</default>
|
||||
<notnull>true</notnull>
|
||||
<length>4</length>
|
||||
</field>
|
||||
<index>
|
||||
<name>deck_assigned_users_idx_p</name>
|
||||
<field>
|
||||
<name>participant</name>
|
||||
</field>
|
||||
</index>
|
||||
<index>
|
||||
<name>deck_assigned_users_idx_c</name>
|
||||
<field>
|
||||
<name>card_id</name>
|
||||
</field>
|
||||
</index>
|
||||
</declaration>
|
||||
</table>
|
||||
<table>
|
||||
<name>*dbprefix*deck_board_acl</name>
|
||||
<declaration>
|
||||
<field>
|
||||
<name>id</name>
|
||||
<type>integer</type>
|
||||
<default>0</default>
|
||||
<notnull>true</notnull>
|
||||
<autoincrement>1</autoincrement>
|
||||
<length>4</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>board_id</name>
|
||||
<type>integer</type>
|
||||
<notnull>true</notnull>
|
||||
<length>8</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>type</name>
|
||||
<type>integer</type>
|
||||
<notnull>true</notnull>
|
||||
<length>4</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>participant</name>
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>permission_edit</name>
|
||||
<type>boolean</type>
|
||||
<default>false</default>
|
||||
</field>
|
||||
<field>
|
||||
<name>permission_share</name>
|
||||
<type>boolean</type>
|
||||
<default>false</default>
|
||||
</field>
|
||||
<field>
|
||||
<name>permission_manage</name>
|
||||
<type>boolean</type>
|
||||
<default>false</default>
|
||||
</field>
|
||||
<index>
|
||||
<name>deck_board_acl_uq_i</name>
|
||||
<unique>true</unique>
|
||||
<field>
|
||||
<name>board_id</name>
|
||||
<sorting>ascending</sorting>
|
||||
</field>
|
||||
<field>
|
||||
<name>type</name>
|
||||
<sorting>ascending</sorting>
|
||||
</field>
|
||||
<field>
|
||||
<name>participant</name>
|
||||
<sorting>ascending</sorting>
|
||||
</field>
|
||||
</index>
|
||||
<index>
|
||||
<name>deck_board_acl_idx_i</name>
|
||||
<field>
|
||||
<name>board_id</name>
|
||||
</field>
|
||||
</index>
|
||||
</declaration>
|
||||
|
||||
</table>
|
||||
</database>
|
||||
@@ -3,7 +3,7 @@
|
||||
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
|
||||
<id>deck</id>
|
||||
<name>Deck</name>
|
||||
<summary>Personal planning and team project organization</summary>
|
||||
<summary>A kanban style project and personal management tool for Nextcloud</summary>
|
||||
<description>Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
- 🚀 Get your project organized
|
||||
|
||||
</description>
|
||||
<version>1.3.0-beta2</version>
|
||||
<version>0.7.0</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Julius Härtl</author>
|
||||
<namespace>Deck</namespace>
|
||||
@@ -29,20 +29,25 @@
|
||||
<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/1.0/Deck-1.png</screenshot>
|
||||
<screenshot>https://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-2.png</screenshot>
|
||||
<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>
|
||||
<dependencies>
|
||||
<php min-version="7.3"/>
|
||||
<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="21" max-version="21" />
|
||||
<nextcloud min-version="17" max-version="17" />
|
||||
</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>
|
||||
@@ -59,20 +64,9 @@
|
||||
<provider>OCA\Deck\Activity\DeckProvider</provider>
|
||||
</providers>
|
||||
</activity>
|
||||
|
||||
<fulltextsearch>
|
||||
<provider min-version="16">OCA\Deck\Provider\DeckProvider</provider>
|
||||
</fulltextsearch>
|
||||
<navigations>
|
||||
<navigation>
|
||||
<name>Deck</name>
|
||||
<route>deck.page.index</route>
|
||||
<icon>deck.svg</icon>
|
||||
<order>10</order>
|
||||
</navigation>
|
||||
</navigations>
|
||||
<sabre>
|
||||
<calendar-plugins>
|
||||
<plugin>OCA\Deck\DAV\CalendarPlugin</plugin>
|
||||
</calendar-plugins>
|
||||
</sabre>
|
||||
|
||||
</info>
|
||||
|
||||
@@ -26,6 +26,9 @@ 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'],
|
||||
@@ -35,9 +38,8 @@ return [
|
||||
['name' => 'board#deleteUndo', 'url' => '/boards/{boardId}/deleteUndo', 'verb' => 'POST'],
|
||||
['name' => 'board#getUserPermissions', 'url' => '/boards/{boardId}/permissions', 'verb' => 'GET'],
|
||||
['name' => 'board#addAcl', 'url' => '/boards/{boardId}/acl', 'verb' => 'POST'],
|
||||
['name' => 'board#updateAcl', 'url' => '/boards/{boardId}/acl/{aclId}', 'verb' => 'PUT'],
|
||||
['name' => 'board#updateAcl', 'url' => '/boards/{boardId}/acl', 'verb' => 'PUT'],
|
||||
['name' => 'board#deleteAcl', 'url' => '/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
|
||||
['name' => 'board#clone', 'url' => '/boards/{boardId}/clone', 'verb' => 'POST'],
|
||||
|
||||
// stacks
|
||||
['name' => 'stack#index', 'url' => '/stacks/{boardId}', 'verb' => 'GET'],
|
||||
@@ -61,9 +63,8 @@ return [
|
||||
['name' => 'card#assignLabel', 'url' => '/cards/{cardId}/label/{labelId}', 'verb' => 'POST'],
|
||||
['name' => 'card#removeLabel', 'url' => '/cards/{cardId}/label/{labelId}', 'verb' => 'DELETE'],
|
||||
['name' => 'card#assignUser', 'url' => '/cards/{cardId}/assign', 'verb' => 'POST'],
|
||||
['name' => 'card#unassignUser', 'url' => '/cards/{cardId}/unassign', 'verb' => 'PUT'],
|
||||
['name' => 'card#unassignUser', 'url' => '/cards/{cardId}/assign/{userId}', 'verb' => 'DELETE'],
|
||||
|
||||
// attachments
|
||||
['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'],
|
||||
@@ -80,66 +81,46 @@ return [
|
||||
['name' => 'label#delete', 'url' => '/labels/{labelId}', 'verb' => 'DELETE'],
|
||||
|
||||
// api
|
||||
['name' => 'board_api#index', 'url' => '/api/v{apiVersion}/boards', 'verb' => 'GET'],
|
||||
['name' => 'board_api#get', 'url' => '/api/v{apiVersion}/boards/{boardId}', 'verb' => 'GET'],
|
||||
['name' => 'board_api#create', 'url' => '/api/v{apiVersion}/boards', 'verb' => 'POST'],
|
||||
['name' => 'board_api#delete', 'url' => '/api/v{apiVersion}/boards/{boardId}', 'verb' => 'DELETE'],
|
||||
['name' => 'board_api#update', 'url' => '/api/v{apiVersion}/boards/{boardId}', 'verb' => 'PUT'],
|
||||
['name' => 'board_api#undo_delete', 'url' => '/api/v{apiVersion}/boards/{boardId}/undo_delete', 'verb' => 'POST'],
|
||||
['name' => 'board_api#addAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl', 'verb' => 'POST'],
|
||||
['name' => 'board_api#deleteAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
|
||||
['name' => 'board_api#updateAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl/{aclId}', 'verb' => 'PUT'],
|
||||
['name' => 'board_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/v{apiVersion}/boards/{boardId}/stacks', 'verb' => 'GET'],
|
||||
['name' => 'stack_api#getArchived', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/archived', 'verb' => 'GET'],
|
||||
['name' => 'stack_api#get', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}', 'verb' => 'GET'],
|
||||
['name' => 'stack_api#create', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks', 'verb' => 'POST'],
|
||||
['name' => 'stack_api#update', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}', 'verb' => 'PUT'],
|
||||
['name' => 'stack_api#delete', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}', 'verb' => 'DELETE'],
|
||||
['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/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'GET'],
|
||||
['name' => 'card_api#create', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards', 'verb' => 'POST'],
|
||||
['name' => 'card_api#update', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#assignLabel', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignLabel', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#removeLabel', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/removeLabel', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#assignUser', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignUser', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#unassignUser', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/unassignUser', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#reorder', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/reorder', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#delete', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}', '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' => 'card_api#findAllWithDue', 'url' => '/api/v{apiVersion}/dashboard/due', 'verb' => 'GET'],
|
||||
['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' => 'label_api#get', 'url' => '/api/v{apiVersion}/boards/{boardId}/labels/{labelId}', 'verb' => 'GET'],
|
||||
['name' => 'label_api#create', 'url' => '/api/v{apiVersion}/boards/{boardId}/labels', 'verb' => 'POST'],
|
||||
['name' => 'label_api#update', 'url' => '/api/v{apiVersion}/boards/{boardId}/labels/{labelId}', 'verb' => 'PUT'],
|
||||
['name' => 'label_api#delete', 'url' => '/api/v{apiVersion}/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' => 'attachment_api#getAll', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments', 'verb' => 'GET', 'requirements' => ['apiVersion' => '1.0']],
|
||||
['name' => 'attachment_api#display', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'GET', 'requirements' => ['apiVersion' => '1.0']],
|
||||
['name' => 'attachment_api#create', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments', 'verb' => 'POST', 'requirements' => ['apiVersion' => '1.0']],
|
||||
['name' => 'attachment_api#update', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'PUT', 'requirements' => ['apiVersion' => '1.0']],
|
||||
['name' => 'attachment_api#delete', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'DELETE', 'requirements' => ['apiVersion' => '1.0']],
|
||||
['name' => 'attachment_api#restore', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}/restore', 'verb' => 'PUT', 'requirements' => ['apiVersion' => '1.0']],
|
||||
|
||||
['name' => 'attachment_api_v11#getAll', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments', 'verb' => 'GET', 'requirements' => ['apiVersion' => '1.1']],
|
||||
['name' => 'attachment_api_v11#display', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{type}/{attachmentId}', 'verb' => 'GET', 'requirements' => ['apiVersion' => '1.1']],
|
||||
['name' => 'attachment_api_v11#create', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments', 'verb' => 'POST', 'requirements' => ['apiVersion' => '1.1']],
|
||||
['name' => 'attachment_api_v11#update', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{type}/{attachmentId}', 'verb' => 'PUT', 'requirements' => ['apiVersion' => '1.1']],
|
||||
['name' => 'attachment_api_v11#delete', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{type}/{attachmentId}', 'verb' => 'DELETE', 'requirements' => ['apiVersion' => '1.1']],
|
||||
['name' => 'attachment_api_v11#restore', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{type}/{attachmentId}/restore', 'verb' => 'PUT', 'requirements' => ['apiVersion' => '1.1']],
|
||||
|
||||
['name' => 'board_api#preflighted_cors', 'url' => '/api/v{apiVersion}/{path}','verb' => 'OPTIONS', 'requirements' => ['path' => '.+']],
|
||||
],
|
||||
'ocs' => [
|
||||
['name' => 'Config#get', 'url' => '/api/v{apiVersion}/config', 'verb' => 'GET'],
|
||||
['name' => 'Config#setValue', 'url' => '/api/v{apiVersion}/config/{key}', 'verb' => 'POST'],
|
||||
|
||||
['name' => 'comments_api#list', 'url' => '/api/v{apiVersion}/cards/{cardId}/comments', 'verb' => 'GET'],
|
||||
['name' => 'comments_api#create', 'url' => '/api/v{apiVersion}/cards/{cardId}/comments', 'verb' => 'POST'],
|
||||
['name' => 'comments_api#update', 'url' => '/api/v{apiVersion}/cards/{cardId}/comments/{commentId}', 'verb' => 'PUT'],
|
||||
['name' => 'comments_api#delete', 'url' => '/api/v{apiVersion}/cards/{cardId}/comments/{commentId}', 'verb' => 'DELETE'],
|
||||
|
||||
['name' => 'overview_api#upcomingCards', 'url' => '/api/v{apiVersion}/overview/upcoming', 'verb' => 'GET'],
|
||||
['name' => 'board_api#preflighted_cors', 'url' => '/api/v1.0/{path}','verb' => 'OPTIONS', 'requirements' => ['path' => '.+']],
|
||||
]
|
||||
];
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"name": "nextcloud/deck",
|
||||
"type": "project",
|
||||
"license": "AGPLv3",
|
||||
"minimum-stability": "dev",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Julius Härtl",
|
||||
@@ -14,22 +13,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"roave/security-advisories": "dev-master",
|
||||
"christophwurst/nextcloud": "dev-master",
|
||||
"phpunit/phpunit": "^8",
|
||||
"nextcloud/coding-standard": "^0.5.0",
|
||||
"symfony/event-dispatcher": "^4.0",
|
||||
"vimeo/psalm": "^4.3",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"classmap-authoritative": true
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "find . -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",
|
||||
"cs:check": "php-cs-fixer fix --dry-run --diff",
|
||||
"cs:fix": "php-cs-fixer fix",
|
||||
"psalm": "psalm",
|
||||
"psalm:fix": "psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MismatchingDocblockParamType,MismatchingDocblockReturnType,MissingParamType,InvalidFalsableReturnType"
|
||||
"christophwurst/nextcloud": "^16.0",
|
||||
"jakub-onderka/php-parallel-lint": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
4942
composer.lock
generated
@@ -1,8 +1,7 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Ryan Fletcher <ryan.fletcher@codepassion.ca>
|
||||
/*
|
||||
* @copyright Copyright (c) 2018 Michael Weimann <mail@michael-weimann.eu>
|
||||
*
|
||||
* @author Ryan Fletcher <ryan.fletcher@codepassion.ca>
|
||||
* @author 2018 Michael Weimann <mail@michael-weimann.eu>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
@@ -20,7 +19,25 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\Deck\Controller;
|
||||
|
||||
class AttachmentApiV11Controller extends AttachmentApiController {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
77
css/autocomplete.scss
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
.resource-type-deck img {
|
||||
opacity: 0.4 !important;
|
||||
.icon-deck {
|
||||
background-image: url('../img/deck-dark.svg');
|
||||
}
|
||||
|
||||
.resource-type-deck:hover img {
|
||||
opacity: 0.7 !important;
|
||||
.resource-type-deck img {
|
||||
opacity: 0.4 !important;
|
||||
}
|
||||
.resource-type-deck:hover img {
|
||||
opacity: 0.7 !important;
|
||||
}
|
||||
|
||||
261
css/comments.scss
Normal file
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
113
css/comp-appnav.scss
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* @copyright Copyright (c) 2017 Julius Härtl <jus@bitgrid.net>
|
||||
* @copyright Copyright (c) 2016, John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Hotfix for support <NC13 with new app sidebar
|
||||
*/
|
||||
#app-navigation {
|
||||
.app-navigation-entry-menu.open {
|
||||
ul li a {
|
||||
background-position: 10px center;
|
||||
padding: 0 10px 0 36px !important;
|
||||
}
|
||||
}
|
||||
.app-navigation-entry-edit {
|
||||
display: none;
|
||||
}
|
||||
.editing {
|
||||
.app-navigation-entry-edit {
|
||||
display: block;
|
||||
position: absolute;
|
||||
background: $color-main-background;
|
||||
height: auto;
|
||||
z-index: 250;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* copied styles from core/css/styles.scss
|
||||
* to have the same breadcrumb styling in NC12
|
||||
*/
|
||||
.breadcrumb {
|
||||
display: inline-flex;
|
||||
}
|
||||
div.crumb {
|
||||
display: inline-flex;
|
||||
background-repeat: no-repeat;
|
||||
background-position: right center;
|
||||
height: 44px;
|
||||
background-size: auto 24px;
|
||||
flex: 0 0 auto;
|
||||
order: 1;
|
||||
padding-right: 7px;
|
||||
&.crumbmenu {
|
||||
order: 2;
|
||||
position: relative;
|
||||
a {
|
||||
opacity: 0.5
|
||||
}
|
||||
}
|
||||
&.hidden {
|
||||
display: none;
|
||||
~ .crumb {
|
||||
order: 3;
|
||||
}
|
||||
}
|
||||
> a,
|
||||
> span {
|
||||
position: relative;
|
||||
padding: 12px;
|
||||
opacity: 0.5;
|
||||
top: 0 !important;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
flex: 0 0 auto;
|
||||
&.icon-home {
|
||||
// Hide home text
|
||||
text-indent: -9999px;
|
||||
}
|
||||
}
|
||||
> a[class^='icon-'] {
|
||||
padding: 0;
|
||||
width: 44px;
|
||||
}
|
||||
&:not(:first-child) a {
|
||||
}
|
||||
&:last-child {
|
||||
font-weight: 600;
|
||||
margin-right: 10px;
|
||||
// Allow multiple span next to the main 'a'
|
||||
a ~ span {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
&:hover, &:focus, a:focus, &:active {
|
||||
> a,
|
||||
> span {
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2020 Jakob Röhrl <jakob.roehrl@web.de>
|
||||
/*
|
||||
* @copyright Copyright (c) 2018 Michael Weimann <mail@michael-weimann.eu>
|
||||
*
|
||||
* @author Jakob Röhrl <jakob.roehrl@web.de>
|
||||
* @author 2018 Michael Weimann <mail@michael-weimann.eu>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
@@ -21,23 +20,24 @@
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Exceptions;
|
||||
.compact-mode {
|
||||
.card {
|
||||
margin: $compact-board-item-margin;
|
||||
|
||||
use OCA\Deck\StatusException;
|
||||
&:last-child {
|
||||
margin: $compact-board-last-item-margin;
|
||||
}
|
||||
}
|
||||
|
||||
class ConflictException extends StatusException {
|
||||
private $data;
|
||||
.stack {
|
||||
.as-sortable-placeholder {
|
||||
margin: $compact-board-item-margin;
|
||||
min-height: 43px;
|
||||
height: 43px;
|
||||
|
||||
public function __construct($message, $data = null) {
|
||||
parent::__construct($message);
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getStatus() {
|
||||
return 409;
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
return $this->data;
|
||||
}
|
||||
&:last-child {
|
||||
margin: $compact-board-last-item-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
@include icon-black-white('deck', 'deck', 1);
|
||||
@@ -1,39 +1,80 @@
|
||||
/**
|
||||
* Custom icons
|
||||
*/
|
||||
@include icon-black-white('deck', 'deck', 1);
|
||||
@include icon-black-white('archive', 'deck', 1);
|
||||
@include icon-black-white('circles', 'deck', 1);
|
||||
@include icon-black-white('clone', 'deck', 1);
|
||||
@include icon-black-white('filter', 'deck', 1);
|
||||
@include icon-black-white('filter_set', 'deck', 1);
|
||||
@include icon-black-white('attach', 'deck', 1);
|
||||
@include icon-black-white('reply', 'deck', 1);
|
||||
@include icon-black-white('notifications-dark', 'deck', 1);
|
||||
@include icon-black-white('description', 'deck', 1);
|
||||
.icon-deck {
|
||||
background-image: url('../img/deck-dark.svg');
|
||||
}
|
||||
|
||||
.icon-group {
|
||||
background-image: url('../../../settings/img/users.svg');
|
||||
}
|
||||
|
||||
.icon-help {
|
||||
background-image: url('../../../settings/img/help.svg');
|
||||
}
|
||||
|
||||
.icon-add-white {
|
||||
background-image: url('../img/add-white.svg');
|
||||
}
|
||||
|
||||
.icon-archive {
|
||||
background-image: url('../img/archive.svg');
|
||||
}
|
||||
|
||||
.icon-archive-white {
|
||||
background-image: url('../img/archive-white.svg');
|
||||
}
|
||||
|
||||
.icon-details {
|
||||
background-image: url('../img/details.svg');
|
||||
}
|
||||
|
||||
.icon-details-white {
|
||||
background-image: url('../img/details-white.svg');
|
||||
}
|
||||
|
||||
.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'));
|
||||
}
|
||||
|
||||
.icon-badge {
|
||||
background-image: url('../img/calendar-dark.svg');
|
||||
}
|
||||
|
||||
.icon-toggle-compact-collapsed {
|
||||
@include icon-color('toggle-view-expand', 'deck', $color-black);
|
||||
background-image: url('../img/toggle-view-expand.svg');
|
||||
}
|
||||
|
||||
.icon-toggle-compact-expanded {
|
||||
@include icon-color('toggle-view-collapse', 'deck', $color-black);
|
||||
}
|
||||
.icon-activity {
|
||||
@include icon-color('activity-dark', 'activity', $color-black);
|
||||
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);
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
.icon-circles {
|
||||
opacity: 1;
|
||||
background-size: 20px;
|
||||
background-position: center center;
|
||||
}
|
||||
|
||||
.icon-colorpicker {
|
||||
background-image: url('../img/color_picker.svg');
|
||||
}
|
||||
opacity: 1;
|
||||
background-size: 20px;
|
||||
background-position: center center;
|
||||
}
|
||||
@@ -59,6 +59,8 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
span.due { }
|
||||
|
||||
div.card-assigned-users {
|
||||
margin-right:10px;
|
||||
}
|
||||
@@ -73,7 +75,7 @@
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
margin: 2cm;
|
||||
}
|
||||
}
|
||||
|
||||
div#innerBoard {
|
||||
display:flex;
|
||||
|
||||
1659
css/style.scss
Normal file
@@ -1,34 +1,5 @@
|
||||
# Nextcloud APIs
|
||||
|
||||
## Capabilities
|
||||
|
||||
The [Nextcloud Capabilities API](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#capabilities-api) provides the following information for the Deck app:
|
||||
|
||||
- `version` Current version of the Deck app running
|
||||
- `canCreateBoards` Ability of the current user to create new boards for themselves
|
||||
|
||||
```
|
||||
{
|
||||
"ocs": {
|
||||
"meta": {
|
||||
"status": "ok",
|
||||
"statuscode": 200,
|
||||
"message": "OK"
|
||||
},
|
||||
"data": {
|
||||
"capabilities": {
|
||||
"deck": {
|
||||
"version": "0.8.0",
|
||||
"canCreateBoards": true
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Available sharees
|
||||
|
||||
When sharing a board to a user, group or circle, the possible sharees can be obtained though the files_sharing API.
|
||||
|
||||
467
docs/API.md
@@ -1,11 +1,10 @@
|
||||
|
||||
The REST API provides access for authenticated users to their data inside the Deck app. To get a better understanding of Decks data models and their relations, please have a look at the [data structure](structure.md) documentation.
|
||||
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
|
||||
- All request parameters are required, unless otherwise specified
|
||||
|
||||
## Naming
|
||||
|
||||
@@ -21,7 +20,7 @@ The REST API provides access for authenticated users to their data inside the De
|
||||
|
||||
### 400 Bad request
|
||||
|
||||
In case the request is invalid, e.g. because a parameter is missing or an invalid value has been transmitted, a 400 error will be returned:
|
||||
In case the request is invalid, e.g. because a parameter is missing, a 400 error will be returned:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -41,24 +40,11 @@ In any case a user doesn't have access to a requested entity, a 403 error will b
|
||||
}
|
||||
```
|
||||
|
||||
## Formats
|
||||
|
||||
### Date
|
||||
|
||||
Datetime values in request data need to be provided in ISO-8601. Example: 2020-01-20T09:52:43+00:00
|
||||
|
||||
## Headers
|
||||
|
||||
### If-Modified-Since
|
||||
|
||||
Some index endpoints support limiting the result set to entries that have been changed since the given time.
|
||||
The supported date formats are:
|
||||
|
||||
* IMF-fixdate: `Sun, 03 Aug 2019 10:34:12 GMT`
|
||||
* (obsolete) RFC 850: `Sunday, 03-Aug-19 10:34:12 GMT`
|
||||
* (obsolete) ANSI C asctime(): `Sun Aug 3 10:34:12 2019`
|
||||
|
||||
It is highly recommended to only use the IMF-fixdate format. Note that according to [RFC2616](https://tools.ietf.org/html/rfc2616#section-3.3) all HTTP date/time stamps MUST be represented in Greenwich Mean Time (GMT), without exception.
|
||||
|
||||
Example curl request:
|
||||
|
||||
@@ -66,41 +52,9 @@ Example curl request:
|
||||
curl -u admin:admin -X GET \
|
||||
'http://localhost:8000/index.php/apps/deck/api/v1.0/boards/2/stacks' \
|
||||
-H "OCS-APIRequest: true" \
|
||||
-H "If-Modified-Since: Mon, 05 Nov 2018 09:28:00 GMT"
|
||||
-H "If-Modified-Since: Mon, 5 Nov 2018 09:28:00 GMT"
|
||||
```
|
||||
|
||||
### ETag
|
||||
|
||||
An ETag header is returned in order to determine if further child elements have been updated for the following endpoints:
|
||||
|
||||
- Fetch all user board `GET /api/v1.0/boards`
|
||||
- Fetch a single board `GET /api/v1.0/boards/{boardId}`
|
||||
- Fetch all stacks of a board `GET /api/v1.0/boards/{boardId}/stacks`
|
||||
- Fetch a single stacks of a board `GET /api/v1.0/boards/{boardId}/stacks/{stackId}`
|
||||
- Fetch a single card of a board `GET /api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}`
|
||||
- Fetch attachments of a card `GET /api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments`
|
||||
|
||||
If a `If-None-Match` header is provided and the requested element has not changed a `304` Not Modified response will be returned.
|
||||
|
||||
Changes of child elements will propagate to their parents and also cause an update of the ETag which will be useful for determining if a sync is necessary on any client integration side. As an example, if a label is added to a card, the ETag of all related entities (the card, stack and board) will change.
|
||||
|
||||
If available the ETag will also be part of JSON response objects as shown below for a card:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 81,
|
||||
"ETag": "bdb10fa2d2aeda092a2b6b469454dc90",
|
||||
"title": "Test card"
|
||||
}
|
||||
```
|
||||
|
||||
# Changelog
|
||||
|
||||
## 1.0.0 (unreleased)
|
||||
|
||||
- The maximum length of the card title has been extended from 100 to 255 characters
|
||||
- The API will now return a 400 Bad request response if the length limitation of a board, stack or card title is exceeded
|
||||
|
||||
# Endpoints
|
||||
|
||||
## Boards
|
||||
@@ -115,7 +69,7 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | ---------------------------- |
|
||||
| details | Bool | **Optional** Enhance boards with details about labels, stacks and users |
|
||||
| options | Bool | **Optional** Enhance boards with details about labels, stacks and users |
|
||||
|
||||
#### Response
|
||||
|
||||
@@ -145,12 +99,7 @@ Returns an array of board items
|
||||
"users": [],
|
||||
"shared": 0,
|
||||
"deletedAt": 0,
|
||||
"id": 10,
|
||||
"lastModified": 1586269585,
|
||||
"settings": {
|
||||
"notify-due": "off",
|
||||
"calendar": true
|
||||
}
|
||||
"id": 10
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -161,7 +110,7 @@ Returns an array of board items
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------ | ---------------------------------------------------- |
|
||||
| title | String | The title of the new board, maximum length is limited to 100 characters |
|
||||
| title | String | The title of the new board |
|
||||
| color | String | The hexadecimal color of the new board (e.g. FF0000) |
|
||||
|
||||
```json
|
||||
@@ -224,15 +173,10 @@ Returns an array of board items
|
||||
},
|
||||
"users": [],
|
||||
"deletedAt": 0,
|
||||
"id": 10,
|
||||
"lastModified": 1586269585
|
||||
"id": 10
|
||||
}
|
||||
```
|
||||
|
||||
##### 403 Forbidden
|
||||
|
||||
A 403 response might be returned if the users ability to create new boards has been disabled by the administrator. For checking this before, see the `canCreateBoards` value in the [Nextcloud capabilties](./API-Nextcloud.md).
|
||||
|
||||
### GET /boards/{boardId} - Get board details
|
||||
|
||||
#### Request parameters
|
||||
@@ -310,9 +254,9 @@ A 403 response might be returned if the users ability to create new boards has b
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------ | ---------------------------------------------------- |
|
||||
| title | String | The title of the board, maximum length is limited to 100 characters |
|
||||
| color | String | The hexadecimal color of the board (e.g. FF0000) |
|
||||
| archived | Bool | Whether or not this board should be archived. |
|
||||
| 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
|
||||
{
|
||||
@@ -482,13 +426,6 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit
|
||||
|
||||
### POST /boards/{boardId}/stacks - Create a new stack
|
||||
|
||||
#### Request body
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | ---------------------------------------------------- |
|
||||
| title | String | The title of the new stack, maximum length is limited to 100 characters |
|
||||
| order | Integer | Order for sorting the stacks |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
@@ -512,7 +449,7 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | ---------------------------------------------------- |
|
||||
| title | String | The title of the stack, maximum length is limited to 100 characters |
|
||||
| title | String | The title of the new stack |
|
||||
| order | Integer | Order for sorting the stacks |
|
||||
|
||||
#### Response
|
||||
@@ -561,11 +498,9 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | ---------------------------------------------------- |
|
||||
| title | String | The title of the card, maximum length is limited to 255 characters |
|
||||
| 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 |
|
||||
| description | String | _(optional)_ The markdown description of the card |
|
||||
| duedate | timestamp | _(optional)_ The duedate of the card or null |
|
||||
|
||||
#### Response
|
||||
|
||||
@@ -584,7 +519,7 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit
|
||||
"owner":"admin",
|
||||
"order":999,
|
||||
"archived":false,
|
||||
"duedate": "2019-12-24T19:29:30+00:00",
|
||||
"duedate":null,
|
||||
"deletedAt":0,
|
||||
"commentsUnread":0,
|
||||
"id":10,
|
||||
@@ -608,11 +543,11 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-------------|-----------|------------------------------------------------------|
|
||||
| title | String | The title of the card, maximum length is limited to 255 characters |
|
||||
| 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 ISO-8601 formatted duedate of the card or null |
|
||||
| duedate | timestamp | The duedate of the card or null |
|
||||
|
||||
|
||||
```
|
||||
@@ -621,7 +556,7 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit
|
||||
"description": "A card description",
|
||||
"type": "plain",
|
||||
"order": 999,
|
||||
"duedate": "2019-12-24T19:29:30+00:00",
|
||||
"duedate": null,
|
||||
}
|
||||
```
|
||||
|
||||
@@ -702,34 +637,7 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit
|
||||
|
||||
##### 200 Success
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 3,
|
||||
"participant": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin"
|
||||
},
|
||||
"cardId": 1
|
||||
}
|
||||
```
|
||||
|
||||
##### 400 Bad request
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 400,
|
||||
"message": "The user is already assigned to the card"
|
||||
}
|
||||
```
|
||||
|
||||
The request can fail with a bad request response for the following reasons:
|
||||
- Missing or wrongly formatted request parameters
|
||||
- The user is already assigned to the card
|
||||
- The user is not part of the board
|
||||
|
||||
|
||||
### PUT /boards/{boardId}/stacks/{stackId}/cards/{cardId}/unassignUser - Unassign a user from a card
|
||||
### PUT /boards/{boardId}/stacks/{stackId}/cards/{cardId}/unassignUser - Assign a user to a card
|
||||
|
||||
#### Request parameters
|
||||
|
||||
@@ -743,7 +651,7 @@ The request can fail with a bad request response for the following reasons:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | --------------------------------------- |
|
||||
| userId | String | The user id to unassign from the card |
|
||||
| userId | String | The user id to assign to the card |
|
||||
|
||||
#### Response
|
||||
|
||||
@@ -987,342 +895,3 @@ For now only `deck_file` is supported as an attachment type.
|
||||
|
||||
##### 200 Success
|
||||
|
||||
# OCS API
|
||||
|
||||
The following endpoints are available through the Nextcloud OCS endpoint, which is available at `/ocs/v2.php/apps/deck/api/v1.0/`.
|
||||
This has the benefit that both the web UI as well as external integrations can use the same API.
|
||||
|
||||
## Config
|
||||
|
||||
Deck stores user and app configuration values globally and per board. The GET endpoint allows to fetch the current global configuration while board settings will be exposed through the board element on the regular API endpoints.
|
||||
|
||||
### GET /api/v1.0/config - Fetch app configuration values
|
||||
|
||||
#### Response
|
||||
|
||||
| Config key | Description |
|
||||
| --- | --- |
|
||||
| calendar | Determines if the calendar/tasks integration through the CalDAV backend is enabled for the user (boolean) |
|
||||
| groupLimit | Determines if creating new boards is limited to certain groups of the instance. The resulting output is an array of group objects with the id and the displayname (Admin only)|
|
||||
|
||||
```
|
||||
{
|
||||
"ocs": {
|
||||
"meta": {
|
||||
"status": "ok",
|
||||
"statuscode": 200,
|
||||
"message": "OK"
|
||||
},
|
||||
"data": {
|
||||
"calendar": true,
|
||||
"groupLimit": [
|
||||
{
|
||||
"id": "admin",
|
||||
"displayname": "admin"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### POST /api/v1.0/config/{id}/{key} - Set a config value
|
||||
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | --------------------------------------- |
|
||||
| id | Integer | The id of the board |
|
||||
| key | String | The config key to set, prefixed with `board:{boardId}:` for board specific settings |
|
||||
| value | String | The value that should be stored for the config key |
|
||||
|
||||
##### Board configuration
|
||||
|
||||
| Key | Value |
|
||||
| --- | ----- |
|
||||
| notify-due | `off`, `assigned` or `all` |
|
||||
| calendar | Boolean |
|
||||
|
||||
#### Example request
|
||||
|
||||
```
|
||||
curl -X POST 'https://admin:admin@nextcloud.local/ocs/v2.php/apps/deck/api/v1.0/config/calendar' -H 'Accept: application/json' -H "Content-Type: application/json" -H 'OCS-APIRequest: true' --data-raw '{"value":false}'
|
||||
|
||||
{
|
||||
"ocs": {
|
||||
"meta": {
|
||||
"status": "ok",
|
||||
"statuscode": 200,
|
||||
"message": "OK"
|
||||
},
|
||||
"data": false
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
### GET /cards/{cardId}/comments - List comments
|
||||
|
||||
#### Request parameters
|
||||
|
||||
string $cardId, int $limit = 20, int $offset = 0
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | --------------------------------------- |
|
||||
| cardId | Integer | The id of the card |
|
||||
| limit | Integer | The maximum number of comments that should be returned, defaults to 20 |
|
||||
| offset | Integer | The start offset used for pagination, defaults to 0 |
|
||||
|
||||
```
|
||||
curl 'https://admin:admin@nextcloud/ocs/v2.php/apps/deck/api/v1.0/cards/12/comments' \
|
||||
-H 'Accept: application/json' -H 'OCS-APIRequest: true'
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
A list of comments will be provided under the `ocs.data` key. If no or no more comments are available the list will be empty.
|
||||
|
||||
##### 200 Success
|
||||
|
||||
```
|
||||
{
|
||||
"ocs": {
|
||||
"meta": {
|
||||
"status": "ok",
|
||||
"statuscode": 200,
|
||||
"message": "OK"
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"id": 175,
|
||||
"objectId": 12,
|
||||
"message": "This is a comment with a mention to @alice",
|
||||
"actorId": "admin",
|
||||
"actorType": "users",
|
||||
"actorDisplayName": "Administrator",
|
||||
"creationDateTime": "2020-03-10T10:23:07+00:00",
|
||||
"mentions": [
|
||||
{
|
||||
"mentionId": "alice",
|
||||
"mentionType": "user",
|
||||
"mentionDisplayName": "alice"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In case a comment is marked as a reply to another comment object, the parent comment will be added as `replyTo` entry to the response. Only the next parent node is added, nested replies are not exposed directly.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 175,
|
||||
"objectId": 12,
|
||||
"message": "This is a comment with a mention to @alice",
|
||||
"actorId": "admin",
|
||||
"actorType": "users",
|
||||
"actorDisplayName": "Administrator",
|
||||
"creationDateTime": "2020-03-10T10:23:07+00:00",
|
||||
"mentions": [
|
||||
{
|
||||
"mentionId": "alice",
|
||||
"mentionType": "user",
|
||||
"mentionDisplayName": "alice"
|
||||
}
|
||||
],
|
||||
"replyTo": {
|
||||
"id": 175,
|
||||
"objectId": 12,
|
||||
"message": "This is a comment with a mention to @alice",
|
||||
"actorId": "admin",
|
||||
"actorType": "users",
|
||||
"actorDisplayName": "Administrator",
|
||||
"creationDateTime": "2020-03-10T10:23:07+00:00",
|
||||
"mentions": [
|
||||
{
|
||||
"mentionId": "alice",
|
||||
"mentionType": "user",
|
||||
"mentionDisplayName": "alice"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
### POST /cards/{cardId}/comments - Create a new comment
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | --------------------------------------- |
|
||||
| cardId | Integer | The id of the card |
|
||||
| message | String | The message of the comment, maximum length is limited to 1000 characters |
|
||||
| parentId | Integer | _(optional)_ The start offset used for pagination, defaults to null |
|
||||
|
||||
Mentions will be parsed by the server. The server will return a list of mentions in the response to this request as shown below.
|
||||
|
||||
```
|
||||
curl -X POST 'https://admin:admin@nextcloud/ocs/v2.php/apps/deck/api/v1.0/cards/12/comments' \
|
||||
-H 'Accept: application/json' -H 'OCS-APIRequest: true'
|
||||
-H 'Content-Type: application/json;charset=utf-8'
|
||||
--data '{"message":"My message to @bob","parentId":null}'
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
A list of comments will be provided under the `ocs.data` key. If no or no more comments are available the list will be empty.
|
||||
|
||||
##### 200 Success
|
||||
|
||||
```
|
||||
{
|
||||
"ocs": {
|
||||
"meta": {
|
||||
"status": "ok",
|
||||
"statuscode": 200,
|
||||
"message": "OK"
|
||||
},
|
||||
"data": {
|
||||
"id": "177",
|
||||
"objectId": "13",
|
||||
"message": "My message to @bob",
|
||||
"actorId": "admin",
|
||||
"actorType": "users",
|
||||
"actorDisplayName": "Administrator",
|
||||
"creationDateTime": "2020-03-10T10:30:17+00:00",
|
||||
"mentions": [
|
||||
{
|
||||
"mentionId": "bob",
|
||||
"mentionType": "user",
|
||||
"mentionDisplayName": "bob"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### 400 Bad request
|
||||
|
||||
A bad request response is returned if invalid input values are provided. The response message will contain details about which part was not valid.
|
||||
|
||||
##### 404 Not found
|
||||
|
||||
A not found response might be returned if:
|
||||
- The card for the given cardId could not be found
|
||||
- The parent comment could not be found
|
||||
|
||||
|
||||
### PUT /cards/{cardId}/comments/{commentId} - Update a comment
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | --------------------------------------- |
|
||||
| cardId | Integer | The id of the card |
|
||||
| commentId | Integer | The id of the comment |
|
||||
| message | String | The message of the comment, maximum length is limited to 1000 characters |
|
||||
|
||||
Mentions will be parsed by the server. The server will return a list of mentions in the response to this request as shown below.
|
||||
|
||||
Updating comments is limited to the current user being the same as the comment author specified in the `actorId` of the comment.
|
||||
|
||||
```
|
||||
curl -X POST 'https://admin:admin@nextcloud/ocs/v2.php/apps/deck/api/v1.0/cards/12/comments' \
|
||||
-H 'Accept: application/json' -H 'OCS-APIRequest: true'
|
||||
-H 'Content-Type: application/json;charset=utf-8'
|
||||
--data '{"message":"My message"}'
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
A list of comments will be provided under the `ocs.data` key. If no or no more comments are available the list will be empty.
|
||||
|
||||
##### 200 Success
|
||||
|
||||
```
|
||||
{
|
||||
"ocs": {
|
||||
"meta": {
|
||||
"status": "ok",
|
||||
"statuscode": 200,
|
||||
"message": "OK"
|
||||
},
|
||||
"data": {
|
||||
"id": "177",
|
||||
"objectId": "13",
|
||||
"message": "My message",
|
||||
"actorId": "admin",
|
||||
"actorType": "users",
|
||||
"actorDisplayName": "Administrator",
|
||||
"creationDateTime": "2020-03-10T10:30:17+00:00",
|
||||
"mentions": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### 400 Bad request
|
||||
|
||||
A bad request response is returned if invalid input values are provided. The response message will contain details about which part was not valid.
|
||||
|
||||
##### 404 Not found
|
||||
|
||||
A not found response might be returned if:
|
||||
- The card for the given cardId could not be found
|
||||
- The comment could not be found
|
||||
|
||||
### DELETE /cards/{cardId}/comments/{commentId} - Delete a comment
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | --------------------------------------- |
|
||||
| cardId | Integer | The id of the card |
|
||||
| commentId | Integer | The id of the comment |
|
||||
|
||||
Deleting comments is limited to the current user being the same as the comment author specified in the `actorId` of the comment.
|
||||
|
||||
```
|
||||
curl -X DELETE 'https://admin:admin@nextcloud/ocs/v2.php/apps/deck/api/v1.0/cards/12/comments' \
|
||||
-H 'Accept: application/json' -H 'OCS-APIRequest: true'
|
||||
-H 'Content-Type: application/json;charset=utf-8'
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
A list of comments will be provided under the `ocs.data` key. If no or no more comments are available the list will be empty.
|
||||
|
||||
##### 200 Success
|
||||
|
||||
```
|
||||
{
|
||||
"ocs": {
|
||||
"meta": {
|
||||
"status": "ok",
|
||||
"statuscode": 200,
|
||||
"message": "OK"
|
||||
},
|
||||
"data": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### 400 Bad request
|
||||
|
||||
A bad request response is returned if invalid input values are provided. The response message will contain details about which part was not valid.
|
||||
|
||||
##### 404 Not found
|
||||
|
||||
A not found response might be returned if:
|
||||
- The card for the given cardId could not be found
|
||||
- The comment could not be found
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## What is Markdown
|
||||
|
||||
The [wikipedia markdown entry](https://en.wikipedia.org/wiki/Markdown) introduced markdown as :
|
||||
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.
|
||||
|
||||
@@ -14,18 +14,14 @@ That same link offers also a comprehensive list of what is supported, and what i
|
||||
|
||||
[CommonMark Markdown Reference](http://commonmark.org/help/)
|
||||
|
||||
## Note about checklists
|
||||
|
||||
It is possible to create checklists in Deck by writing it in Markdown, using the following syntax:
|
||||
```md
|
||||
- [ ] This is a not checked item
|
||||
- [x] This is a checked item
|
||||
```
|
||||
Then, the items can be checked and unchecked by clicking on the rendered checkbox.
|
||||
Also, a summary of the completed items will be visible at the bottom of the card element.
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Introduction
|
||||
### What about Deck ?
|
||||
You may know Kanban website like Trello? Deck is about the same thing but secured and respectful of your privacy!
|
||||
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
|
||||
@@ -36,7 +36,6 @@ And all the magic of this software consists on moving your cards from a stack to
|
||||
|
||||
### 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
|
||||
@@ -58,7 +57,6 @@ Once finished or obsolete, a task could be archived. The tasks is not deleted, i
|
||||
### 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
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.subnav ul {
|
||||
padding-left: 20px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
|
||||
|
Before Width: | Height: | Size: 390 B |
@@ -1 +0,0 @@
|
||||
<svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M11.8 13.8H2.2V4.2h9.6m1.2 0c0-.67-.53-1.2-1.2-1.2H2.2C1.53 3 1 3.53 1 4.2v9.6c0 .67.53 1.2 1.2 1.2h9.6c.67 0 1.2-.53 1.2-1.2"/><path d="m4.2 1c-0.67 0-1.2 0.54-1.2 1.2h10.8v10.8c0.67 0 1.2-0.53 1.2-1.2v-9.6c0-0.67-0.53-1.2-1.2-1.2z"/></svg>
|
||||
|
Before Width: | Height: | Size: 327 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M14,17L4,17v2h10v-2zM20,9L4,9v2h16L20,9zM4,15h16v-2L4,13v2zM4,5v2h16L20,5L4,5z"/></svg>
|
||||
<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: 180 B After Width: | Height: | Size: 292 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 4.233 4.233"><g paint-order="stroke fill markers"><path d="M.52.465h3.283L2.631 1.918h-.99zM1.642 1.918h.992v1.866l-.996-.455z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 216 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4.233 4.233" height="16" width="16"><path d="M.52.465h3.283L2.631 1.918h-.99zm1.122 1.453h.992v1.866l-.996-.455z" paint-order="stroke fill markers"/><ellipse ry=".691" rx=".674" cy="3.461" cx="3.45" fill="#000"/></svg>
|
||||
|
Before Width: | Height: | Size: 272 B |
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewBox="0 0 16 16">
|
||||
<path d="m8 2c-0.5523 0-1 0.4477-1 1 0 0.0472 0.021 0.0873 0.0273 0.1328-1.7366 0.4362-3.0273 1.9953-3.0273 3.8672v2l-1 1v1h10v-1l-1-1v-2c0-1.8719-1.291-3.431-3.0273-3.8672 0.0063-0.0455 0.0273-0.0856 0.0273-0.1328 0-0.5523-0.4477-1-1-1zm-2 10c0 1.1046 0.8954 2 2 2s2-0.8954 2-2z" fill="#000"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 456 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M15 15s-.4-7.8-7-10V1L1 8l7 7v-4c5.1 0 7 4 7 4z"/></svg>
|
||||
|
Before Width: | Height: | Size: 128 B |
@@ -1,10 +1,11 @@
|
||||
module.exports = {
|
||||
plugins: ['@babel/plugin-syntax-dynamic-import'],
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: false
|
||||
targets: {
|
||||
browsers: ['last 2 versions', 'ie >= 11']
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
49
js/.jshintrc
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"esversion": 6,
|
||||
|
||||
"globals": {
|
||||
"jasmine" : false,
|
||||
"spyOn" : false,
|
||||
"it" : false,
|
||||
"describe" : false,
|
||||
"expect" : false,
|
||||
"beforeEach" : false,
|
||||
"waits" : false,
|
||||
"waitsFor" : false,
|
||||
"runs" : false,
|
||||
"require" : false,
|
||||
"module": true
|
||||
},
|
||||
|
||||
"asi" : true,
|
||||
"boss" : true,
|
||||
"browser" : true,
|
||||
"curly" : true,
|
||||
"debug" : true,
|
||||
"devel" : true,
|
||||
"eqeqeq" : true,
|
||||
"eqnull" : false,
|
||||
"evil" : false,
|
||||
"forin" : true,
|
||||
"immed" : true,
|
||||
"indent" : 4,
|
||||
"jquery" : true,
|
||||
"latedef" : true,
|
||||
"laxbreak" : false,
|
||||
"newcap" : true,
|
||||
"noarg" : true,
|
||||
"node" : false,
|
||||
"noempty" : false,
|
||||
"nomen" : false,
|
||||
"nonew" : true,
|
||||
"onevar" : true,
|
||||
"plusplus" : false,
|
||||
"quotmark" : "single",
|
||||
"regexp" : false,
|
||||
"sub" : true,
|
||||
"trailing" : true,
|
||||
"undef" : true,
|
||||
"unused" : true,
|
||||
"white" : false,
|
||||
"scripturl" : true
|
||||
}
|
||||
64
js/app/App.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* @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 angular */
|
||||
|
||||
|
||||
angular.module('markdown', [])
|
||||
.provider('markdown', [function () {
|
||||
var opts = {};
|
||||
return {
|
||||
config: function (newOpts) {
|
||||
opts = newOpts;
|
||||
},
|
||||
$get: function () {
|
||||
return new window.showdown.Converter(opts);
|
||||
}
|
||||
};
|
||||
}])
|
||||
.filter('markdown', ['markdown', function (markdown) {
|
||||
return function (text) {
|
||||
return markdown.makeHtml(text || '');
|
||||
};
|
||||
}]);
|
||||
|
||||
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
|
||||
]);
|
||||
|
||||
export default app;
|
||||
117
js/app/Config.js
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* @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 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) {
|
||||
'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);
|
||||
|
||||
$urlRouterProvider.otherwise('/');
|
||||
|
||||
$stateProvider
|
||||
.state('list', {
|
||||
url: '/:filter',
|
||||
templateUrl: '/boardlist.mainView.html',
|
||||
controller: 'ListController',
|
||||
reloadOnSearch: false,
|
||||
params: {
|
||||
filter: {value: '', dynamic: true}
|
||||
}
|
||||
})
|
||||
.state('board', {
|
||||
url: '/board/:boardId/:filter',
|
||||
templateUrl: '/board.html',
|
||||
controller: 'BoardController',
|
||||
params: {
|
||||
filter: {value: '', dynamic: true}
|
||||
}
|
||||
})
|
||||
.state('board.detail', {
|
||||
url: '/detail/',
|
||||
reloadOnSearch: false,
|
||||
params: {
|
||||
tab: {value: 0, dynamic: true},
|
||||
},
|
||||
views: {
|
||||
'sidebarView@': {
|
||||
templateUrl: '/board.sidebarView.html',
|
||||
controller: 'BoardController'
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('board.card', {
|
||||
url: '/card/:cardId',
|
||||
params: {
|
||||
tab: {value: 0, dynamic: true},
|
||||
},
|
||||
views: {
|
||||
'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;
|
||||
});
|
||||
|
||||
});
|
||||
64
js/app/Run.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from './App.js';
|
||||
|
||||
/* global Snap */
|
||||
app.run(function ($document, $rootScope, $transitions, BoardService) {
|
||||
'use strict';
|
||||
|
||||
$document.click(function (event) {
|
||||
$rootScope.$broadcast('documentClicked', event);
|
||||
});
|
||||
$transitions.onEnter({from: 'list'}, function ($state, $transition$) {
|
||||
BoardService.unsetCurrrent();
|
||||
});
|
||||
$transitions.onEnter({to: 'list'}, function ($state, $transition$) {
|
||||
BoardService.unsetCurrrent();
|
||||
document.title = "Deck - " + oc_defaults.name;
|
||||
});
|
||||
$transitions.onEnter({to: 'board.card'}, function ($state, $transition$) {
|
||||
$rootScope.sidebar.show = true;
|
||||
});
|
||||
$transitions.onEnter({to: 'board.detail'}, function ($state, $transition$) {
|
||||
$rootScope.sidebar.show = true;
|
||||
});
|
||||
$transitions.onEnter({to: 'board'}, function ($state) {
|
||||
$rootScope.sidebar.show = false;
|
||||
});
|
||||
$transitions.onExit({from: 'board.card'}, function ($state) {
|
||||
$rootScope.sidebar.show = false;
|
||||
});
|
||||
$transitions.onExit({from: 'board.detail'}, function ($state) {
|
||||
$rootScope.sidebar.show = false;
|
||||
});
|
||||
|
||||
$('link[rel="shortcut icon"]').attr(
|
||||
'href',
|
||||
OC.filePath('deck', 'img', 'app-512.png')
|
||||
);
|
||||
|
||||
// Select all elements with data-toggle="tooltips" in the document
|
||||
$('body').tooltip({
|
||||
selector: '[data-toggle="tooltip"]'
|
||||
});
|
||||
|
||||
});
|
||||
350
js/controller/ActivityController.js
Normal file
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* @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;
|
||||
44
js/controller/AppController.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import app from '../app/App.js';
|
||||
|
||||
/* globals oc_current_user: false */
|
||||
app.controller('AppController', function ($scope, $location, $http, $log, $rootScope, $attrs) {
|
||||
$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));
|
||||
}
|
||||
};
|
||||
});
|
||||
78
js/controller/AttachmentController.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* @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 = ``;
|
||||
}
|
||||
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;
|
||||
568
js/controller/BoardController.js
Normal file
@@ -0,0 +1,568 @@
|
||||
/*
|
||||
* @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 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) {
|
||||
|
||||
$scope.sidebar = $rootScope.sidebar;
|
||||
|
||||
$scope.id = $stateParams.boardId;
|
||||
$scope.status = {
|
||||
addCard: [],
|
||||
};
|
||||
$scope.newLabel = {};
|
||||
|
||||
$scope.OC = OC;
|
||||
$scope.stackservice = StackService;
|
||||
$scope.boardservice = BoardService;
|
||||
$scope.cardservice = CardService;
|
||||
$scope.statusservice = StatusService.getInstance();
|
||||
$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() {
|
||||
return $state.params;
|
||||
}, function (params) {
|
||||
$scope.params = params;
|
||||
}, true);
|
||||
$scope.params = $state.params;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
$scope.refreshData();
|
||||
};
|
||||
|
||||
$scope.$watch(function () {
|
||||
if (typeof BoardService.getCurrent() !== 'undefined') {
|
||||
return BoardService.getCurrent().title;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, function () {
|
||||
$scope.setPageTitle();
|
||||
});
|
||||
$scope.setPageTitle = function () {
|
||||
if (BoardService.getCurrent()) {
|
||||
document.title = BoardService.getCurrent().title + ' | Deck - ' + oc_defaults.name;
|
||||
} else {
|
||||
document.title = 'Deck - ' + oc_defaults.name;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.statusservice.retainWaiting();
|
||||
$scope.statusservice.retainWaiting();
|
||||
|
||||
// handle filter parameter for switching between archived/unarchived cards
|
||||
$scope.switchFilter = function (filter) {
|
||||
$state.go('.', {filter: filter});
|
||||
};
|
||||
$scope.$watch(function() {
|
||||
return $scope.params.filter;
|
||||
}, function (filter) {
|
||||
if (filter === 'archive') {
|
||||
$scope.loadArchived();
|
||||
} else {
|
||||
$scope.loadDefault();
|
||||
}
|
||||
});
|
||||
|
||||
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 () {
|
||||
$scope.refreshData();
|
||||
}, true);
|
||||
$scope.refreshData = function () {
|
||||
if ($scope.params.filter === 'archive') {
|
||||
$scope.filterData('-lastModified', $scope.searchText);
|
||||
} else {
|
||||
$scope.filterData('order', $scope.searchText);
|
||||
}
|
||||
};
|
||||
$scope.checkCanEdit = function () {
|
||||
return !BoardService.getCurrent().archived;
|
||||
};
|
||||
|
||||
// filter cards here, as ng-sortable will not work nicely with html-inline filters
|
||||
$scope.filterData = function (order, text) {
|
||||
if ($scope.stacks === undefined) {
|
||||
return;
|
||||
}
|
||||
angular.copy(StackService.getData(), $scope.stacks);
|
||||
$scope.stacks = $filter('orderBy')($scope.stacks, 'order');
|
||||
angular.forEach($scope.stacks, function (value, key) {
|
||||
var cards = $filter('cardSearchFilter')(value.cards, text);
|
||||
cards = $filter('orderBy')(cards, order);
|
||||
$scope.stacks[key].cards = cards;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.loadDefault = function () {
|
||||
StackService.fetchAll($scope.id).then(function (data) {
|
||||
$scope.statusservice.releaseWaiting();
|
||||
}, function (error) {
|
||||
$scope.statusservice.setError('Error occured', error);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.loadArchived = function () {
|
||||
StackService.fetchArchived($scope.id).then(function (data) {
|
||||
$scope.statusservice.releaseWaiting();
|
||||
}, function (error) {
|
||||
$scope.statusservice.setError('Error occured', error);
|
||||
});
|
||||
};
|
||||
|
||||
// Handle initial Loading
|
||||
BoardService.fetchOne($scope.id).then(function (data) {
|
||||
$scope.statusservice.releaseWaiting();
|
||||
$scope.setPageTitle();
|
||||
}, function (error) {
|
||||
$scope.statusservice.setError('Error occured', error);
|
||||
});
|
||||
|
||||
$scope.searchForUser = function (search) {
|
||||
BoardService.searchUsers(search);
|
||||
};
|
||||
|
||||
$scope.newStack = {'boardId': $scope.id};
|
||||
$scope.newCard = {};
|
||||
|
||||
// Create a new Stack
|
||||
$scope.createStack = function () {
|
||||
StackService.create($scope.newStack).then(function (data) {
|
||||
$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);
|
||||
};
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.cardAndStackUndoDelete = function(deletedCard, associatedDeletedStack) {
|
||||
$scope.stackUndoDelete(associatedDeletedStack).then(function() {
|
||||
$scope.cardUndoDelete(deletedCard);
|
||||
});
|
||||
};
|
||||
|
||||
$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);
|
||||
};
|
||||
|
||||
$scope.labelDelete = function (label) {
|
||||
LabelService.delete(label.id);
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.labelCreate = function (label) {
|
||||
label.boardId = $scope.id;
|
||||
LabelService.create(label).then(function (data) {
|
||||
$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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.aclAdd = function (sharee) {
|
||||
sharee.boardId = $scope.id;
|
||||
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);
|
||||
};
|
||||
|
||||
$scope.aclTypeString = function (acl) {
|
||||
if (typeof acl === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
switch (acl.type) {
|
||||
case OC.Share.SHARE_TYPE_USER:
|
||||
return 'user';
|
||||
case OC.Share.SHARE_TYPE_GROUP:
|
||||
return 'group';
|
||||
case OC.Share.SHARE_TYPE_CIRCLE:
|
||||
return 'circles';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// settings for card sorting
|
||||
$scope.sortOptions = {
|
||||
id: 'card',
|
||||
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 newStack = event.dest.sortableScope.$parent.s.id;
|
||||
var oldStack = card.stackId;
|
||||
card.stackId = newStack;
|
||||
CardService.update(card);
|
||||
CardService.reorder(card, order).then(function (data) {
|
||||
StackService.addCard(card);
|
||||
StackService.reorderCard(card, order);
|
||||
StackService.removeCard({
|
||||
id: card.id,
|
||||
stackId: oldStack
|
||||
});
|
||||
});
|
||||
},
|
||||
orderChanged: function (event) {
|
||||
var order = event.dest.index;
|
||||
var card = $scope.cardservice.get(event.source.itemScope.c.id);
|
||||
var stack = event.dest.sortableScope.$parent.s.id;
|
||||
CardService.reorder(card, order).then(function (data) {
|
||||
StackService.reorderCard(card, order);
|
||||
$scope.refreshData();
|
||||
});
|
||||
},
|
||||
scrollableContainer: '#innerBoard',
|
||||
containerPositioning: 'relative',
|
||||
containment: '#innerBoard',
|
||||
longTouch: true,
|
||||
// auto scroll on drag
|
||||
dragMove: function (itemPosition, containment, eventObj) {
|
||||
if (eventObj) {
|
||||
var container = $('#board');
|
||||
var offset = container.offset();
|
||||
var targetX = eventObj.pageX - (offset.left || container.scrollLeft());
|
||||
var targetY = eventObj.pageY - (offset.top || container.scrollTop());
|
||||
if (targetX < offset.left) {
|
||||
container.scrollLeft(container.scrollLeft() - 25);
|
||||
} else if (targetX > container.width()) {
|
||||
container.scrollLeft(container.scrollLeft() + 25);
|
||||
}
|
||||
if (targetY < offset.top) {
|
||||
container.scrollTop(container.scrollTop() - 25);
|
||||
} else if (targetY > container.height()) {
|
||||
container.scrollTop(container.scrollTop() + 25);
|
||||
}
|
||||
}
|
||||
},
|
||||
accept: function (sourceItemHandleScope, destSortableScope, destItemScope) {
|
||||
return sourceItemHandleScope.sortableScope.options.id === 'card';
|
||||
}
|
||||
};
|
||||
|
||||
$scope.sortOptionsStack = {
|
||||
id: 'stack',
|
||||
orderChanged: function (event) {
|
||||
var order = event.dest.index;
|
||||
var stack = event.source.itemScope.s;
|
||||
StackService.reorder(stack, order).then(function (data) {
|
||||
$scope.refreshData();
|
||||
});
|
||||
},
|
||||
scrollableContainer: '#board',
|
||||
containerPositioning: 'relative',
|
||||
containment: '#innerBoard',
|
||||
dragMove: function (itemPosition, containment, eventObj) {
|
||||
if (eventObj) {
|
||||
var container = $('#board');
|
||||
var offset = container.offset();
|
||||
var targetX = eventObj.pageX - (offset.left || container.scrollLeft());
|
||||
var targetY = eventObj.pageY - (offset.top || container.scrollTop());
|
||||
if (targetX < offset.left) {
|
||||
container.scrollLeft(container.scrollLeft() - 50);
|
||||
} else if (targetX > container.width()) {
|
||||
container.scrollLeft(container.scrollLeft() + 50);
|
||||
}
|
||||
if (targetY < offset.top) {
|
||||
container.scrollTop(container.scrollTop() - 50);
|
||||
} else if (targetY > container.height()) {
|
||||
container.scrollTop(container.scrollTop() + 50);
|
||||
}
|
||||
}
|
||||
},
|
||||
accept: function (sourceItemHandleScope, destSortableScope, destItemScope) {
|
||||
return sourceItemHandleScope.sortableScope.options.id === 'stack';
|
||||
}
|
||||
};
|
||||
|
||||
$scope.labelStyle = function (color) {
|
||||
return {
|
||||
'background-color': '#' + color,
|
||||
'color': $filter('textColorFilter')(color)
|
||||
};
|
||||
};
|
||||
|
||||
$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;
|
||||
};
|
||||
|
||||
});
|
||||
286
js/controller/CardController.js
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* @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 app moment angular OC OCP OCA */
|
||||
import app from '../app/App.js';
|
||||
|
||||
app.controller('CardController', function ($scope, $rootScope, $sce, $location, $stateParams, $state, $interval, $timeout, $filter, BoardService, CardService, StackService, StatusService, markdownItConverter, FileService) {
|
||||
$scope.sidebar = $rootScope.sidebar;
|
||||
$scope.status = {
|
||||
renameTitle: '',
|
||||
lastEdit: 0,
|
||||
lastSave: Date.now()
|
||||
};
|
||||
|
||||
$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;
|
||||
$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;
|
||||
}
|
||||
if (BoardService.isArchived() || CardService.getCurrent().archived) {
|
||||
return false;
|
||||
}
|
||||
if ($scope.card.archived || !$scope.boardservice.canEdit()) {
|
||||
return false;
|
||||
}
|
||||
$scope.status.cardEditDescription = true;
|
||||
$scope.status.edit = angular.copy(CardService.getCurrent());
|
||||
return true;
|
||||
};
|
||||
$scope.cardEditDescriptionChanged = function ($event) {
|
||||
$scope.status.lastEdit = Date.now();
|
||||
var header = $('.tabDetails');
|
||||
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) {
|
||||
$scope.status.lastSave = currentTime;
|
||||
$scope.status.saving = true;
|
||||
var header = $('.tabDetails');
|
||||
header.find('.save-indicator.unsaved').fadeIn(500);
|
||||
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);
|
||||
$scope.status.saving = false;
|
||||
});
|
||||
}
|
||||
}, 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
|
||||
$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');
|
||||
header.find('.save-indicator.unsaved').hide();
|
||||
header.find('.save-indicator.saved').fadeIn(500).fadeOut(1000);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.labelAssign = function (element, model) {
|
||||
CardService.assignLabel($scope.cardId, element.id).then(function (data) {
|
||||
});
|
||||
};
|
||||
|
||||
$scope.labelRemove = function (element, model) {
|
||||
CardService.removeLabel($scope.cardId, element.id).then(function (data) {
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setDuedate = function (duedate) {
|
||||
var element = CardService.getCurrent();
|
||||
var newDate = moment(element.duedate);
|
||||
if(!newDate.isValid()) {
|
||||
newDate = moment();
|
||||
}
|
||||
newDate.date(duedate.date());
|
||||
newDate.month(duedate.month());
|
||||
newDate.year(duedate.year());
|
||||
element.duedate = newDate.toISOString();
|
||||
CardService.update(element);
|
||||
};
|
||||
$scope.setDuedateTime = function (time) {
|
||||
var element = CardService.getCurrent();
|
||||
var newDate = moment(element.duedate);
|
||||
if(!newDate.isValid()) {
|
||||
newDate = moment();
|
||||
}
|
||||
newDate.hour(time.hour());
|
||||
newDate.minute(time.minute());
|
||||
element.duedate = newDate.toISOString();
|
||||
CardService.update(element);
|
||||
};
|
||||
|
||||
$scope.resetDuedate = function () {
|
||||
var element = CardService.getCurrent();
|
||||
element.duedate = null;
|
||||
CardService.update(element);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show ui-select field when clicking the add button
|
||||
*/
|
||||
$scope.toggleAssignUser = function() {
|
||||
$scope.status.showAssignUser = !$scope.status.showAssignUser;
|
||||
if ($scope.status.showAssignUser === true) {
|
||||
$timeout(function () {
|
||||
$('#assignUserSelect').find('a').click();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide ui-select when select list is closed
|
||||
*/
|
||||
$scope.assingUserOpenClose = function(isOpen) {
|
||||
$scope.status.showAssignUser = isOpen;
|
||||
};
|
||||
|
||||
$scope.addAssignedUser = function(item) {
|
||||
CardService.assignUser(CardService.getCurrent(), item.uid).then(function (data) {
|
||||
});
|
||||
$scope.status.showAssignUser = false;
|
||||
};
|
||||
|
||||
$scope.removeAssignedUser = function(uid) {
|
||||
CardService.unassignUser(CardService.getCurrent(), uid).then(function (data) {
|
||||
});
|
||||
};
|
||||
|
||||
$scope.labelStyle = function (color) {
|
||||
return {
|
||||
'background-color': '#' + color,
|
||||
'color': $filter('textColorFilter')(color)
|
||||
};
|
||||
};
|
||||
|
||||
$scope.isTimelineEnabled = function() {
|
||||
return OCP.Comments && OCA.Activity;
|
||||
};
|
||||
|
||||
});
|
||||
44
js/controller/ColorPickerController.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* @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};
|
||||
};
|
||||
}]);
|
||||
255
js/controller/ListController.js
Normal file
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* @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 app angular oc_isadmin */
|
||||
|
||||
var ListController = function ($scope, $location, $filter, BoardService, $element, $timeout, $stateParams, $state, StatusService, $http, $q, $rootScope) {
|
||||
|
||||
function calculateNewColor() {
|
||||
var boards = BoardService.getAll();
|
||||
var boardKeys = Object.keys(boards);
|
||||
var colorOccurrences = [];
|
||||
|
||||
for (var i = 0; i < $scope.colors.length; i++) {
|
||||
colorOccurrences.push(0);
|
||||
}
|
||||
|
||||
for (var j = 0; j < boardKeys.length; j++) {
|
||||
var key = boardKeys[j];
|
||||
var board = boards[key];
|
||||
|
||||
if (board && $scope.colors.indexOf(board.color) !== -1) {
|
||||
colorOccurrences[$scope.colors.indexOf(board.color)]++;
|
||||
}
|
||||
}
|
||||
|
||||
return $scope.colors[colorOccurrences.indexOf(Math.min.apply(Math, colorOccurrences))];
|
||||
}
|
||||
|
||||
$scope.boards = [];
|
||||
$scope.newBoard = {};
|
||||
$scope.status = {
|
||||
deleteUndo: [],
|
||||
filter: $stateParams.filter ? $stateParams.filter : '',
|
||||
sidebar: false
|
||||
};
|
||||
$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') {
|
||||
$scope.boardservice.sidebar = $scope.boardservice.getData();
|
||||
$scope.boardservice.sidebar = $filter('orderBy')($scope.boardservice.sidebar, 'title');
|
||||
$scope.boardservice.sidebar = $filter('cardFilter')($scope.boardservice.sidebar, {archived: false});
|
||||
} else {
|
||||
$scope.boardservice.sorted = $scope.boardservice.getData();
|
||||
if ($scope.status.filter === 'archived') {
|
||||
var filter = {};
|
||||
filter[$scope.status.filter] = true;
|
||||
$scope.boardservice.sorted = $filter('cardFilter')($scope.boardservice.sorted, filter);
|
||||
} else if ($scope.status.filter === 'shared') {
|
||||
$scope.boardservice.sorted = $filter('cardFilter')($scope.boardservice.sorted, {archived: false});
|
||||
$scope.boardservice.sorted = $filter('boardFilterAcl')($scope.boardservice.sorted);
|
||||
} else {
|
||||
$scope.boardservice.sorted = $filter('cardFilter')($scope.boardservice.sorted, {archived: false});
|
||||
}
|
||||
$scope.boardservice.sorted = $filter('orderBy')($scope.boardservice.sorted, ['deletedAt', 'title']);
|
||||
}
|
||||
};
|
||||
|
||||
var finishedLoading = function() {
|
||||
filterData();
|
||||
$scope.newBoard.color = calculateNewColor();
|
||||
};
|
||||
|
||||
var initialize = function () {
|
||||
$scope.statusservice = StatusService.listStatus;
|
||||
|
||||
if($element.attr('id') === 'app-navigation') {
|
||||
$scope.statusservice.retainWaiting();
|
||||
BoardService.fetchAll().then(function(data) {
|
||||
finishedLoading();
|
||||
$scope.statusservice.releaseWaiting();
|
||||
BoardService.loaded = true;
|
||||
}, function (error) {
|
||||
$scope.statusservice.setError('Error occured', error);
|
||||
});
|
||||
} else {
|
||||
/* initialize main list controller when board list is loaded */
|
||||
var boardDataWatch = $scope.$watch(function () {
|
||||
return $scope.boardservice.loaded;
|
||||
}, function () {
|
||||
if (BoardService.loaded === true) {
|
||||
boardDataWatch();
|
||||
finishedLoading();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.$watch(function () {
|
||||
return $scope.boardservice.data;
|
||||
}, function () {
|
||||
filterData();
|
||||
}, true);
|
||||
|
||||
/* Watch for board filter change */
|
||||
$scope.$watchCollection(function(){
|
||||
return $state.params;
|
||||
}, function(){
|
||||
$scope.status.filter = $state.params.filter;
|
||||
filterData();
|
||||
});
|
||||
};
|
||||
initialize();
|
||||
|
||||
$scope.selectColor = function(color) {
|
||||
$scope.newBoard.color = color;
|
||||
};
|
||||
|
||||
$scope.gotoBoard = function(board) {
|
||||
if(board.deletedAt > 0) {
|
||||
return false;
|
||||
}
|
||||
return $state.go('board', {boardId: board.id});
|
||||
};
|
||||
|
||||
$scope.boardCreate = function() {
|
||||
if(!$scope.newBoard.title || !$scope.newBoard.color) {
|
||||
$scope.status.addBoard=false;
|
||||
return;
|
||||
}
|
||||
BoardService.create($scope.newBoard)
|
||||
.then(function (response) {
|
||||
$scope.newBoard = {};
|
||||
$scope.newBoard.color = calculateNewColor();
|
||||
$scope.status.addBoard=false;
|
||||
filterData();
|
||||
}, function(error) {
|
||||
$scope.status.createBoard = 'Unable to insert board: ' + error.message;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.boardUpdate = function(board) {
|
||||
BoardService.update(board).then(function(data) {
|
||||
board.status.edit = false;
|
||||
filterData();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.boardUpdateBegin = function(board) {
|
||||
$scope.updatingBoard = angular.copy(board);
|
||||
};
|
||||
|
||||
$scope.boardUpdateReset = function(board) {
|
||||
board.title = $scope.updatingBoard.title;
|
||||
board.color = $scope.updatingBoard.color;
|
||||
filterData();
|
||||
board.status.edit = false;
|
||||
};
|
||||
|
||||
$scope.boardArchive = function (board) {
|
||||
board.archived = true;
|
||||
BoardService.update(board).then(function(data) {
|
||||
filterData();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.boardUnarchive = function (board) {
|
||||
board.archived = false;
|
||||
BoardService.update(board).then(function(data) {
|
||||
filterData();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.boardDelete = function(board) {
|
||||
BoardService.delete(board.id).then(function (data) {
|
||||
filterData();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.boardDeleteUndo = function (board) {
|
||||
BoardService.deleteUndo(board.id).then(function (data) {
|
||||
filterData();
|
||||
});
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export default ListController;
|
||||
48
js/directive/appPopoverMenuUtils.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
app.directive('appPopoverMenuUtils', function () {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'C',
|
||||
link: function (scope, elm) {
|
||||
var menu = elm.find('.popovermenu');
|
||||
var button = elm.find('button');
|
||||
button.click(function (e) {
|
||||
var popovermenus = $('.popovermenu');
|
||||
var shouldShow = menu.hasClass('hidden');
|
||||
popovermenus.addClass('hidden');
|
||||
if (shouldShow) {
|
||||
menu.toggleClass('hidden');
|
||||
}
|
||||
e.stopPropagation();
|
||||
});
|
||||
scope.$on('documentClicked', function (scope, event) {
|
||||
/* prevent closing popover if target has no-close class */
|
||||
if (event.target !== button && !$(event.target).hasClass('no-close')) {
|
||||
menu.addClass('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
47
js/directive/appnavigationentryutils.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
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 () {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'C',
|
||||
link: function (scope, elm) {
|
||||
|
||||
var menu = elm.siblings('.app-navigation-entry-menu');
|
||||
var button = $(elm)
|
||||
.find('.app-navigation-entry-utils-menu-button button');
|
||||
|
||||
button.click(function () {
|
||||
menu.toggleClass('open');
|
||||
});
|
||||
scope.$on('documentClicked', function (scope, event) {
|
||||
if (event.target !== button[0]) {
|
||||
menu.removeClass('open');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,27 +2,28 @@
|
||||
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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
|
||||
*
|
||||
*
|
||||
* 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 'icons';
|
||||
@import 'print';
|
||||
app.directive('autofocusOnInsert', function () {
|
||||
'use strict';
|
||||
return function (scope, elm) {
|
||||
elm.focus();
|
||||
};
|
||||
});
|
||||
55
js/directive/avatar.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
app.directive('avatar', function() {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'AEC',
|
||||
transclude: true,
|
||||
replace: true,
|
||||
template: '<div class="avatardiv-container"><div class="avatardiv" data-toggle="tooltip" ng-transclude></div></div>',
|
||||
scope: { attr: '=' },
|
||||
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') {
|
||||
avatardiv.contactsMenu(value, 0, $(element));
|
||||
avatardiv.addClass('has-contactsmenu');
|
||||
}
|
||||
if(typeof attr.tooltip !== 'undefined' && attr.tooltip !== 'false') {
|
||||
$(element).tooltip({
|
||||
title: scope.displayname,
|
||||
placement: 'top'
|
||||
});
|
||||
}
|
||||
avatardiv.avatar(value, scope.size, false, false, false, attr.displayname);
|
||||
},
|
||||
controller: function () {}
|
||||
};
|
||||
});
|
||||
@@ -19,21 +19,20 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
const arrayMove = function(arrayToSort, removedIndex, addedIndex) {
|
||||
if (removedIndex === null && addedIndex === null) return arrayToSort
|
||||
app.directive('bindHtmlCompile', function ($compile) {
|
||||
'use strict';
|
||||
|
||||
const result = [...arrayToSort]
|
||||
let itemToAdd = arrayToSort[removedIndex]
|
||||
|
||||
if (removedIndex !== null) {
|
||||
itemToAdd = result.splice(removedIndex, 1)[0]
|
||||
}
|
||||
|
||||
if (addedIndex !== null) {
|
||||
result.splice(addedIndex, 0, itemToAdd)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export default arrayMove
|
||||
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);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
42
js/directive/contactsmenudelete.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* @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('contactsmenudelete', function() {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority: 1,
|
||||
link: function(scope, element, attr){
|
||||
var user = attr.user;
|
||||
var menu = $(element).parent().find('.contactsmenu-popover');
|
||||
if (oc_current_user === user) {
|
||||
menu.children(':first').remove();
|
||||
}
|
||||
var menuEntry = $('<li><a><span class="icon icon-delete"></span><span>' + t('deck', 'Remove user from card') + '</span></a></li>');
|
||||
menuEntry.on('click', function () {
|
||||
scope.removeAssignedUser(user);
|
||||
});
|
||||
$(menu).append(menuEntry);
|
||||
}
|
||||
};
|
||||
});
|
||||
59
js/directive/contenteditable.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* @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();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
55
js/directive/datepicker.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* @copyright Copyright (c) 2017 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';
|
||||
|
||||
/* global app */
|
||||
/* gloabl t */
|
||||
/* global moment */
|
||||
|
||||
app.directive('datepicker', function () {
|
||||
'use strict';
|
||||
return {
|
||||
link: function (scope, elm, attr) {
|
||||
return elm.datepicker({
|
||||
dateFormat: moment.localeData().longDateFormat('L').replace('YYYY', 'YY').toLowerCase(),
|
||||
onSelect: function(date, inst) {
|
||||
var selectedDate = $(this).datepicker('getDate');
|
||||
scope.setDuedate(moment(selectedDate));
|
||||
scope.$apply();
|
||||
},
|
||||
beforeShow: function(input, inst) {
|
||||
var dp, marginLeft;
|
||||
dp = $(inst).datepicker('widget');
|
||||
marginLeft = -Math.abs($(input).outerWidth() - dp.outerWidth()) / 2 + 'px';
|
||||
dp.css({
|
||||
'margin-left': marginLeft
|
||||
});
|
||||
$('div.ui-datepicker:before').css({
|
||||
'left': 100 + 'px'
|
||||
});
|
||||
return $('.hasDatepicker').datepicker();
|
||||
},
|
||||
minDate: null
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
41
js/directive/elastic.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
// original idea from blockloop: http://stackoverflow.com/a/24090733
|
||||
app.directive('elastic', [
|
||||
'$timeout',
|
||||
function($timeout) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, element) {
|
||||
$scope.initialHeight = $scope.initialHeight || element[0].style.height;
|
||||
var resize = function() {
|
||||
element[0].style.height = $scope.initialHeight;
|
||||
element[0].style.height = "" + element[0].scrollHeight + "px";
|
||||
};
|
||||
element.on("input change", resize);
|
||||
$timeout(resize, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
||||
61
js/directive/search.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
app.directive('search', function ($document, $location) {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
'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('');
|
||||
});
|
||||
});
|
||||
} 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) {
|
||||
doSearch();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
48
js/directive/timepicker.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* @copyright Copyright (c) 2017 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 '../legacy/jquery.ui.timepicker.js';
|
||||
import 'legacy/jquery.ui.timepicker.css';
|
||||
|
||||
/* global app */
|
||||
/* global t */
|
||||
/* global moment */
|
||||
|
||||
app.directive('timepicker', function() {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elm, attr) {
|
||||
return $(elm).timepicker({
|
||||
onSelect: function(date, inst) {
|
||||
scope.setDuedateTime(moment('2000-01-01 ' + date));
|
||||
scope.$apply();
|
||||
},
|
||||
myPosition: 'center top',
|
||||
atPosition: 'center bottom',
|
||||
hourText: t('deck', 'Hours'),
|
||||
minuteText: t('deck', 'Minutes'),
|
||||
showPeriodLabels: false
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
34
js/filters/boardFilterAcl.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* @copyright Copyright (c) 2017 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('boardFilterAcl', function() {
|
||||
return function(boards) {
|
||||
var _result = [];
|
||||
angular.forEach(boards, function(board){
|
||||
if(board.acl !== null && Object.keys(board.acl).length > 0) {
|
||||
_result.push(board);
|
||||
}
|
||||
});
|
||||
return _result;
|
||||
};
|
||||
});
|
||||
@@ -20,36 +20,18 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Board model
|
||||
*
|
||||
* @typedef {Object} Board
|
||||
* @property {String} title
|
||||
* @property {boolean} archived
|
||||
* @property {number} shared 1 (shared) or 0 (not shared)
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
/**
|
||||
* Stack model
|
||||
*
|
||||
* @typedef {Object} Stack
|
||||
* @property {String} title
|
||||
* @property {number} boardId
|
||||
* @property {number} order
|
||||
*/
|
||||
|
||||
/**
|
||||
* Card model
|
||||
*
|
||||
* @typedef {Object} Card
|
||||
* @property {String} title
|
||||
* @property {boolean} archived
|
||||
* @property {number} order
|
||||
*/
|
||||
/**
|
||||
* Label model
|
||||
*
|
||||
* @typedef {Object} Label
|
||||
* @property {String} title
|
||||
* @property {String} color
|
||||
*/
|
||||
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];
|
||||
};
|
||||
});
|
||||
40
js/filters/cardFilter.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
// usage | cardFilter({ member: 'admin'})
|
||||
|
||||
app.filter('cardFilter', function() {
|
||||
return function(cards, rules) {
|
||||
var _result = [];
|
||||
angular.forEach(cards, function(card){
|
||||
var _card = card;
|
||||
var keys = Object.keys(rules);
|
||||
keys.some(function(key, condition) {
|
||||
if(_card[key]===rules[key]) {
|
||||
_result.push(_card);
|
||||
}
|
||||
});
|
||||
});
|
||||
return _result;
|
||||
};
|
||||
});
|
||||
44
js/filters/cardSearchFilter.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
app.filter('cardSearchFilter', function() {
|
||||
return function(cards, searchString) {
|
||||
var _result = {};
|
||||
var rules = {
|
||||
title: searchString,
|
||||
//owner: searchString,
|
||||
};
|
||||
angular.forEach(cards, function(card){
|
||||
var _card = card;
|
||||
Object.keys(rules).some(function(rule) {
|
||||
if(_card[rule].search(rules[rule])>=0) {
|
||||
_result[_card.id] = _card;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return $.map(_result, function(value, index) {
|
||||
return [value];
|
||||
});
|
||||
};
|
||||
});
|
||||
63
js/filters/dateFilters.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
/* global app */
|
||||
/* global OC */
|
||||
/* global moment */
|
||||
|
||||
app.filter('relativeDateFilter', function() {
|
||||
return function (timestamp) {
|
||||
return OC.Util.relativeModifiedDate(timestamp*1000);
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('relativeDateFilterString', function() {
|
||||
return function (date) {
|
||||
return OC.Util.relativeModifiedDate(Date.parse(date));
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('dateToTimestamp', function() {
|
||||
return function (date) {
|
||||
return Date.parse(date);
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('parseDate', function() {
|
||||
return function (date) {
|
||||
if(moment(date).isValid()) {
|
||||
var dateFormat = moment.localeData().longDateFormat('L');
|
||||
return moment(date).format(dateFormat);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('parseTime', function() {
|
||||
return function (date) {
|
||||
if(moment(date).isValid()) {
|
||||
return moment(date).format('HH:mm');
|
||||
}
|
||||
return '';
|
||||
};
|
||||
});
|
||||
67
js/filters/iconWhiteFilter.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
app.filter('iconWhiteFilter', function () {
|
||||
return function (hex) {
|
||||
// RGB2HLS by Garry Tan
|
||||
// http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
|
||||
var result = /^([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex);
|
||||
var color = result ? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
} : null;
|
||||
if (result === null) {
|
||||
return '';
|
||||
}
|
||||
var r = color.r / 255;
|
||||
var g = color.g / 255;
|
||||
var b = color.b / 255;
|
||||
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
var h, s, l = (max + min) / 2;
|
||||
|
||||
if (max === min) {
|
||||
h = s = 0; // achromatic
|
||||
} else {
|
||||
var d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0);
|
||||
break;
|
||||
case g:
|
||||
h = (b - r) / d + 2;
|
||||
break;
|
||||
case b:
|
||||
h = (r - g) / d + 4;
|
||||
break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
if (l < 0.5) {
|
||||
return '-white';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
});
|
||||
38
js/filters/lightenColorFilter.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
app.filter('lightenColorFilter', function() {
|
||||
return function (hex) {
|
||||
var result = /^([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex);
|
||||
var color = result ? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
} : null;
|
||||
if (result !== null) {
|
||||
return 'rgba(' + color.r + ',' + color.g + ',' + color.b + ',0.7)';
|
||||
} else {
|
||||
return '#' + hex;
|
||||
}
|
||||
};
|
||||
});
|
||||
43
js/filters/orderObjectBy.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
app.filter('orderObjectBy', function(){
|
||||
return function(input, attribute) {
|
||||
if (!angular.isObject(input)) {
|
||||
return input;
|
||||
}
|
||||
var array = [];
|
||||
for(var objectKey in input) {
|
||||
if ({}.hasOwnProperty.call(input, objectKey)) {
|
||||
array.push(input[objectKey]);
|
||||
}
|
||||
}
|
||||
|
||||
array.sort(function(a, b){
|
||||
a = parseInt(a[attribute]);
|
||||
b = parseInt(b[attribute]);
|
||||
return a < b;
|
||||
});
|
||||
return array;
|
||||
};
|
||||
});
|
||||
69
js/filters/textColorFilter.js
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
app.filter('textColorFilter', function () {
|
||||
return function (hex) {
|
||||
// RGB2HLS by Garry Tan
|
||||
// http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
|
||||
var result = /^#?([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex);
|
||||
var color = result ? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
} : null;
|
||||
if (result !== null) {
|
||||
var r = color.r / 255;
|
||||
var g = color.g / 255;
|
||||
var b = color.b / 255;
|
||||
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
var h, s, l = (max + min) / 2;
|
||||
|
||||
if (max === min) {
|
||||
h = s = 0; // achromatic
|
||||
} else {
|
||||
var d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0);
|
||||
break;
|
||||
case g:
|
||||
h = (b - r) / d + 2;
|
||||
break;
|
||||
case b:
|
||||
h = (r - g) / d + 4;
|
||||
break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
if (l < 0.5) {
|
||||
return '#ffffff';
|
||||
} else {
|
||||
return '#000000';
|
||||
}
|
||||
} else {
|
||||
return '#000000';
|
||||
}
|
||||
|
||||
};
|
||||
});
|
||||
46
js/filters/withoutAssignedUsers.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* @copyright Copyright (c) 2017 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';
|
||||
|
||||
/* global app */
|
||||
/* global angular */
|
||||
|
||||
/*
|
||||
* Remove all assignedUsers from users list
|
||||
*/
|
||||
app.filter('withoutAssignedUsers', function () {
|
||||
return function (users, assignedUsers) {
|
||||
var _result = [];
|
||||
angular.forEach(users, function (user) {
|
||||
var _found = false;
|
||||
angular.forEach(assignedUsers, function (assignedUser) {
|
||||
if (assignedUser.participant.uid === user.uid) {
|
||||
_found = true;
|
||||
}
|
||||
});
|
||||
if (_found === false) {
|
||||
_result.push(user);
|
||||
}
|
||||
});
|
||||
return _result;
|
||||
};
|
||||
});
|
||||
69
js/init-collections.js
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* @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));
|
||||
38
js/init.js
Normal file
@@ -0,0 +1,38 @@
|
||||
'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);
|
||||
});
|
||||
|
||||
161
js/legacy/commentcollection.js
Normal file
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* @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;
|
||||
|
||||
119
js/legacy/commentmodel.js
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
54
js/legacy/commentsummarymodel.js
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
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;
|
||||
|
||||
1
js/legacy/jquery.atwho.min.js
vendored
Normal file
561
js/legacy/jquery.caret.min.js
vendored
Normal file
@@ -0,0 +1,561 @@
|
||||
/*
|
||||
* @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.$));
|
||||
57
js/legacy/jquery.ui.timepicker.css
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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; }
|
||||
|
||||
|
||||
1496
js/legacy/jquery.ui.timepicker.js
vendored
Normal file
120
js/legacy/markdown-it-checkbox.js
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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
Normal file
56
js/package.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "deck",
|
||||
"description": "Frontend for the Nextcloud Deck app",
|
||||
"repository": "https://github.com/nextcloud/deck",
|
||||
"version": "1.0.0",
|
||||
"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"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"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": []
|
||||
}
|
||||
256
js/service/ActivityService.js
Normal file
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* @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};
|
||||
232
js/service/ApiService.js
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
/** global: oc_defaults */
|
||||
app.factory('ApiService', function ($http, $q) {
|
||||
var ApiService = function (http, endpoint) {
|
||||
// Consider renaming endpoint to resource
|
||||
this.endpoint = endpoint;
|
||||
this.baseUrl = this.generateUrl(this.endpoint);
|
||||
this.http = http;
|
||||
this.q = $q;
|
||||
this.data = {};
|
||||
this.deleted = {};
|
||||
this.id = null;
|
||||
this.sorted = [];
|
||||
};
|
||||
|
||||
ApiService.prototype.generateUrl = function(path) {
|
||||
return OC.generateUrl('/apps/deck/' + path);
|
||||
};
|
||||
|
||||
ApiService.prototype.tryAllThenDeleted = function(id) {
|
||||
let object = this.data[id];
|
||||
if (object === undefined) {
|
||||
object = this.deleted[id];
|
||||
}
|
||||
return object;
|
||||
};
|
||||
|
||||
ApiService.prototype.fetchAll = function () {
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
$http.get(this.baseUrl).then(function (response) {
|
||||
var objects = response.data;
|
||||
objects.forEach(function (obj) {
|
||||
self.data[obj.id] = obj;
|
||||
});
|
||||
deferred.resolve(self.data);
|
||||
}, function (error) {
|
||||
deferred.reject('Fetching ' + self.endpoint + ' failed');
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
ApiService.prototype.fetchDeleted = function (scopeId) {
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
self.deleted = {};
|
||||
$http.get(this.generateUrl(scopeId + '/' + this.endpoint + '/deleted')).then(function (response) {
|
||||
var objects = response.data;
|
||||
objects.forEach(function (obj) {
|
||||
if(self.deleted[obj.id] !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.deleted[obj.id] = obj;
|
||||
|
||||
if(self.afterFetch !== undefined) {
|
||||
self.afterFetch(obj);
|
||||
}
|
||||
});
|
||||
deferred.resolve(objects);
|
||||
}, function (error) {
|
||||
deferred.reject('Fetching ' + self.endpoint + ' failed');
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
|
||||
ApiService.prototype.fetchOne = function (id) {
|
||||
|
||||
this.id = id;
|
||||
var deferred = $q.defer();
|
||||
|
||||
if (id === undefined) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
$http.get(this.baseUrl + '/' + id).then(function (response) {
|
||||
var data = response.data;
|
||||
if (self.data[data.id] === undefined) {
|
||||
self.data[data.id] = response.data;
|
||||
}
|
||||
$.each(response.data, function (key, value) {
|
||||
self.data[data.id][key] = value;
|
||||
});
|
||||
deferred.resolve(response.data);
|
||||
|
||||
}, function (error) {
|
||||
deferred.reject('Fetching ' + self.endpoint + ' failed');
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
ApiService.prototype.create = function (entity) {
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
$http.post(this.baseUrl, entity).then(function (response) {
|
||||
self.add(response.data);
|
||||
deferred.resolve(response.data);
|
||||
}, function (error) {
|
||||
deferred.reject(error.data.message);
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
ApiService.prototype.update = function (entity) {
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
$http.put(this.baseUrl + '/' + entity.id, entity).then(function (response) {
|
||||
self.add(response.data);
|
||||
deferred.resolve(response.data);
|
||||
}, function (error) {
|
||||
deferred.reject(error.data.message);
|
||||
});
|
||||
return deferred.promise;
|
||||
|
||||
};
|
||||
|
||||
ApiService.prototype.delete = function (id) {
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
|
||||
$http.delete(this.baseUrl + '/' + id).then(function (response) {
|
||||
self.deleted[id] = self.data[id];
|
||||
delete self.data[id];
|
||||
|
||||
let deletedAt = response.data.deletedAt;
|
||||
if (deletedAt !== undefined) {
|
||||
self.deleted[id].deletedAt = deletedAt;
|
||||
}
|
||||
|
||||
deferred.resolve(response.data);
|
||||
|
||||
}, function (error) {
|
||||
deferred.reject('Deleting ' + self.endpoint + ' failed');
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
ApiService.prototype.undoDelete = function(entity) {
|
||||
var self = this;
|
||||
entity.deletedAt = 0;
|
||||
|
||||
var promise = this.update(entity);
|
||||
|
||||
promise.then(() => {
|
||||
self.data[entity.id] = entity;
|
||||
delete this.deleted[entity.id];
|
||||
});
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
// methods for managing data
|
||||
ApiService.prototype.clear = function () {
|
||||
this.data = {};
|
||||
};
|
||||
|
||||
ApiService.prototype.add = function (entity) {
|
||||
var element = this.data[entity.id];
|
||||
if (element === undefined) {
|
||||
this.data[entity.id] = entity;
|
||||
} else {
|
||||
Object.keys(entity).forEach(function (key) {
|
||||
if (entity[key] !== null && element[key] !== entity[key]) {
|
||||
element[key] = entity[key];
|
||||
}
|
||||
});
|
||||
element.status = {};
|
||||
}
|
||||
};
|
||||
|
||||
ApiService.prototype.addAll = function (entities) {
|
||||
var self = this;
|
||||
angular.forEach(entities, function (entity) {
|
||||
self.add(entity);
|
||||
});
|
||||
};
|
||||
|
||||
ApiService.prototype.getCurrent = function () {
|
||||
return this.data[this.id];
|
||||
};
|
||||
|
||||
ApiService.prototype.unsetCurrrent = function () {
|
||||
this.id = null;
|
||||
};
|
||||
|
||||
|
||||
ApiService.prototype.getData = function () {
|
||||
return $.map(this.data, function (value, index) {
|
||||
return [value];
|
||||
});
|
||||
};
|
||||
|
||||
ApiService.prototype.getAll = function () {
|
||||
return this.data;
|
||||
};
|
||||
|
||||
ApiService.prototype.get = function (id) {
|
||||
return this.data[id];
|
||||
};
|
||||
|
||||
ApiService.prototype.getName = function () {
|
||||
var funcNameRegex = /function (.{1,})\(/;
|
||||
var results = (funcNameRegex).exec((this).constructor.toString());
|
||||
return (results && results.length > 1) ? results[1] : '';
|
||||
};
|
||||
|
||||
return ApiService;
|
||||
|
||||
});
|
||||
272
js/service/BoardService.js
Normal file
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
/* global app OC angular */
|
||||
app.factory('BoardService', function (ApiService, $http, $q) {
|
||||
var BoardService = function ($http, ep, $q) {
|
||||
ApiService.call(this, $http, ep, $q);
|
||||
};
|
||||
BoardService.prototype = angular.copy(ApiService.prototype);
|
||||
|
||||
BoardService.prototype.delete = function (id) {
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
|
||||
$http.delete(this.baseUrl + '/' + id).then(function (response) {
|
||||
self.data[id].deletedAt = response.data.deletedAt;
|
||||
deferred.resolve(response.data);
|
||||
}, function (error) {
|
||||
deferred.reject('Deleting ' + self.endpoint + ' failed');
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
BoardService.prototype.deleteUndo = function (id) {
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
var _id = id;
|
||||
$http.post(this.baseUrl + '/' + id + '/deleteUndo').then(function (response) {
|
||||
self.data[_id].deletedAt = 0;
|
||||
deferred.resolve(response.data);
|
||||
}, function (error) {
|
||||
deferred.reject('Deleting ' + self.endpoint + ' failed');
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
BoardService.prototype.searchUsers = function (search) {
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
var searchData = {
|
||||
format: 'json',
|
||||
perPage: 4,
|
||||
itemType: [0, 1, 7]
|
||||
};
|
||||
if (search !== "") {
|
||||
searchData.search = search;
|
||||
}
|
||||
$http({
|
||||
method: 'GET',
|
||||
url: OC.linkToOCS('apps/files_sharing/api/v1') + 'sharees',
|
||||
params: searchData
|
||||
})
|
||||
.then(function (result) {
|
||||
var response = result.data;
|
||||
if (response.ocs.meta.statuscode !== 100) {
|
||||
deferred.reject('Error while searching for sharees');
|
||||
return;
|
||||
}
|
||||
self.sharees = [];
|
||||
|
||||
var users = response.ocs.data.exact.users.concat(response.ocs.data.users.slice(0, 4));
|
||||
var groups = response.ocs.data.exact.groups.concat(response.ocs.data.groups.slice(0, 4));
|
||||
var circles = response.ocs.data.exact.groups.concat(response.ocs.data.circles.slice(0, 4));
|
||||
|
||||
// filter out everyone who is already in the share list
|
||||
angular.forEach(users, function (item) {
|
||||
var acl = self.generateAcl(OC.Share.SHARE_TYPE_USER, item);
|
||||
var exists = false;
|
||||
angular.forEach(self.getCurrent().acl, function (acl) {
|
||||
if (acl.participant.primaryKey === item.value.shareWith) {
|
||||
exists = true;
|
||||
}
|
||||
});
|
||||
if (!exists && OC.getCurrentUser().uid !== item.value.shareWith) {
|
||||
self.sharees.push(acl);
|
||||
}
|
||||
});
|
||||
angular.forEach(groups, function (item) {
|
||||
var acl = self.generateAcl(OC.Share.SHARE_TYPE_GROUP, item);
|
||||
var exists = false;
|
||||
angular.forEach(self.getCurrent().acl, function (acl) {
|
||||
if (acl.participant.primaryKey === item.value.shareWith) {
|
||||
exists = true;
|
||||
}
|
||||
});
|
||||
if (!exists) {
|
||||
self.sharees.push(acl);
|
||||
}
|
||||
});
|
||||
angular.forEach(circles, function (item) {
|
||||
var acl = self.generateAcl(OC.Share.SHARE_TYPE_CIRCLE, item);
|
||||
var exists = false;
|
||||
angular.forEach(self.getCurrent().acl, function (acl) {
|
||||
if (acl.participant.primaryKey === item.value.shareWith) {
|
||||
exists = true;
|
||||
}
|
||||
});
|
||||
if (!exists) {
|
||||
self.sharees.push(acl);
|
||||
}
|
||||
});
|
||||
|
||||
deferred.resolve(self.sharees);
|
||||
}, function () {
|
||||
deferred.reject('Error while searching for sharees');
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
BoardService.prototype.generateAcl = function (type, ocsItem) {
|
||||
return {
|
||||
boardId: null,
|
||||
id: null,
|
||||
owner: false,
|
||||
participant: {
|
||||
primaryKey: ocsItem.value.shareWith,
|
||||
uid: ocsItem.value.shareWith,
|
||||
displayname: ocsItem.label
|
||||
},
|
||||
permissionEdit: true,
|
||||
permissionManage: false,
|
||||
permissionShare: false,
|
||||
type: type
|
||||
};
|
||||
};
|
||||
|
||||
BoardService.prototype.addAcl = function (acl) {
|
||||
var board = this.getCurrent();
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
var _acl = acl;
|
||||
$http.post(this.baseUrl + '/' + acl.boardId + '/acl', _acl).then(function (response) {
|
||||
if (!board.acl || board.acl.length === 0) {
|
||||
board.acl = {};
|
||||
}
|
||||
board.acl[response.data.id] = response.data;
|
||||
if (response.data.type === OC.Share.SHARE_TYPE_USER) {
|
||||
self._updateUsers();
|
||||
} else {
|
||||
self.fetchOne(response.data.boardId);
|
||||
}
|
||||
deferred.resolve(response.data);
|
||||
}, function (error) {
|
||||
deferred.reject('Error creating ACL ' + _acl);
|
||||
});
|
||||
acl = null;
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
BoardService.prototype.deleteAcl = function (acl) {
|
||||
var board = this.getCurrent();
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
$http.delete(this.baseUrl + '/' + acl.boardId + '/acl/' + acl.id).then(function (response) {
|
||||
var index = board.acl.findIndex((item) => item.id === response.data.id);
|
||||
delete board.acl[index];
|
||||
if (response.data.type === OC.Share.SHARE_TYPE_USER) {
|
||||
self._updateUsers();
|
||||
} else {
|
||||
self.fetchOne(response.data.boardId);
|
||||
}
|
||||
deferred.resolve(response.data);
|
||||
}, function (error) {
|
||||
deferred.reject('Error deleting ACL ' + acl.id);
|
||||
});
|
||||
acl = null;
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
BoardService.prototype.updateAcl = function (acl) {
|
||||
var board = this.getCurrent();
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
var _acl = acl;
|
||||
$http.put(this.baseUrl + '/' + acl.boardId + '/acl', _acl).then(function (response) {
|
||||
var index = board.acl.findIndex((item) => item.id === _acl.id);
|
||||
board.acl[index] = response.data;
|
||||
if (response.data.type === OC.Share.SHARE_TYPE_USER) {
|
||||
self._updateUsers();
|
||||
} else {
|
||||
self.fetchOne(response.data.boardId);
|
||||
}
|
||||
deferred.resolve(response.data);
|
||||
}, function (error) {
|
||||
deferred.reject('Error updating ACL ' + _acl);
|
||||
});
|
||||
acl = null;
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
BoardService.prototype._updateUsers = function () {
|
||||
if (!this.getCurrent() || !this.getCurrent().acl) {
|
||||
return [];
|
||||
}
|
||||
|
||||
this.getCurrent().users = [this.getCurrent().owner];
|
||||
let self = this;
|
||||
angular.forEach(this.getCurrent().acl, function(value, key) {
|
||||
if (value.type === OC.Share.SHARE_TYPE_USER) {
|
||||
self.getCurrent().users.push(value.participant);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
BoardService.prototype.getUsers = function () {
|
||||
if (this.getCurrent() && !this.getCurrent().users) {
|
||||
this._updateUsers();
|
||||
}
|
||||
return this.getCurrent().users;
|
||||
};
|
||||
|
||||
BoardService.prototype.canRead = function () {
|
||||
if (!this.getCurrent() || !this.getCurrent().permissions) {
|
||||
return false;
|
||||
}
|
||||
return this.getCurrent().permissions['PERMISSION_READ'];
|
||||
};
|
||||
|
||||
BoardService.prototype.canEdit = function () {
|
||||
if (!this.getCurrent() || !this.getCurrent().permissions) {
|
||||
return false;
|
||||
}
|
||||
return this.getCurrent().permissions['PERMISSION_EDIT'];
|
||||
};
|
||||
|
||||
BoardService.prototype.canManage = function (board) {
|
||||
if (board !== null && board !== undefined) {
|
||||
return board.permissions['PERMISSION_MANAGE'];
|
||||
}
|
||||
if (!this.getCurrent() || !this.getCurrent().permissions) {
|
||||
return false;
|
||||
}
|
||||
return this.getCurrent().permissions['PERMISSION_MANAGE'];
|
||||
};
|
||||
|
||||
BoardService.prototype.canShare = function () {
|
||||
if (!this.getCurrent() || !this.getCurrent().permissions) {
|
||||
return false;
|
||||
}
|
||||
return this.getCurrent().permissions['PERMISSION_SHARE'];
|
||||
};
|
||||
|
||||
BoardService.prototype.isArchived = function () {
|
||||
if (!this.getCurrent() || this.getCurrent().archived) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return new BoardService($http, 'boards', $q);
|
||||
|
||||
});
|
||||