Compare commits

..

13 Commits

Author SHA1 Message Date
Jakob Röhrl
13d57f7a8d attachents in details view
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2020-12-30 13:30:19 +01:00
Julius Härtl
638e8c152e Only run psalm on stable/master branches
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2020-12-30 13:02:22 +01:00
Julius Härtl
cbfee393fd Merge pull request #2690 from nextcloud/tests/relativeci
Try out relativeci
2020-12-30 12:45:40 +01:00
Julius Härtl
90cf64481e Test run relativeci
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2020-12-30 12:42:03 +01:00
Julius Härtl
d1db72fcf2 Merge pull request #2686 from nextcloud/tests/integration 2020-12-30 12:36:20 +01:00
Julius Härtl
89efcdee62 Simplify phpunit run configuration
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2020-12-30 12:13:27 +01:00
Julius Härtl
96e9f8275f Run integration tests
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2020-12-30 12:13:27 +01:00
Julius Härtl
9082075949 Merge pull request #2689 from nextcloud/fix/wordingBiggerView
modal > bigger view wording
2020-12-30 12:11:42 +01:00
Jakob Röhrl
9f708e54c9 modal > bigger view wording
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2020-12-30 11:55:00 +01:00
Julius Härtl
84fbbafcfb Merge pull request #2688 from nextcloud/fix/boardName
title > boardname
2020-12-30 11:54:37 +01:00
Julius Härtl
da24176aec Merge pull request #2687 from nextcloud/fix/3dots
3dots no opacity
2020-12-30 11:54:07 +01:00
Jakob Röhrl
06a973ae02 title > boardname
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2020-12-30 11:51:18 +01:00
Jakob Röhrl
2748b4a0b1 3dots no opacity
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2020-12-30 11:31:23 +01:00
20 changed files with 1099 additions and 722 deletions

View File

@@ -1,4 +1,4 @@
name: PHP AppCode Check
name: Nextcloud app code check
on:
pull_request:

View File

@@ -1,4 +1,4 @@
name: Build app package
name: Package build
on:
pull_request:

89
.github/workflows/integration.yml vendored Normal file
View File

@@ -0,0 +1,89 @@
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
./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

View File

@@ -1,4 +1,4 @@
name: Nightly build
name: Package nightly
on:
push:

View File

@@ -22,5 +22,8 @@ jobs:
npm ci
- name: build
run: |
npm run build --if-present
mkdir -p js
npm run build --if-present -- --profile --json | tail -n +6 > js/webpack-stats.json
npx relative-ci-agent

View File

@@ -10,82 +10,30 @@ on:
env:
APP_NAME: deck
jobs:
php:
integration:
runs-on: ubuntu-latest
strategy:
# do not stop on another job's failure
fail-fast: false
matrix:
php-versions: ['7.4']
databases: ['sqlite']
server-versions: ['master']
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ 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
coverage: none
- name: Set up PHPUnit
working-directory: apps/${{ env.APP_NAME }}
run: composer i
- name: Set up Nextcloud
env:
DB_PORT: 4444
run: |
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 password
./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
mysql:
runs-on: ubuntu-latest
strategy:
# do not stop on another job's failure
fail-fast: false
matrix:
php-versions: ['7.3', '7.4']
databases: ['mysql']
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:
@@ -114,11 +62,11 @@ jobs:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@v1
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
tools: phpunit
extensions: mbstring, iconv, fileinfo, intl, mysql, pdo_mysql
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql
coverage: none
- name: Set up PHPUnit
@@ -126,83 +74,14 @@ jobs:
run: composer i
- name: Set up Nextcloud
env:
DB_PORT: 4444
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 password
./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
pgsql:
runs-on: ubuntu-latest
strategy:
# do not stop on another job's failure
fail-fast: false
matrix:
php-versions: ['7.4']
databases: ['pgsql']
server-versions: ['master']
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
services:
postgres:
image: postgres
ports:
- 4444: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
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, pgsql, pdo_pgsql
coverage: none
- name: Set up PHPUnit
working-directory: apps/${{ env.APP_NAME }}
run: composer i
- name: Set up Nextcloud
env:
DB_PORT: 4444
run: |
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 password
./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 &

View File

@@ -1,6 +1,11 @@
name: Static analysis
on: [push, pull_request]
on:
pull_request:
push:
branches:
- master
- stable*
jobs:
static-psalm-analysis:

442
package-lock.json generated
View File

@@ -2717,6 +2717,25 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
"@bundle-stats/utils": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@bundle-stats/utils/-/utils-2.4.0.tgz",
"integrity": "sha512-JPM2q496ECnfbbVslXPm3aR0QaFth/XyqZSY7m/6/7EVkI0rO0Znx5jEZ/0RXtRuiU83eVsWrwoUabeLt5PO2g==",
"dev": true,
"requires": {
"convert-units": "^2.3.4",
"core-js": "^3.1.4",
"superstruct": "^0.8.3"
},
"dependencies": {
"core-js": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.1.tgz",
"integrity": "sha512-9Id2xHY1W7m8hCl8NkhQn5CufmF/WuR30BTRewvCXc1aZd3kMECwNZ69ndLbekKfakw9Rf2Xyc+QR6E7Gg+obg==",
"dev": true
}
}
},
"@cnakazawa/watch": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz",
@@ -4365,6 +4384,182 @@
"fastq": "^1.6.0"
}
},
"@relative-ci/agent": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@relative-ci/agent/-/agent-1.4.0.tgz",
"integrity": "sha512-bP2fFYK9rQZTs8bLHoyRjUzAYrRhcFHmfYZmyOu0lXoHe1tyXD7NxYKicLElG2RNjTlzbQS0bwsTU9xjK6wSGg==",
"dev": true,
"requires": {
"@bundle-stats/utils": "^2.1.1",
"@relative-ci/env-ci": "^5.1.0",
"core-js": "^3.6.4",
"cosmiconfig": "^7.0.0",
"debug": "^4.1.1",
"dotenv": "^8.2.0",
"fs-extra": "^9.0.0",
"isomorphic-fetch": "^3.0.0",
"lodash": "^4.17.15"
},
"dependencies": {
"core-js": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.1.tgz",
"integrity": "sha512-9Id2xHY1W7m8hCl8NkhQn5CufmF/WuR30BTRewvCXc1aZd3kMECwNZ69ndLbekKfakw9Rf2Xyc+QR6E7Gg+obg==",
"dev": true
},
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"fs-extra": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
"dev": true,
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^1.0.0"
}
},
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"dependencies": {
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"dev": true
}
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"universalify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
"dev": true
}
}
},
"@relative-ci/env-ci": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@relative-ci/env-ci/-/env-ci-5.1.0.tgz",
"integrity": "sha512-7KI6izFxbx0Hyzk1kgpL/DaAmP9wEArSGJWaCo/B4cgy/D5Q2lOH1P/LkItTq2rnPl96bo1aCOB6z0/RmEBA2w==",
"dev": true,
"requires": {
"execa": "^4.0.0",
"java-properties": "^1.0.0"
},
"dependencies": {
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
}
},
"execa": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
"integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==",
"dev": true,
"requires": {
"cross-spawn": "^7.0.0",
"get-stream": "^5.0.0",
"human-signals": "^1.1.1",
"is-stream": "^2.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^4.0.0",
"onetime": "^5.1.0",
"signal-exit": "^3.0.2",
"strip-final-newline": "^2.0.0"
}
},
"get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"dev": true,
"requires": {
"pump": "^3.0.0"
}
},
"is-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
"dev": true
},
"npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"dev": true,
"requires": {
"path-key": "^3.0.0"
}
},
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"requires": {
"shebang-regex": "^3.0.0"
}
},
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}
}
}
},
"@sinonjs/commons": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz",
@@ -5413,6 +5608,12 @@
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
},
"at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
"dev": true
},
"atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
@@ -7320,6 +7521,16 @@
"safe-buffer": "~5.1.1"
}
},
"convert-units": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/convert-units/-/convert-units-2.3.4.tgz",
"integrity": "sha512-ERHfdA0UhHJp1IpwE6PnFJx8LqG7B1ZjJ20UvVCmopEnVCfER68Tbe3kvN63dLbYXDA2xFWRE6zd4Wsf0w7POg==",
"dev": true,
"requires": {
"lodash.foreach": "2.3.x",
"lodash.keys": "2.3.x"
}
},
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
@@ -8163,6 +8374,12 @@
"domelementtype": "1"
}
},
"dotenv": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==",
"dev": true
},
"duplexify": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
@@ -11930,6 +12147,16 @@
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true
},
"isomorphic-fetch": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz",
"integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==",
"dev": true,
"requires": {
"node-fetch": "^2.6.1",
"whatwg-fetch": "^3.4.1"
}
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@@ -12058,6 +12285,12 @@
"istanbul-lib-report": "^3.0.0"
}
},
"java-properties": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz",
"integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==",
"dev": true
},
"jed": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/jed/-/jed-1.1.1.tgz",
@@ -14757,17 +14990,190 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
},
"lodash._basebind": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basebind/-/lodash._basebind-2.3.0.tgz",
"integrity": "sha1-K1vEUqDhBhQ7IYafIzvbWHQX0kg=",
"dev": true,
"requires": {
"lodash._basecreate": "~2.3.0",
"lodash._setbinddata": "~2.3.0",
"lodash.isobject": "~2.3.0"
}
},
"lodash._basecreate": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-2.3.0.tgz",
"integrity": "sha1-m4ioak3P97fzxh2Dovz8BnHsneA=",
"dev": true,
"requires": {
"lodash._renative": "~2.3.0",
"lodash.isobject": "~2.3.0",
"lodash.noop": "~2.3.0"
}
},
"lodash._basecreatecallback": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basecreatecallback/-/lodash._basecreatecallback-2.3.0.tgz",
"integrity": "sha1-N7KrF1kaM56YjbMln81GAZ16w2I=",
"dev": true,
"requires": {
"lodash._setbinddata": "~2.3.0",
"lodash.bind": "~2.3.0",
"lodash.identity": "~2.3.0",
"lodash.support": "~2.3.0"
}
},
"lodash._basecreatewrapper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basecreatewrapper/-/lodash._basecreatewrapper-2.3.0.tgz",
"integrity": "sha1-qgxhrZYETDkzN2ExSDqXWcNlEkc=",
"dev": true,
"requires": {
"lodash._basecreate": "~2.3.0",
"lodash._setbinddata": "~2.3.0",
"lodash._slice": "~2.3.0",
"lodash.isobject": "~2.3.0"
}
},
"lodash._createwrapper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-2.3.0.tgz",
"integrity": "sha1-0arhEC2t9EDo4G/BM6bt1/4UYHU=",
"dev": true,
"requires": {
"lodash._basebind": "~2.3.0",
"lodash._basecreatewrapper": "~2.3.0",
"lodash.isfunction": "~2.3.0"
}
},
"lodash._objecttypes": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.3.0.tgz",
"integrity": "sha1-aj6jmH3W7rgCGy1cnDA1Scwrrh4=",
"dev": true
},
"lodash._renative": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._renative/-/lodash._renative-2.3.0.tgz",
"integrity": "sha1-d9jt1M7SbdWXH54Vpfdy5OMX+9M=",
"dev": true
},
"lodash._setbinddata": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._setbinddata/-/lodash._setbinddata-2.3.0.tgz",
"integrity": "sha1-5WEEkKzRMnfVmFjZW18nJ/FQjwQ=",
"dev": true,
"requires": {
"lodash._renative": "~2.3.0",
"lodash.noop": "~2.3.0"
}
},
"lodash._shimkeys": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.3.0.tgz",
"integrity": "sha1-YR+TFJ4+bHIQlrSHae8pU3rai6k=",
"dev": true,
"requires": {
"lodash._objecttypes": "~2.3.0"
}
},
"lodash._slice": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._slice/-/lodash._slice-2.3.0.tgz",
"integrity": "sha1-FHGYEyhZly5GgMoppZkshVZpqlw=",
"dev": true
},
"lodash.bind": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-2.3.0.tgz",
"integrity": "sha1-wqjhi2jl7MFS4rFoJmEW/qWwFsw=",
"dev": true,
"requires": {
"lodash._createwrapper": "~2.3.0",
"lodash._renative": "~2.3.0",
"lodash._slice": "~2.3.0"
}
},
"lodash.foreach": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-2.3.0.tgz",
"integrity": "sha1-CDQEyR6EbudyRf3512UZxosq8Wg=",
"dev": true,
"requires": {
"lodash._basecreatecallback": "~2.3.0",
"lodash.forown": "~2.3.0"
}
},
"lodash.forown": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.forown/-/lodash.forown-2.3.0.tgz",
"integrity": "sha1-JPtKr4ANRfwtxgv+w84EyDajrX8=",
"dev": true,
"requires": {
"lodash._basecreatecallback": "~2.3.0",
"lodash._objecttypes": "~2.3.0",
"lodash.keys": "~2.3.0"
}
},
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
"lodash.identity": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-2.3.0.tgz",
"integrity": "sha1-awGiEMlIU1XCqRO0i2cRIZoXPe0=",
"dev": true
},
"lodash.isfunction": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-2.3.0.tgz",
"integrity": "sha1-aylz5HpkfPEucNZ2rqE2Q3BuUmc=",
"dev": true
},
"lodash.isobject": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.3.0.tgz",
"integrity": "sha1-LhbT/Fg9qYMZaJU/LY5tc0NPZ5k=",
"dev": true,
"requires": {
"lodash._objecttypes": "~2.3.0"
}
},
"lodash.keys": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.3.0.tgz",
"integrity": "sha1-s1D0+Syqn0WkouzwGEVM8vKK4lM=",
"dev": true,
"requires": {
"lodash._renative": "~2.3.0",
"lodash._shimkeys": "~2.3.0",
"lodash.isobject": "~2.3.0"
}
},
"lodash.noop": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-2.3.0.tgz",
"integrity": "sha1-MFnWKNUbv5N80qC2/Dp/ISpmnCw=",
"dev": true
},
"lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
"dev": true
},
"lodash.support": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.support/-/lodash.support-2.3.0.tgz",
"integrity": "sha1-fq8DivTw1qq3drRKptz8gDNMm/0=",
"dev": true,
"requires": {
"lodash._renative": "~2.3.0"
}
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
@@ -15591,6 +15997,12 @@
"lodash": "^4.17.15"
}
},
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
"dev": true
},
"node-forge": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
@@ -20243,6 +20655,24 @@
"postcss": "^7.0.2"
}
},
"superstruct": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.8.4.tgz",
"integrity": "sha512-48Ors8IVWZm/tMr8r0Si6+mJiB7mkD7jqvIzktjJ4+EnP5tBp0qOpiM1J8sCUorKx+TXWrfb3i1UcjdD1YK/wA==",
"dev": true,
"requires": {
"kind-of": "^6.0.2",
"tiny-invariant": "^1.0.6"
},
"dependencies": {
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
}
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -20501,6 +20931,12 @@
"setimmediate": "^1.0.4"
}
},
"tiny-invariant": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
"integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==",
"dev": true
},
"tinycolor2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
@@ -22243,6 +22679,12 @@
"iconv-lite": "0.4.24"
}
},
"whatwg-fetch": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz",
"integrity": "sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A==",
"dev": true
},
"whatwg-mimetype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",

View File

@@ -17,7 +17,7 @@
"license": "agpl",
"private": true,
"scripts": {
"build": "NODE_ENV=production webpack --progress --hide-modules --config webpack.js",
"build": "NODE_ENV=production webpack --progress --config webpack.js",
"dev": "NODE_ENV=development webpack --progress --config webpack.js",
"watch": "NODE_ENV=development webpack --progress --watch --config webpack.js",
"lint": "eslint --ext .js,.vue src",
@@ -75,6 +75,7 @@
"@nextcloud/eslint-config": "^2.2.0",
"@nextcloud/eslint-plugin": "^1.5.0",
"@nextcloud/webpack-vue-config": "^1.4.1",
"@relative-ci/agent": "^1.4.0",
"@vue/test-utils": "^1.1.2",
"acorn": "^8.0.4",
"babel-eslint": "^10.1.0",

8
relativeci.config.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
// Allow the agent to pick up the current commit message
includeCommitMessage: true,
webpack: {
// Path to Webpack stats JSON file
stats: './js/webpack-stats.json'
}
};

410
src/components/Controls.vue Normal file
View File

@@ -0,0 +1,410 @@
<!--
* @copyright Copyright (c) 2018 Michael Weimann <mail@michael-weimann.eu>
*
* @author Michael Weimann <mail@michael-weimann.eu>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
-->
<template>
<div class="controls">
<div v-if="overviewName" class="board-title">
<div class="board-bullet icon-calendar-dark" />
<h2>{{ overviewName }}</h2>
</div>
<div v-else-if="board" class="board-title">
<div :style="{backgroundColor: '#' + board.color}" class="board-bullet" />
<h2>{{ board.title }}</h2>
<p v-if="showArchived">
({{ t('deck', 'Archived cards') }})
</p>
</div>
<div v-if="board" class="board-actions">
<div v-if="canManage && !showArchived && !board.archived"
id="stack-add"
v-click-outside="hideAddStack">
<Actions v-if="!isAddStackVisible">
<ActionButton icon="icon-add" @click.stop="showAddStack">
{{ t('deck', 'Add list') }}
</ActionButton>
</Actions>
<form v-else @submit.prevent="addNewStack()">
<label for="new-stack-input-main" class="hidden-visually">{{ t('deck', 'Add list') }}</label>
<input id="new-stack-input-main"
v-model="newStackTitle"
v-focus
type="text"
class="no-close"
:placeholder="t('deck', 'List name')"
required>
<input v-tooltip="t('deck', 'Add list')"
class="icon-confirm"
type="submit"
value="">
</form>
</div>
<div class="board-action-buttons">
<Popover @show="filterVisible=true" @hide="filterVisible=false">
<Actions slot="trigger" :title="t('deck', 'Apply filter')">
<ActionButton v-if="isFilterActive" icon="icon-filter_set" />
<ActionButton v-else icon="icon-filter" />
</Actions>
<template>
<div v-if="filterVisible" class="filter">
<h3>{{ t('deck', 'Filter by tag') }}</h3>
<div v-for="label in labelsSorted" :key="label.id" class="filter--item">
<input
:id="label.id"
v-model="filter.tags"
type="checkbox"
class="checkbox"
:value="label.id"
@change="setFilter">
<label :for="label.id"><span class="label" :style="labelStyle(label)">{{ label.title }}</span></label>
</div>
<h3>{{ t('deck', 'Filter by assigned user') }}</h3>
<div class="filter--item">
<input
id="unassigned"
v-model="filter.unassigned"
type="checkbox"
class="checkbox"
value="unassigned"
@change="setFilter"
@click="beforeSetFilter">
<label for="unassigned">{{ t('deck', 'Unassigned') }}</label>
</div>
<div v-for="user in board.users" :key="user.uid" class="filter--item">
<input
:id="user.uid"
v-model="filter.users"
type="checkbox"
class="checkbox"
:value="user.uid"
@change="setFilter">
<label :for="user.uid"><Avatar :user="user.uid" :size="24" :disable-menu="true" /> {{ user.displayname }}</label>
</div>
<h3>{{ t('deck', 'Filter by due date') }}</h3>
<div class="filter--item">
<input
id="overdue"
v-model="filter.due"
type="radio"
class="radio"
value="overdue"
@change="setFilter"
@click="beforeSetFilter">
<label for="overdue">{{ t('deck', 'Overdue') }}</label>
</div>
<div class="filter--item">
<input
id="dueToday"
v-model="filter.due"
type="radio"
class="radio"
value="dueToday"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueToday">{{ t('deck', 'Next 24 hours') }}</label>
</div>
<div class="filter--item">
<input
id="dueWeek"
v-model="filter.due"
type="radio"
class="radio"
value="dueWeek"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueWeek">{{ t('deck', 'Next 7 days') }}</label>
</div>
<div class="filter--item">
<input
id="dueMonth"
v-model="filter.due"
type="radio"
class="radio"
value="dueMonth"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueMonth">{{ t('deck', 'Next 30 days') }}</label>
</div>
<div class="filter--item">
<input
id="noDue"
v-model="filter.due"
type="radio"
class="radio"
value="noDue"
@change="setFilter"
@click="beforeSetFilter">
<label for="noDue">{{ t('deck', 'No due date') }}</label>
</div>
<Button :disabled="!isFilterActive" @click="clearFilter">
{{ t('deck', 'Clear filter') }}
</Button>
</div>
</template>
</Popover>
<Actions>
<ActionButton
icon="icon-archive"
@click="toggleShowArchived">
{{ showArchived ? t('deck', 'Hide archived cards') : t('deck', 'Show archived cards') }}
</ActionButton>
<ActionButton v-if="compactMode"
icon="icon-toggle-compact-collapsed"
@click="toggleCompactMode">
{{ t('deck', 'Toggle compact mode') }}
</ActionButton>
<ActionButton v-else
icon="icon-toggle-compact-expanded"
@click="toggleCompactMode">
{{ t('deck', 'Toggle compact mode') }}
</ActionButton>
</Actions>
<!-- FIXME: ActionRouter currently doesn't work as an inline action -->
<Actions :title="t('deck', 'Details')">
<ActionButton icon="icon-menu-sidebar" @click="toggleDetailsView" />
</Actions>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import { Actions, ActionButton, Popover, Avatar } from '@nextcloud/vue'
import labelStyle from '../mixins/labelStyle'
export default {
name: 'Controls',
components: {
Actions, ActionButton, Popover, Avatar,
},
mixins: [labelStyle],
props: {
board: {
type: Object,
required: false,
default: null,
},
overviewName: {
type: String,
required: false,
default: null,
},
},
data() {
return {
newStackTitle: '',
stack: '',
filterVisible: false,
showArchived: false,
isAddStackVisible: false,
filter: { tags: [], users: [], due: '', unassigned: false },
}
},
computed: {
...mapGetters([
'canEdit',
'canManage',
]),
...mapState({
compactMode: state => state.compactMode,
}),
detailsRoute() {
return {
name: 'board.details',
}
},
isFilterActive() {
if (this.filter.tags.length !== 0 || this.filter.users.length !== 0 || this.filter.due !== '') {
return true
}
return false
},
labelsSorted() {
return [...this.board.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
},
},
watch: {
board(current, previous) {
if (current?.id !== previous?.id) {
this.clearFilter()
}
},
},
methods: {
beforeSetFilter(e) {
if (this.filter.due === e.target.value) {
this.filter.due = ''
this.$store.dispatch('setFilter', { ...this.filter })
}
if (e.target.value === 'unassigned') {
this.filter.users = []
}
},
setFilter() {
if (this.filter.users.length > 0) {
this.filter.unassigned = false
}
this.$nextTick(() => this.$store.dispatch('setFilter', { ...this.filter }))
},
toggleNav() {
this.$store.dispatch('toggleNav')
},
toggleCompactMode() {
this.$store.dispatch('toggleCompactMode')
},
toggleShowArchived() {
this.$store.dispatch('toggleShowArchived')
this.showArchived = !this.showArchived
},
addNewStack() {
this.stack = { title: this.newStackTitle }
this.$store.dispatch('createStack', this.stack)
this.newStackTitle = ''
this.stack = null
this.isAddStackVisible = false
},
showAddStack() {
this.isAddStackVisible = true
},
hideAddStack() {
this.isAddStackVisible = false
},
toggleDetailsView() {
if (this.$route.name === 'board.details') {
this.$router.push({ name: 'board' })
} else {
this.$router.push({ name: 'board.details' })
}
},
clearFilter() {
const filterReset = { tags: [], users: [], due: '' }
this.$store.dispatch('setFilter', { ...filterReset })
this.filter = filterReset
},
},
}
</script>
<style lang="scss" scoped>
.controls {
display: flex;
padding: 3px;
height: 44px;
padding-left: 44px;
.board-title {
display: flex;
align-items: center;
h2 {
margin: 0;
margin-right: 10px;
}
.board-bullet {
display: inline-block;
width: 20px;
height: 20px;
border: none;
border-radius: 50%;
background-color: transparent;
margin: 12px;
margin-left: -4px;
}
}
#stack-add form {
display: flex;
}
}
#app-navigation-toggle-custom {
position: static;
width: 44px;
height: 44px;
cursor: pointer;
opacity: 1;
display: inline-block !important;
}
.board-actions {
flex-grow: 1;
order: 100;
display: flex;
justify-content: flex-end;
}
.board-action-buttons {
display: flex;
button {
border: 0;
width: 44px;
margin: 0 0 0 -1px;
background-color: transparent;
}
}
.filter--item {
input + label {
display: block;
padding: 6px 0;
vertical-align: middle;
.avatardiv {
vertical-align: middle;
margin-bottom: 2px;
margin-right: 3px;
}
.label {
padding: 5px;
border-radius: 3px;
}
}
}
.filter {
width: 250px;
max-height: 80vh;
overflow: auto;
padding: 8px;
}
.filter h3 {
margin-top: 0px;
margin-bottom: 5px;
}
</style>
<style lang="scss">
.tooltip-inner.popover-inner {
text-align: left;
}
</style>

View File

@@ -29,7 +29,7 @@
<div class="board-list-bullet" />
</div>
<div class="board-list-title-cell">
{{ t('deck', 'Title') }}
{{ t('deck', 'Board name') }}
</div>
<div class="board-list-avatars-cell">
{{ t('deck', 'Members') }}

View File

@@ -21,58 +21,65 @@
-->
<template>
<AttachmentDragAndDrop :card-id="cardId" class="drop-upload--sidebar">
<button class="icon-upload" @click="clickAddNewAttachmment()">
{{ t('deck', 'Upload attachment') }}
</button>
<input ref="localAttachments"
type="file"
style="display: none;"
multiple
@change="handleUploadFile">
<ul class="attachment-list">
<li v-for="attachment in uploadQueue" :key="attachment.name" class="attachment">
<a class="fileicon" :style="mimetypeForAttachment('none')" />
<div class="details">
<a>
<div class="filename">
<span class="basename">{{ attachment.name }}</span>
</div>
<progress :value="attachment.progress" max="100" />
</a>
</div>
</li>
<li v-for="attachment in attachments"
:key="attachment.id"
class="attachment">
<a class="fileicon" :style="mimetypeForAttachment(attachment.extendedData.mimetype)" :href="attachmentUrl(attachment)" />
<div class="details">
<a :href="attachmentUrl(attachment)" target="_blank">
<div class="filename">
<span class="basename">{{ attachment.data }}</span>
</div>
<span class="filesize">{{ formattedFileSize(attachment.extendedData.filesize) }}</span>
<span class="filedate">{{ relativeDate(attachment.createdAt*1000) }}</span>
<span class="filedate">{{ attachment.createdBy }}</span>
</a>
</div>
<Actions v-if="selectable">
<ActionButton icon="icon-confirm" @click="$emit('selectAttachment', attachment)">
{{ t('deck', 'Add this attachment') }}
</ActionButton>
</Actions>
<Actions v-if="removable">
<ActionButton v-if="attachment.deletedAt === 0" icon="icon-delete" @click="$emit('deleteAttachment', attachment)">
{{ t('deck', 'Delete Attachment') }}
</ActionButton>
<div>
<h5>
{{ t('deck', 'Attachments') }}
<Actions>
<ActionButton icon="icon-upload" @click="clickAddNewAttachmment()">
{{ t('deck', 'Upload attachment') }}
</ActionButton>
</Actions>
</h5>
<AttachmentDragAndDrop :card-id="cardId" class="drop-upload--sidebar">
<input ref="localAttachments"
type="file"
style="display: none;"
multiple
@change="handleUploadFile">
<ul class="attachment-list">
<li v-for="attachment in uploadQueue" :key="attachment.name" class="attachment">
<a class="fileicon" :style="mimetypeForAttachment('none')" />
<div class="details">
<a>
<div class="filename">
<span class="basename">{{ attachment.name }}</span>
</div>
<progress :value="attachment.progress" max="100" />
</a>
</div>
</li>
<li v-for="attachment in attachments"
:key="attachment.id"
class="attachment">
<a class="fileicon" :style="mimetypeForAttachment(attachment.extendedData.mimetype)" :href="attachmentUrl(attachment)" />
<div class="details">
<a :href="attachmentUrl(attachment)" target="_blank">
<div class="filename">
<span class="basename">{{ attachment.data }}</span>
</div>
<span class="filesize">{{ formattedFileSize(attachment.extendedData.filesize) }}</span>
<span class="filedate">{{ relativeDate(attachment.createdAt*1000) }}</span>
<span class="filedate">{{ attachment.createdBy }}</span>
</a>
</div>
<Actions v-if="selectable">
<ActionButton icon="icon-confirm" @click="$emit('selectAttachment', attachment)">
{{ t('deck', 'Add this attachment') }}
</ActionButton>
</Actions>
<Actions v-if="removable">
<ActionButton v-if="attachment.deletedAt === 0" icon="icon-delete" @click="$emit('deleteAttachment', attachment)">
{{ t('deck', 'Delete Attachment') }}
</ActionButton>
<ActionButton v-else icon="icon-history" @click="$emit('restoreAttachment', attachment)">
{{ t('deck', 'Restore Attachment') }}
</ActionButton>
</Actions>
</li>
</ul>
</AttachmentDragAndDrop>
<ActionButton v-else icon="icon-history" @click="$emit('restoreAttachment', attachment)">
{{ t('deck', 'Restore Attachment') }}
</ActionButton>
</Actions>
</li>
</ul>
</AttachmentDragAndDrop>
</div>
</template>
<script>
@@ -171,6 +178,21 @@ export default {
<style lang="scss" scoped>
h5 {
border-bottom: 1px solid var(--color-border);
margin-top: 20px;
margin-bottom: 5px;
color: var(--color-text-maxcontrast);
.icon-upload {
background-size: 16px;
float: right;
margin-top: -14px;
opacity: .7;
}
}
.icon-upload {
padding-left: 35px;
background-position: 10px center;

View File

@@ -46,13 +46,6 @@
<CardSidebarTabDetails :card="currentCard" />
</AppSidebarTab>
<AppSidebarTab id="attachments"
:order="1"
:name="t('deck', 'Attachments')"
icon="icon-attach">
<CardSidebarTabAttachments :card="currentCard" />
</AppSidebarTab>
<AppSidebarTab
id="comments"
:order="2"
@@ -75,7 +68,6 @@
import { ActionButton, AppSidebar, AppSidebarTab } from '@nextcloud/vue'
import { mapState, mapGetters } from 'vuex'
import CardSidebarTabDetails from './CardSidebarTabDetails'
import CardSidebarTabAttachments from './CardSidebarTabAttachments'
import CardSidebarTabComments from './CardSidebarTabComments'
import CardSidebarTabActivity from './CardSidebarTabActivity'
import relativeDate from '../../mixins/relativeDate'
@@ -90,7 +82,6 @@ export default {
AppSidebar,
AppSidebarTab,
ActionButton,
CardSidebarTabAttachments,
CardSidebarTabComments,
CardSidebarTabActivity,
CardSidebarTabDetails,

View File

@@ -101,7 +101,6 @@
:lang="lang"
:formatter="format"
:disabled="saving || !canEdit"
:shortcuts="shortcuts"
confirm />
<Actions v-if="canEdit">
<ActionButton v-if="copiedCard.duedate" icon="icon-delete" @click="removeDue()">
@@ -118,6 +117,12 @@
type="deck-card" />
</div>
<AttachmentList
:card-id="card.id"
:removable="true"
@deleteAttachment="deleteAttachment"
@restoreAttachment="restoreAttachment" />
<Description :key="card.id" :card="card" />
</div>
</template>
@@ -126,7 +131,7 @@
import { mapState, mapGetters } from 'vuex'
import moment from '@nextcloud/moment'
import { Avatar, Actions, ActionButton, Multiselect, DatetimePicker } from '@nextcloud/vue'
import AttachmentList from './AttachmentList'
import { CollectionList } from 'nextcloud-vue-collections'
import Color from '../../mixins/color'
import {
@@ -147,6 +152,7 @@ export default {
ActionButton,
Avatar,
CollectionList,
AttachmentList,
},
mixins: [Color],
props: {
@@ -177,23 +183,6 @@ export default {
stringify: this.stringify,
parse: this.parse,
},
shortcuts: [
{
text: 'Today',
onClick() {
const date = new Date()
return date
},
},
{
text: 'Tomorrow',
onClick() {
const date = new Date()
date.setTime(date.getTime() + 3600 * 1000 * 24)
return date
},
},
],
}
},
computed: {
@@ -252,6 +241,12 @@ export default {
this.initialize()
},
methods: {
deleteAttachment(attachment) {
this.$store.dispatch('deleteAttachment', attachment)
},
restoreAttachment(attachment) {
this.$store.dispatch('restoreAttachment', attachment)
},
async initialize() {
if (!this.card) {
return
@@ -334,10 +329,6 @@ export default {
</script>
<style lang="scss" scoped>
.section-wrapper::v-deep .mx-datepicker-main .mx-datepicker-popup {
left: 0 !important;
}
.section-wrapper {
display: flex;
max-width: 100%;

View File

@@ -58,7 +58,7 @@
type="checkbox"
class="checkbox">
<label for="toggle-modal">
{{ t('deck', 'Use modal card view') }}
{{ t('deck', 'Use bigger card view') }}
</label>
</div>

View File

@@ -1,22 +1,17 @@
{
"require-dev": {
"phpunit/phpunit": "~4.6",
"behat/behat": "^3.0",
"guzzlehttp/guzzle": "~5.0",
"phpunit/phpunit": "~6.5",
"behat/behat": "~3.8.0",
"guzzlehttp/guzzle": "6.5.2",
"jarnaiz/behat-junit-formatter": "^1.3",
"sabre/dav": "3.2"
"sabre/dav": "3.2.3",
"symfony/event-dispatcher": "~4.4"
},
"autoload": {
"files": [
"../../../../build/integration/features/bootstrap/Auth.php",
"../../../../build/integration/features/bootstrap/Provisioning.php",
"../../../../build/integration/features/bootstrap/Sharing.php",
"../../../../build/integration/features/bootstrap/WebDav.php",
"../../../../build/integration/features/bootstrap/Trashbin.php"
],
"psr-0": {
"": [
"features/bootstrap/"
"features/bootstrap/",
"../../../../build/integration/features/bootstrap/"
]
}
}

View File

@@ -1,451 +0,0 @@
<?php
/**
*
* @author Christoph Wurst <christoph@owncloud.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Sergio Bertolin <sbertolin@solidgear.es>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Message\ResponseInterface;
require __DIR__ . '/../../vendor/autoload.php';
trait BasicStructure {
use Auth;
/** @var string */
private $currentUser = '';
/** @var string */
private $currentServer = '';
/** @var string */
private $baseUrl = '';
/** @var int */
private $apiVersion = 1;
/** @var ResponseInterface */
private $response = null;
/** @var CookieJar */
private $cookieJar;
/** @var string */
private $requestToken;
public function __construct($baseUrl, $admin, $regular_user_password) {
// Initialize your context here
$this->baseUrl = $baseUrl;
$this->adminUser = $admin;
$this->regularUser = $regular_user_password;
$this->localBaseUrl = $this->baseUrl;
$this->remoteBaseUrl = $this->baseUrl;
$this->currentServer = 'LOCAL';
$this->cookieJar = new CookieJar();
// in case of ci deployment we take the server url from the environment
$testServerUrl = getenv('TEST_SERVER_URL');
if ($testServerUrl !== false) {
$this->baseUrl = $testServerUrl;
$this->localBaseUrl = $testServerUrl;
}
// federated server url from the environment
$testRemoteServerUrl = getenv('TEST_SERVER_FED_URL');
if ($testRemoteServerUrl !== false) {
$this->remoteBaseUrl = $testRemoteServerUrl;
}
}
/**
* @Given /^using api version "(\d+)"$/
* @param string $version
*/
public function usingApiVersion($version) {
$this->apiVersion = (int) $version;
}
/**
* @Given /^As an "([^"]*)"$/
* @param string $user
*/
public function asAn($user) {
$this->currentUser = $user;
}
/**
* @Given /^Using server "(LOCAL|REMOTE)"$/
* @param string $server
* @return string Previous used server
*/
public function usingServer($server) {
$previousServer = $this->currentServer;
if ($server === 'LOCAL') {
$this->baseUrl = $this->localBaseUrl;
$this->currentServer = 'LOCAL';
return $previousServer;
} else {
$this->baseUrl = $this->remoteBaseUrl;
$this->currentServer = 'REMOTE';
return $previousServer;
}
}
/**
* @When /^sending "([^"]*)" to "([^"]*)"$/
* @param string $verb
* @param string $url
*/
public function sendingTo($verb, $url) {
$this->sendingToWith($verb, $url, null);
}
/**
* Parses the xml answer to get ocs response which doesn't match with
* http one in v1 of the api.
* @param ResponseInterface $response
* @return string
*/
public function getOCSResponse($response) {
return $response->xml()->meta[0]->statuscode;
}
/**
* This function is needed to use a vertical fashion in the gherkin tables.
* @param array $arrayOfArrays
* @return array
*/
public function simplifyArray($arrayOfArrays) {
$a = array_map(function ($subArray) {
return $subArray[0];
}, $arrayOfArrays);
return $a;
}
/**
* @When /^sending "([^"]*)" to "([^"]*)" with$/
* @param string $verb
* @param string $url
* @param \Behat\Gherkin\Node\TableNode $body
*/
public function sendingToWith($verb, $url, $body) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php" . $url;
$client = new Client();
$options = [];
if ($this->currentUser === 'admin') {
$options['auth'] = $this->adminUser;
} else {
$options['auth'] = [$this->currentUser, $this->regularUser];
}
$options['headers'] = [
'OCS_APIREQUEST' => 'true'
];
if ($body instanceof \Behat\Gherkin\Node\TableNode) {
$fd = $body->getRowsHash();
$options['body'] = $fd;
}
// TODO: Fix this hack!
if ($verb === 'PUT' && $body === null) {
$options['body'] = [
'foo' => 'bar',
];
}
try {
$this->response = $client->send($client->createRequest($verb, $fullUrl, $options));
} catch (ClientException $ex) {
$this->response = $ex->getResponse();
}
}
/**
* @When /^sending "([^"]*)" with exact url to "([^"]*)"$/
* @param string $verb
* @param string $url
*/
public function sendingToDirectUrl($verb, $url) {
$this->sendingToWithDirectUrl($verb, $url, null);
}
public function sendingToWithDirectUrl($verb, $url, $body) {
$fullUrl = substr($this->baseUrl, 0, -5) . $url;
$client = new Client();
$options = [];
if ($this->currentUser === 'admin') {
$options['auth'] = $this->adminUser;
} else {
$options['auth'] = [$this->currentUser, $this->regularUser];
}
if ($body instanceof \Behat\Gherkin\Node\TableNode) {
$fd = $body->getRowsHash();
$options['body'] = $fd;
}
try {
$this->response = $client->send($client->createRequest($verb, $fullUrl, $options));
} catch (ClientException $ex) {
$this->response = $ex->getResponse();
}
}
public function isExpectedUrl($possibleUrl, $finalPart) {
$baseUrlChopped = substr($this->baseUrl, 0, -4);
$endCharacter = strlen($baseUrlChopped) + strlen($finalPart);
return (substr($possibleUrl,0,$endCharacter) == "$baseUrlChopped" . "$finalPart");
}
/**
* @Then /^the OCS status code should be "([^"]*)"$/
* @param int $statusCode
*/
public function theOCSStatusCodeShouldBe($statusCode) {
PHPUnit_Framework_Assert::assertEquals($statusCode, $this->getOCSResponse($this->response));
}
/**
* @Then /^the HTTP status code should be "([^"]*)"$/
* @param int $statusCode
*/
public function theHTTPStatusCodeShouldBe($statusCode) {
PHPUnit_Framework_Assert::assertEquals($statusCode, $this->response->getStatusCode());
}
/**
* @Then /^the Content-Type should be "([^"]*)"$/
* @param string $contentType
*/
public function theContentTypeShouldbe($contentType) {
PHPUnit_Framework_Assert::assertEquals($contentType, $this->response->getHeader('Content-Type'));
}
/**
* @param ResponseInterface $response
*/
private function extracRequestTokenFromResponse(ResponseInterface $response) {
$this->requestToken = substr(preg_replace('/(.*)data-requesttoken="(.*)">(.*)/sm', '\2', $response->getBody()->getContents()), 0, 89);
}
/**
* @Given Logging in using web as :user
* @param string $user
*/
public function loggingInUsingWebAs($user) {
$loginUrl = substr($this->baseUrl, 0, -5) . '/login';
// Request a new session and extract CSRF token
$client = new Client();
$response = $client->get(
$loginUrl,
[
'cookies' => $this->cookieJar,
]
);
$this->extracRequestTokenFromResponse($response);
// Login and extract new token
$password = ($user === 'admin') ? 'admin' : '123456';
$client = new Client();
$response = $client->post(
$loginUrl,
[
'body' => [
'user' => $user,
'password' => $password,
'requesttoken' => $this->requestToken,
],
'cookies' => $this->cookieJar,
]
);
$this->extracRequestTokenFromResponse($response);
}
/**
* @When Sending a :method to :url with requesttoken
* @param string $method
* @param string $url
*/
public function sendingAToWithRequesttoken($method, $url) {
$baseUrl = substr($this->baseUrl, 0, -5);
$client = new Client();
$request = $client->createRequest(
$method,
$baseUrl . $url,
[
'cookies' => $this->cookieJar,
]
);
$request->addHeader('requesttoken', $this->requestToken);
try {
$this->response = $client->send($request);
} catch (ClientException $e) {
$this->response = $e->getResponse();
}
}
/**
* @When Sending a :method to :url without requesttoken
* @param string $method
* @param string $url
*/
public function sendingAToWithoutRequesttoken($method, $url) {
$baseUrl = substr($this->baseUrl, 0, -5);
$client = new Client();
$request = $client->createRequest(
$method,
$baseUrl . $url,
[
'cookies' => $this->cookieJar,
]
);
try {
$this->response = $client->send($request);
} catch (ClientException $e) {
$this->response = $e->getResponse();
}
}
public static function removeFile($path, $filename) {
if (file_exists("$path" . "$filename")) {
unlink("$path" . "$filename");
}
}
/**
* @Given User :user modifies text of :filename with text :text
* @param string $user
* @param string $filename
* @param string $text
*/
public function modifyTextOfFile($user, $filename, $text) {
self::removeFile("../../data/$user/files", "$filename");
file_put_contents("../../data/$user/files" . "$filename", "$text");
}
public function createFileSpecificSize($name, $size) {
$file = fopen("work/" . "$name", 'w');
fseek($file, $size - 1 ,SEEK_CUR);
fwrite($file,'a'); // write a dummy char at SIZE position
fclose($file);
}
public function createFileWithText($name, $text) {
$file = fopen("work/" . "$name", 'w');
fwrite($file, $text);
fclose($file);
}
/**
* @Given file :filename of size :size is created in local storage
* @param string $filename
* @param string $size
*/
public function fileIsCreatedInLocalStorageWithSize($filename, $size) {
$this->createFileSpecificSize("local_storage/$filename", $size);
}
/**
* @Given file :filename with text :text is created in local storage
* @param string $filename
* @param string $text
*/
public function fileIsCreatedInLocalStorageWithText($filename, $text) {
$this->createFileWithText("local_storage/$filename", $text);
}
/**
* @When Sleep for :seconds seconds
* @param int $seconds
*/
public function sleepForSeconds($seconds) {
sleep((int)$seconds);
}
/**
* @BeforeSuite
*/
public static function addFilesToSkeleton() {
for ($i = 0; $i < 5; $i++) {
file_put_contents("../../core/skeleton/" . "textfile" . "$i" . ".txt", "Nextcloud test text file\n");
}
if (!file_exists("../../core/skeleton/FOLDER")) {
mkdir("../../core/skeleton/FOLDER", 0777, true);
}
if (!file_exists("../../core/skeleton/PARENT")) {
mkdir("../../core/skeleton/PARENT", 0777, true);
}
file_put_contents("../../core/skeleton/PARENT/" . "parent.txt", "Nextcloud test text file\n");
if (!file_exists("../../core/skeleton/PARENT/CHILD")) {
mkdir("../../core/skeleton/PARENT/CHILD", 0777, true);
}
file_put_contents("../../core/skeleton/PARENT/CHILD/" . "child.txt", "Nextcloud test text file\n");
}
/**
* @AfterSuite
*/
public static function removeFilesFromSkeleton() {
for ($i = 0; $i < 5; $i++) {
self::removeFile("../../core/skeleton/", "textfile" . "$i" . ".txt");
}
if (is_dir("../../core/skeleton/FOLDER")) {
rmdir("../../core/skeleton/FOLDER");
}
self::removeFile("../../core/skeleton/PARENT/CHILD/", "child.txt");
if (is_dir("../../core/skeleton/PARENT/CHILD")) {
rmdir("../../core/skeleton/PARENT/CHILD");
}
self::removeFile("../../core/skeleton/PARENT/", "parent.txt");
if (is_dir("../../core/skeleton/PARENT")) {
rmdir("../../core/skeleton/PARENT");
}
}
/**
* @BeforeScenario @local_storage
*/
public static function removeFilesFromLocalStorageBefore() {
$dir = "./work/local_storage/";
$di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
$ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($ri as $file) {
$file->isDir() ? rmdir($file) : unlink($file);
}
}
/**
* @AfterScenario @local_storage
*/
public static function removeFilesFromLocalStorageAfter() {
$dir = "./work/local_storage/";
$di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
$ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($ri as $file) {
$file->isDir() ? rmdir($file) : unlink($file);
}
}
}

View File

@@ -28,23 +28,7 @@ class FeatureContext implements Context {
* @When Sending a :method to :url with JSON
*/
public function sendingAToWithJSON($method, $url, \Behat\Gherkin\Node\PyStringNode $data) {
$baseUrl = substr($this->baseUrl, 0, -5);
$client = new Client;
$request = $client->createRequest(
$method,
$baseUrl . $url,
[
'cookies' => $this->cookieJar,
'json' => json_decode($data)
]
);
$request->addHeader('requesttoken', $this->requestToken);
try {
$this->response = $client->send($request);
} catch (ClientException $e) {
$this->response = $e->getResponse();
}
$this->sendJSONrequest($method, $url, json_decode($data));
}
@@ -149,17 +133,18 @@ class FeatureContext implements Context {
$baseUrl = substr($this->baseUrl, 0, -5);
$client = new Client;
$request = $client->createRequest(
$method,
$baseUrl . $url,
[
'cookies' => $this->cookieJar,
'json' => $data
]
);
$request->addHeader('requesttoken', $this->requestToken);
try {
$this->response = $client->send($request);
$this->response = $client->request(
$method,
$baseUrl . $url,
[
'cookies' => $this->cookieJar,
'json' => $data,
'headers' => [
'requesttoken' => $this->requestToken
]
]
);
} catch (ClientException $e) {
$this->response = $e->getResponse();
}

View File

@@ -19,6 +19,13 @@ const config = {
'node_modules',
],
},
stats: {
context: path.resolve(__dirname, 'src'),
assets: true,
entrypoints: true,
chunks: true,
modules: true
}
}
module.exports = merge(webpackConfig, config)