Compare commits
133 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0a8a00131 | ||
|
|
b686d446b1 | ||
|
|
8e60717cca | ||
|
|
b8b4d708f3 | ||
|
|
5bbd88be0b | ||
|
|
60e18f3556 | ||
|
|
4aef4db2c9 | ||
|
|
bb1022ac62 | ||
|
|
d36cc8e8b3 | ||
|
|
2221ed53b0 | ||
|
|
ae3a67bd43 | ||
|
|
c640b31384 | ||
|
|
546a3008b5 | ||
|
|
c25bd37971 | ||
|
|
36028cdfeb | ||
|
|
85ea22ff0c | ||
|
|
8cf08ddd08 | ||
|
|
5668abacec | ||
|
|
5ffdc3ec60 | ||
|
|
7d72a67c4c | ||
|
|
bab74ced75 | ||
|
|
f6a58bb8b7 | ||
|
|
5dacdf070e | ||
|
|
890291ccaa | ||
|
|
89771438f8 | ||
|
|
508bf95378 | ||
|
|
c15160dad1 | ||
|
|
7590f1d625 | ||
|
|
c7f09cbf82 | ||
|
|
4e14dfb15f | ||
|
|
84fe5bd9ef | ||
|
|
8cebc35573 | ||
|
|
37b88d0afc | ||
|
|
647bcd3167 | ||
|
|
30629aaacc | ||
|
|
0fbda02b80 | ||
|
|
4763a0621c | ||
|
|
fc7eda5706 | ||
|
|
2fdf50620e | ||
|
|
f20a91553f | ||
|
|
8283aa67f0 | ||
|
|
ababa900bb | ||
|
|
e147b43eb9 | ||
|
|
65675cdbde | ||
|
|
d32e942908 | ||
|
|
32a65e856c | ||
|
|
01522e69ea | ||
|
|
49d356f04f | ||
|
|
04e5310643 | ||
|
|
a293467b59 | ||
|
|
c2546206a3 | ||
|
|
9c406a34c5 | ||
|
|
360b4f57f5 | ||
|
|
460d06fe10 | ||
|
|
b3b2ee1966 | ||
|
|
7092dc06ab | ||
|
|
c47829a3d9 | ||
|
|
54479eee20 | ||
|
|
cdd788a6b8 | ||
|
|
f5242cd10c | ||
|
|
f0299486d6 | ||
|
|
38921cade8 | ||
|
|
8a3e679c33 | ||
|
|
d1b81e697f | ||
|
|
215fcf61bc | ||
|
|
e418373503 | ||
|
|
ca22b0ad2c | ||
|
|
e4cbc694d4 | ||
|
|
f53e51fc4e | ||
|
|
dcbbb22dda | ||
|
|
e85042e1b4 | ||
|
|
a720669354 | ||
|
|
216b9445d3 | ||
|
|
b21faa8501 | ||
|
|
1bc28c68a5 | ||
|
|
f78f8bfd7f | ||
|
|
01bddf029e | ||
|
|
bdead3cdd5 | ||
|
|
88d164b411 | ||
|
|
1638c3d350 | ||
|
|
454d515192 | ||
|
|
e60219c9df | ||
|
|
5c8c73f2ac | ||
|
|
fad63ac6f5 | ||
|
|
31eb8d6698 | ||
|
|
40967a4ee6 | ||
|
|
bfe9b05d69 | ||
|
|
82e3400162 | ||
|
|
a886b4ee78 | ||
|
|
618fb50618 | ||
|
|
f7aae7912d | ||
|
|
2976604b7b | ||
|
|
bbe482586b | ||
|
|
ff61238487 | ||
|
|
9e2dcb686f | ||
|
|
fcc96ca98d | ||
|
|
a43cee8a5d | ||
|
|
f4ccc506af | ||
|
|
fee49f3699 | ||
|
|
d43c7a48cc | ||
|
|
c0fad295b5 | ||
|
|
cb1314f067 | ||
|
|
ba68e4c2f7 | ||
|
|
bd8fd6a66b | ||
|
|
0eba8d0840 | ||
|
|
8fc95dc40d | ||
|
|
ecd3e25588 | ||
|
|
914f912612 | ||
|
|
e68f723095 | ||
|
|
5f71be2e7f | ||
|
|
bc2a72f035 | ||
|
|
cf4be82827 | ||
|
|
23580705aa | ||
|
|
65c8c394a8 | ||
|
|
422788a6a3 | ||
|
|
2d5e29de5d | ||
|
|
2a307b92a7 | ||
|
|
2d8dbc70ad | ||
|
|
cfee259b38 | ||
|
|
f94cdb3ebb | ||
|
|
1ed50fdca6 | ||
|
|
56e460004f | ||
|
|
a95f78d188 | ||
|
|
df09a9a7b2 | ||
|
|
990ee2aef9 | ||
|
|
486ecd12db | ||
|
|
c9cdd7bb11 | ||
|
|
2c753fd084 | ||
|
|
79d2d2f3f5 | ||
|
|
24d9b55bfc | ||
|
|
28cd9fcf77 | ||
|
|
d8a36f0602 | ||
|
|
de06033dcd |
10
.eslintrc.js
10
.eslintrc.js
@@ -1,12 +1,8 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'@nextcloud',
|
||||
'@nextcloud'
|
||||
],
|
||||
rules: {
|
||||
'jsdoc/require-param-description': ['off'],
|
||||
'jsdoc/require-param-type': ['off'],
|
||||
'jsdoc/check-param-names': ['off'],
|
||||
'jsdoc/no-undefined-types': ['off'],
|
||||
'jsdoc/require-property-description' : ['off']
|
||||
},
|
||||
'valid-jsdoc': ['off'],
|
||||
}
|
||||
}
|
||||
|
||||
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -39,13 +39,3 @@ updates:
|
||||
versions:
|
||||
- "< 16"
|
||||
- ">= 15.a"
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: saturday
|
||||
time: "03:00"
|
||||
timezone: Europe/Paris
|
||||
open-pull-requests-limit: 10
|
||||
reviewers:
|
||||
- juliushaertl
|
||||
|
||||
25
.github/stale.yml
vendored
Normal file
25
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- "1. to develop"
|
||||
- "2. developing"
|
||||
- "3. to review"
|
||||
- "discussion"
|
||||
- "bounty"
|
||||
- "bug"
|
||||
- "enhancement"
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 30
|
||||
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
10
.github/workflows/appbuild.yml
vendored
10
.github/workflows/appbuild.yml
vendored
@@ -9,18 +9,16 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-version: [12.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2.4.1
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@v1
|
||||
with:
|
||||
php-version: '7.4'
|
||||
tools: composer
|
||||
|
||||
46
.github/workflows/command-rebase.yml
vendored
46
.github/workflows/command-rebase.yml
vendored
@@ -1,46 +0,0 @@
|
||||
# This workflow is provided via the organization template repository
|
||||
#
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
|
||||
name: Rebase command
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: created
|
||||
|
||||
jobs:
|
||||
rebase:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# On pull requests and if the comment starts with `/rebase`
|
||||
if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/rebase')
|
||||
|
||||
steps:
|
||||
- name: Add reaction on start
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
repository: ${{ github.event.repository.full_name }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reaction-type: "+1"
|
||||
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v2.4.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
|
||||
- name: Add reaction on failure
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
if: failure()
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
repository: ${{ github.event.repository.full_name }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reaction-type: "-1"
|
||||
29
.github/workflows/dependabot-approve-merge.yml
vendored
29
.github/workflows/dependabot-approve-merge.yml
vendored
@@ -1,29 +0,0 @@
|
||||
# This workflow is provided via the organization template repository
|
||||
#
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
|
||||
name: Dependabot
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- master
|
||||
- stable*
|
||||
|
||||
jobs:
|
||||
auto-approve-merge:
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Github actions bot approve
|
||||
- uses: hmarr/auto-approve-action@v2
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Nextcloud bot approve and merge request
|
||||
- uses: ahmadnassri/action-dependabot-auto-merge@v2
|
||||
with:
|
||||
target: patch
|
||||
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}
|
||||
20
.github/workflows/fixup.yml
vendored
20
.github/workflows/fixup.yml
vendored
@@ -1,20 +0,0 @@
|
||||
# This workflow is provided via the organization template repository
|
||||
#
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
|
||||
name: Pull request checks
|
||||
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
commit-message-check:
|
||||
name: Block fixup and squash commits
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Run check
|
||||
uses: xt0rted/block-autosquash-commits-action@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
8
.github/workflows/integration.yml
vendored
8
.github/workflows/integration.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
matrix:
|
||||
php-versions: ['7.4']
|
||||
databases: ['sqlite', 'mysql', 'pgsql']
|
||||
server-versions: ['stable23']
|
||||
server-versions: ['stable21']
|
||||
|
||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: nextcloud/server
|
||||
ref: ${{ matrix.server-versions }}
|
||||
@@ -56,12 +56,12 @@ jobs:
|
||||
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
|
||||
|
||||
- name: Checkout app
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpunit
|
||||
|
||||
33
.github/workflows/lint.yml
vendored
33
.github/workflows/lint.yml
vendored
@@ -13,13 +13,13 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.3', '7.4']
|
||||
php-versions: ['7.2', '7.3', '7.4']
|
||||
|
||||
name: php${{ matrix.php-versions }} lint
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up php${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@v1
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: none
|
||||
@@ -31,9 +31,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@master
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@master
|
||||
with:
|
||||
php-version: 7.4
|
||||
coverage: none
|
||||
@@ -47,16 +47,14 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-version: [12.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use node ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2.4.1
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: ESLint
|
||||
@@ -67,19 +65,16 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-versions: [12.x]
|
||||
|
||||
name: stylelint node${{ matrix.node-version }}
|
||||
name: stylelint node${{ matrix.node-versions }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up node ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2.4.1
|
||||
- name: Set up node ${{ matrix.node-versions }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
node-versions: ${{ matrix.node-versions }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
10
.github/workflows/nightly.yml
vendored
10
.github/workflows/nightly.yml
vendored
@@ -14,18 +14,16 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-version: [12.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2.4.1
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@v1
|
||||
with:
|
||||
php-version: '7.4'
|
||||
tools: composer
|
||||
|
||||
8
.github/workflows/nodejs.yml
vendored
8
.github/workflows/nodejs.yml
vendored
@@ -9,16 +9,14 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-version: [12.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2.4.1
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
- name: install dependencies
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
8
.github/workflows/phpunit.yml
vendored
8
.github/workflows/phpunit.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
matrix:
|
||||
php-versions: ['7.3', '7.4']
|
||||
databases: ['sqlite', 'mysql', 'pgsql']
|
||||
server-versions: ['stable23']
|
||||
server-versions: ['stable21']
|
||||
|
||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: nextcloud/server
|
||||
ref: ${{ matrix.server-versions }}
|
||||
@@ -57,12 +57,12 @@ jobs:
|
||||
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
|
||||
|
||||
- name: Checkout app
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpunit
|
||||
|
||||
6
.github/workflows/static-analysis.yml
vendored
6
.github/workflows/static-analysis.yml
vendored
@@ -12,13 +12,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
ocp-version: [ 'dev-stable23' ]
|
||||
ocp-version: [ 'dev-stable21' ]
|
||||
name: Nextcloud ${{ matrix.ocp-version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@master
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@master
|
||||
with:
|
||||
php-version: 7.4
|
||||
tools: composer:v1
|
||||
|
||||
126
CHANGELOG.md
126
CHANGELOG.md
@@ -1,81 +1,79 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 1.6.2
|
||||
|
||||
### Added
|
||||
|
||||
- Transfer ownership @juliushaertl [#3664](https://github.com/nextcloud/deck/pull/3664)
|
||||
## 1.4.8
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛 Fix missing files sidebar [#3641](https://github.com/nextcloud/deck/pull/3641)
|
||||
- Add a missing translation - not found in transifex [#3706](https://github.com/nextcloud/deck/pull/3706)
|
||||
- Fix: Check all circle shares for permissions [#3717](https://github.com/nextcloud/deck/pull/3717)
|
||||
- Sort boards non case sensitive [#3663](https://github.com/nextcloud/deck/pull/3663)
|
||||
- Use explicit cast to make use of index @juliushaertl [#3497](https://github.com/nextcloud/deck/pull/3497)
|
||||
- Fix paramter replacements when creating deck cards from talk messages @juliushaertl [#3741](https://github.com/nextcloud/deck/pull/3741)
|
||||
- Fix hidden attachment icon on archived cards [#3734](https://github.com/nextcloud/deck/pull/3734)
|
||||
- Fix text selection in dark mode and modal view [#3766](https://github.com/nextcloud/deck/pull/3766)
|
||||
- Fix cursor generation if no results are found [#3461](https://api.github.com/repos/nextcloud/deck/pulls/3461)
|
||||
- Allow to download an attachment without navigating to the files app [#3442](https://api.github.com/repos/nextcloud/deck/pulls/3442)
|
||||
- Exclude deleted boards in the selection for target [#3525](https://api.github.com/repos/nextcloud/deck/pulls/3525)
|
||||
- Make insert attachment buttom easy to click [#3616](https://api.github.com/repos/nextcloud/deck/pulls/3616)
|
||||
- Fix confusion between stackId and boardId in StackService [#3545](https://api.github.com/repos/nextcloud/deck/pulls/3545)
|
||||
|
||||
### Other
|
||||
|
||||
- Properly check for the stack AND setting board permissions [#3713](https://github.com/nextcloud/deck/pull/3713)
|
||||
- Add missing indices [#3755](https://github.com/nextcloud/deck/pull/3755)
|
||||
|
||||
## 1.6.1
|
||||
## 1.4.7
|
||||
|
||||
### Fixed
|
||||
|
||||
- Exclude deleted boards in the selection for target [#3523](https://api.github.com/repos/nextcloud/deck/pulls/3523)
|
||||
- CardApiController: Fix order of optional parameters [#3520](https://api.github.com/repos/nextcloud/deck/pulls/3520)
|
||||
- Fix cursor generation if no results are found [#3462](https://api.github.com/repos/nextcloud/deck/pulls/3462)
|
||||
- Fix CalDAV blocking and modernize circles API usage [#3526](https://api.github.com/repos/nextcloud/deck/pulls/3526)
|
||||
- Fix overview card listing [#3463](https://api.github.com/repos/nextcloud/deck/pulls/3463)
|
||||
- Generate fixed link for activity emails [#3626](https://api.github.com/repos/nextcloud/deck/pulls/3626)
|
||||
- return the selector for collections [#3618](https://api.github.com/repos/nextcloud/deck/pulls/3618)
|
||||
- Fix confusion between stackId and boardId in StackService [#3543](https://api.github.com/repos/nextcloud/deck/pulls/3543)
|
||||
- Fix talk integration [#3537](https://api.github.com/repos/nextcloud/deck/pulls/3537)
|
||||
- Make insert attachment buttom easy to click [#3614](https://api.github.com/repos/nextcloud/deck/pulls/3614)
|
||||
- Fix release asset build
|
||||
|
||||
## 1.6.0
|
||||
|
||||
### Added
|
||||
|
||||
- #3449 Cache most frequent queries
|
||||
- #3177 Use async import for vue component on collections entrypoint @juliushaertl
|
||||
- #2791 Open description links in new tab @fm-sys
|
||||
- #3344 Improve combined search @eneiluj
|
||||
- #3362 Improve search performance @eneiluj
|
||||
- #2710 Due date shortcuts in the datepicker @jakobroehrl
|
||||
## 1.4.6
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3446 Switch to QBMapper in BoardMapper
|
||||
- #3433 Fix event name for updating the description
|
||||
- #3463 Fix overview card listing
|
||||
- #3440 Allow to download an attachment without navigating to the files app
|
||||
- #3462 Fix cursor generation if no results are found
|
||||
- #3161 Reduce duplicate queries when fetching user boards an permissions @juliushaertl
|
||||
- #3151 Always log generic exceptions @juliushaertl
|
||||
- #3217 Move circle checks to a unified service and improve member checks @juliushaertl
|
||||
- #3225 Check for null value to avoid TypeError in the group manager @juliushaertl
|
||||
- #3263 Defer obtaining the user session in the config service @juliushaertl
|
||||
- #3294 Fix print style issues @weeman1337
|
||||
- #3299 Return false instead of throwing when getting calendar setting @juliushaertl
|
||||
- #3298 Delete file shares through attachments API @juliushaertl
|
||||
- #3343 Fix search pagination cursor @eneiluj
|
||||
- #3326 add autofocus on board edit @weeman1337
|
||||
- #3323 Extend drag-and-drop zone in card sidebar @old-green-frog
|
||||
- #3364 Fix optional parameter order @juliushaertl
|
||||
- #3324 Fix menu button position in card modal @valerydmitrieva
|
||||
- #3391 Use displayname instead of uid for mentions (reopened against master) @kffl
|
||||
- #3316 Additional check for stacks @juliushaertl
|
||||
- #3357 Revert "Fix search pagination cursor" @juliushaertl
|
||||
- #3327 Do not show both bullets and checkboxes for checklists @Themanwhosmellslikesugar
|
||||
- #3375 Show absolute dates when printing @weeman1337
|
||||
- #3376 Print assignee names @weeman1337
|
||||
- #3384 Keep exceptions http response generic @juliushaertl
|
||||
- #3379 Fix menu button position in card modal
|
||||
- #3360 Improve combined search @eneiluj
|
||||
- #3367 Fix optional parameter order
|
||||
- #3393 Use displayname instead of uid for mentions
|
||||
- #3359 Rich object string parameters for notifications @juliushaertl
|
||||
- #3385 Extend drag-and-drop zone in card sidebar @Artem4590
|
||||
- #3408 Keep exceptions http response generic
|
||||
|
||||
|
||||
## 1.4.5
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3318 Additional check for stacks
|
||||
|
||||
|
||||
## 1.4.4
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3301 Fix print style issues
|
||||
- #3307 Return false instead of throwing when getting calendar setting
|
||||
- #3227 Additional circle level check
|
||||
- #3304 Delete file shares through attachments API
|
||||
|
||||
## 1.4.3 - 2021-07-09
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#3143](https://github.com/nextcloud/deck/pull/3143) Always pass user id in share provider
|
||||
* [#3153](https://github.com/nextcloud/deck/pull/3153) Only offer stack creation in emptycontent with proper permissions
|
||||
* [#3164](https://github.com/nextcloud/deck/pull/3164) Always log generic exceptions
|
||||
* [#3169](https://github.com/nextcloud/deck/pull/3169) Reduce duplicate queries when fetching user boards an permissions
|
||||
|
||||
|
||||
## 1.4.2 - 2021-05-03
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#3030](https://github.com/nextcloud/deck/pull/3030) Proper error handling when fetching comments fails
|
||||
* [#3031](https://github.com/nextcloud/deck/pull/3031) Allow searching for filters without a query to match all that have a given filter set
|
||||
* [#3039](https://github.com/nextcloud/deck/pull/3039) Catch any error during circle detail fetching
|
||||
* [#3040](https://github.com/nextcloud/deck/pull/3040) Get attachment from the user node instead of the share source
|
||||
|
||||
## 1.4.1 - 2021-04-20
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#2984](https://github.com/nextcloud/deck/pull/2984) Fix codemirror description width
|
||||
* [#2990](https://github.com/nextcloud/deck/pull/2990) Fix unified comments search with postgres
|
||||
* [#2994](https://github.com/nextcloud/deck/pull/2994) Remove notification on unshare and add type hints
|
||||
* [#3006](https://github.com/nextcloud/deck/pull/3006) Only import debounce
|
||||
* [#3008](https://github.com/nextcloud/deck/pull/3008) Do not query the lookupserver when looking for sharees
|
||||
|
||||
|
||||
## 1.4.0 - 2021-04-13
|
||||
|
||||
3
Makefile
3
Makefile
@@ -50,7 +50,8 @@ ifeq (, $(shell which phpunit 2> /dev/null))
|
||||
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
|
||||
phpunit -c tests/phpunit.integration.xml --testsuite=integration-database --coverage-clover build/php-integration.coverage.xml
|
||||
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
|
||||
endif
|
||||
|
||||
test-integration:
|
||||
|
||||
10
README.md
10
README.md
@@ -15,18 +15,12 @@ Deck is a kanban style organization tool aimed at personal planning and project
|
||||
- Keep track of changes in the activity stream
|
||||
- Get your project organized
|
||||
|
||||

|
||||
|
||||
### Mobile apps
|
||||
|
||||
- [Nextcloud Deck app for Android](https://github.com/stefan-niedermann/nextcloud-deck) - It is available in [F-Droid](https://f-droid.org/de/packages/it.niedermann.nextcloud.deck/) and the [Google Play Store](https://play.google.com/store/apps/details?id=it.niedermann.nextcloud.deck.play)
|
||||
- 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)
|
||||
|
||||
### 3rd-Party Integrations
|
||||

|
||||
|
||||
- [trello-to-deck](https://github.com/maxammann/trello-to-deck) - Migrates cards from Trello
|
||||
- [mail2deck](https://github.com/newroco/mail2deck) - Provides an "email in" solution
|
||||
- [A-deck](https://github.com/leoossa/A-deck) - Chrome Extension that allows to create new card in selected stack based on current tab
|
||||
-
|
||||
## Installation/Update
|
||||
|
||||
This app is supposed to work on the two latest Nextcloud versions.
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
- 🚀 Get your project organized
|
||||
|
||||
</description>
|
||||
<version>1.6.2</version>
|
||||
<version>1.4.8</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Julius Härtl</author>
|
||||
<namespace>Deck</namespace>
|
||||
@@ -35,7 +35,7 @@
|
||||
<database min-version="9.4">pgsql</database>
|
||||
<database>sqlite</database>
|
||||
<database min-version="5.5">mysql</database>
|
||||
<nextcloud min-version="23" max-version="23"/>
|
||||
<nextcloud min-version="21" max-version="21"/>
|
||||
</dependencies>
|
||||
<background-jobs>
|
||||
<job>OCA\Deck\Cron\DeleteCron</job>
|
||||
@@ -44,7 +44,6 @@
|
||||
</background-jobs>
|
||||
<commands>
|
||||
<command>OCA\Deck\Command\UserExport</command>
|
||||
<command>OCA\Deck\Command\TransferOwnership</command>
|
||||
</commands>
|
||||
<activity>
|
||||
<settings>
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
return [
|
||||
'routes' => [
|
||||
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
|
||||
['name' => 'page#redirectToCard', 'url' => '/card/{cardId}', 'verb' => 'GET'],
|
||||
|
||||
// boards
|
||||
['name' => 'board#index', 'url' => '/boards', 'verb' => 'GET'],
|
||||
@@ -39,7 +38,6 @@ return [
|
||||
['name' => 'board#updateAcl', 'url' => '/boards/{boardId}/acl/{aclId}', 'verb' => 'PUT'],
|
||||
['name' => 'board#deleteAcl', 'url' => '/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
|
||||
['name' => 'board#clone', 'url' => '/boards/{boardId}/clone', 'verb' => 'POST'],
|
||||
['name' => 'board#transferOwner', 'url' => '/boards/{boardId}/transferOwner', 'verb' => 'PUT'],
|
||||
|
||||
// stacks
|
||||
['name' => 'stack#index', 'url' => '/stacks/{boardId}', 'verb' => 'GET'],
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
const babelConfig = require('@nextcloud/babel-config')
|
||||
|
||||
module.exports = babelConfig
|
||||
module.exports = {
|
||||
plugins: ['@babel/plugin-syntax-dynamic-import'],
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"roave/security-advisories": "dev-master",
|
||||
"christophwurst/nextcloud": "^22@dev",
|
||||
"christophwurst/nextcloud": "^21@dev",
|
||||
"phpunit/phpunit": "^8",
|
||||
"nextcloud/coding-standard": "^0.5.0",
|
||||
"symfony/event-dispatcher": "^4.0",
|
||||
@@ -21,9 +21,6 @@
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2"
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.3"
|
||||
},
|
||||
"optimize-autoloader": true,
|
||||
"classmap-authoritative": true
|
||||
},
|
||||
|
||||
1321
composer.lock
generated
1321
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,6 @@ Overall, Deck is easy to use. You can create boards, add users, share the Deck,
|
||||
3. [Handle cards options](#3-handle-cards-options)
|
||||
4. [Archive old tasks](#4-archive-old-tasks)
|
||||
5. [Manage your board](#5-manage-your-board)
|
||||
6. [New owner for the deck entities](#8-new-owner-for-the-deck-entities)
|
||||
|
||||
### 1. Create my first board
|
||||
In this example, we're going to create a board and share it with an other nextcloud user.
|
||||
@@ -91,22 +90,4 @@ For example the search `project tag:ToDo assigned:alice assigned:bob` will retur
|
||||
|
||||
Other text tokens will be used to perform a case-insensitive search on the card title and description
|
||||
|
||||
In addition, quotes can be used to pass a query with spaces, e.g. `"Exact match with spaces"` or `title:"My card"`.
|
||||
|
||||
### 8. New owner for the deck entities
|
||||
You can transfer ownership of boards, cards, etc to a new user, using `occ` command `deck:transfer-ownership`
|
||||
|
||||
```bash
|
||||
php occ deck:transfer-ownership previousOwner newOwner
|
||||
```
|
||||
|
||||
The transfer will preserve card details linked to the old owner, which can also be remapped by using the `--remap` option on the occ command.
|
||||
```bash
|
||||
php occ deck:transfer-ownership --remap previousOwner newOwner
|
||||
```
|
||||
|
||||
Individual boards can be transferred by adding the id of the board to the command:
|
||||
|
||||
```bash
|
||||
php occ deck:transfer-ownership previousOwner newOwner 123
|
||||
```
|
||||
In addition wuotes can be used to pass a query with spaces, e.g. `"Exact match with spaces"` or `title:"My card"`.
|
||||
|
||||
@@ -173,8 +173,11 @@ OC.L10N.register(
|
||||
"Owner" : "Besitzer",
|
||||
"Delete" : "Löschen",
|
||||
"Failed to create share with {displayName}" : "Fehler beim Erstellen der Freigabe mit dem Namen {displayName}",
|
||||
"Are you sure you want to transfer the board {title} to {user}?" : "Möchtest Du wirklich das Board {title} an {user} übertragen?",
|
||||
"Transfer the board." : "Board übertragen",
|
||||
"Transfer" : "Übertragen",
|
||||
"The board has been transferred to {user}" : "Das Board wurde an {user} übertragen",
|
||||
"Failed to transfer the board to {user}" : "Das Board konnte nicht an {user} übertragen werden",
|
||||
"Add a new list" : "Eine neue Liste hinzufügen",
|
||||
"Archive all cards" : "Alle Karten archivieren",
|
||||
"Delete list" : "Liste löschen",
|
||||
|
||||
@@ -171,8 +171,11 @@
|
||||
"Owner" : "Besitzer",
|
||||
"Delete" : "Löschen",
|
||||
"Failed to create share with {displayName}" : "Fehler beim Erstellen der Freigabe mit dem Namen {displayName}",
|
||||
"Are you sure you want to transfer the board {title} to {user}?" : "Möchtest Du wirklich das Board {title} an {user} übertragen?",
|
||||
"Transfer the board." : "Board übertragen",
|
||||
"Transfer" : "Übertragen",
|
||||
"The board has been transferred to {user}" : "Das Board wurde an {user} übertragen",
|
||||
"Failed to transfer the board to {user}" : "Das Board konnte nicht an {user} übertragen werden",
|
||||
"Add a new list" : "Eine neue Liste hinzufügen",
|
||||
"Archive all cards" : "Alle Karten archivieren",
|
||||
"Delete list" : "Liste löschen",
|
||||
|
||||
@@ -173,8 +173,11 @@ OC.L10N.register(
|
||||
"Owner" : "Besitzer",
|
||||
"Delete" : "Löschen",
|
||||
"Failed to create share with {displayName}" : "Fehler beim Erstellen der Freigabe mit dem Namen {displayName}",
|
||||
"Are you sure you want to transfer the board {title} to {user}?" : "Möchten Sie wirklich das Board {title} auf {user} übertragen?",
|
||||
"Transfer the board." : "Board übertragen.",
|
||||
"Transfer" : "Übertragen",
|
||||
"The board has been transferred to {user}" : "Das Board wurde auf {user} übertragen",
|
||||
"Failed to transfer the board to {user}" : "Das Board konnte nicht auf {user} übertragen werden",
|
||||
"Add a new list" : "Eine neue Liste hinzufügen",
|
||||
"Archive all cards" : "Alle Karten archivieren",
|
||||
"Delete list" : "Liste löschen",
|
||||
|
||||
@@ -171,8 +171,11 @@
|
||||
"Owner" : "Besitzer",
|
||||
"Delete" : "Löschen",
|
||||
"Failed to create share with {displayName}" : "Fehler beim Erstellen der Freigabe mit dem Namen {displayName}",
|
||||
"Are you sure you want to transfer the board {title} to {user}?" : "Möchten Sie wirklich das Board {title} auf {user} übertragen?",
|
||||
"Transfer the board." : "Board übertragen.",
|
||||
"Transfer" : "Übertragen",
|
||||
"The board has been transferred to {user}" : "Das Board wurde auf {user} übertragen",
|
||||
"Failed to transfer the board to {user}" : "Das Board konnte nicht auf {user} übertragen werden",
|
||||
"Add a new list" : "Eine neue Liste hinzufügen",
|
||||
"Archive all cards" : "Alle Karten archivieren",
|
||||
"Delete list" : "Liste löschen",
|
||||
|
||||
@@ -173,8 +173,11 @@ OC.L10N.register(
|
||||
"Owner" : "Właściciel",
|
||||
"Delete" : "Usuń",
|
||||
"Failed to create share with {displayName}" : "Nie udało się utworzyć udostępnienia dla {displayName}",
|
||||
"Are you sure you want to transfer the board {title} to {user}?" : "Czy na pewno chcesz przenieść tablicę {title} do {user}?",
|
||||
"Transfer the board." : "Przeniesienie tablicy.",
|
||||
"Transfer" : "Przenieś",
|
||||
"The board has been transferred to {user}" : "Tablica została przeniesiona do {user}",
|
||||
"Failed to transfer the board to {user}" : "Nie udało się przenieść tablicy do {user}",
|
||||
"Add a new list" : "Dodaj nową listę",
|
||||
"Archive all cards" : "Zarchiwizuj wszystkie karty",
|
||||
"Delete list" : "Usuń listę",
|
||||
|
||||
@@ -171,8 +171,11 @@
|
||||
"Owner" : "Właściciel",
|
||||
"Delete" : "Usuń",
|
||||
"Failed to create share with {displayName}" : "Nie udało się utworzyć udostępnienia dla {displayName}",
|
||||
"Are you sure you want to transfer the board {title} to {user}?" : "Czy na pewno chcesz przenieść tablicę {title} do {user}?",
|
||||
"Transfer the board." : "Przeniesienie tablicy.",
|
||||
"Transfer" : "Przenieś",
|
||||
"The board has been transferred to {user}" : "Tablica została przeniesiona do {user}",
|
||||
"Failed to transfer the board to {user}" : "Nie udało się przenieść tablicy do {user}",
|
||||
"Add a new list" : "Dodaj nową listę",
|
||||
"Archive all cards" : "Zarchiwizuj wszystkie karty",
|
||||
"Delete list" : "Usuń listę",
|
||||
|
||||
@@ -173,8 +173,11 @@ OC.L10N.register(
|
||||
"Owner" : "Sahibi",
|
||||
"Delete" : "Sil",
|
||||
"Failed to create share with {displayName}" : "{displayName} ile paylaşılamadı",
|
||||
"Are you sure you want to transfer the board {title} to {user}?" : "{title} panosunu {user} kullanıcısına aktarmak istediğinize emin misiniz?",
|
||||
"Transfer the board." : "Panoyu aktar.",
|
||||
"Transfer" : "Aktar",
|
||||
"The board has been transferred to {user}" : "Pano {user} kullanıcısına aktarıldı",
|
||||
"Failed to transfer the board to {user}" : "Pano {user} kullanıcısına aktarılamadı",
|
||||
"Add a new list" : "Yeni liste ekle",
|
||||
"Archive all cards" : "Tüm kartları arşivle",
|
||||
"Delete list" : "Listeyi sil",
|
||||
|
||||
@@ -171,8 +171,11 @@
|
||||
"Owner" : "Sahibi",
|
||||
"Delete" : "Sil",
|
||||
"Failed to create share with {displayName}" : "{displayName} ile paylaşılamadı",
|
||||
"Are you sure you want to transfer the board {title} to {user}?" : "{title} panosunu {user} kullanıcısına aktarmak istediğinize emin misiniz?",
|
||||
"Transfer the board." : "Panoyu aktar.",
|
||||
"Transfer" : "Aktar",
|
||||
"The board has been transferred to {user}" : "Pano {user} kullanıcısına aktarıldı",
|
||||
"Failed to transfer the board to {user}" : "Pano {user} kullanıcısına aktarılamadı",
|
||||
"Add a new list" : "Yeni liste ekle",
|
||||
"Archive all cards" : "Tüm kartları arşivle",
|
||||
"Delete list" : "Listeyi sil",
|
||||
|
||||
@@ -173,8 +173,11 @@ OC.L10N.register(
|
||||
"Owner" : "所有者",
|
||||
"Delete" : "刪除",
|
||||
"Failed to create share with {displayName}" : "無法為 {displayName} 創建分享",
|
||||
"Are you sure you want to transfer the board {title} to {user}?" : "您確定要將面板 {title} 轉讓給 {user} 嗎?",
|
||||
"Transfer the board." : "轉移面板。",
|
||||
"Transfer" : "轉移",
|
||||
"The board has been transferred to {user}" : "面板已轉讓給 {user}",
|
||||
"Failed to transfer the board to {user}" : "未能將面板轉移給 {user}",
|
||||
"Add a new list" : "添加一張新清單",
|
||||
"Archive all cards" : "封存所有卡片",
|
||||
"Delete list" : "刪除清單",
|
||||
|
||||
@@ -171,8 +171,11 @@
|
||||
"Owner" : "所有者",
|
||||
"Delete" : "刪除",
|
||||
"Failed to create share with {displayName}" : "無法為 {displayName} 創建分享",
|
||||
"Are you sure you want to transfer the board {title} to {user}?" : "您確定要將面板 {title} 轉讓給 {user} 嗎?",
|
||||
"Transfer the board." : "轉移面板。",
|
||||
"Transfer" : "轉移",
|
||||
"The board has been transferred to {user}" : "面板已轉讓給 {user}",
|
||||
"Failed to transfer the board to {user}" : "未能將面板轉移給 {user}",
|
||||
"Add a new list" : "添加一張新清單",
|
||||
"Archive all cards" : "封存所有卡片",
|
||||
"Delete list" : "刪除清單",
|
||||
|
||||
@@ -35,7 +35,6 @@ use OCP\IConfig;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserManager;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCA\Deck\Service\CardService;
|
||||
|
||||
class DeckProvider implements IProvider {
|
||||
|
||||
@@ -53,10 +52,8 @@ class DeckProvider implements IProvider {
|
||||
private $l10nFactory;
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
/** @var CardService */
|
||||
private $cardService;
|
||||
|
||||
public function __construct(IURLGenerator $urlGenerator, ActivityManager $activityManager, IUserManager $userManager, ICommentsManager $commentsManager, IFactory $l10n, IConfig $config, $userId, CardService $cardService) {
|
||||
public function __construct(IURLGenerator $urlGenerator, ActivityManager $activityManager, IUserManager $userManager, ICommentsManager $commentsManager, IFactory $l10n, IConfig $config, $userId) {
|
||||
$this->userId = $userId;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->activityManager = $activityManager;
|
||||
@@ -64,7 +61,6 @@ class DeckProvider implements IProvider {
|
||||
$this->userManager = $userManager;
|
||||
$this->l10nFactory = $l10n;
|
||||
$this->config = $config;
|
||||
$this->cardService = $cardService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,7 +131,7 @@ class DeckProvider implements IProvider {
|
||||
|
||||
if (array_key_exists('board', $subjectParams)) {
|
||||
$archivedParam = $subjectParams['card']['archived'] ? 'archived/' : '';
|
||||
$card['link'] = $this->cardService->getRedirectUrlForCard($event->getObjectId());
|
||||
$card['link'] = $this->deckUrl('/board/' . $subjectParams['board']['id'] . '/' . $archivedParam . 'card/' . $event->getObjectId());
|
||||
}
|
||||
$params['card'] = $card;
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ class Application extends App implements IBootstrap {
|
||||
// Talk integration has its own entrypoint which already includes collections handling
|
||||
return;
|
||||
}
|
||||
Util::addScript('deck', 'deck-collections');
|
||||
Util::addScript('deck', 'collections');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,9 +100,6 @@ class ResourceProvider implements IProvider {
|
||||
if ($board->getOwner() === $user->getUID()) {
|
||||
return true;
|
||||
}
|
||||
if ($board->getAcl() === null) {
|
||||
return false;
|
||||
}
|
||||
return $this->permissionService->userCan($board->getAcl(), Acl::PERMISSION_READ, $user->getUID());
|
||||
}
|
||||
|
||||
|
||||
@@ -127,9 +127,6 @@ class ResourceProviderCard implements IProvider {
|
||||
if ($board->getOwner() === $user->getUID()) {
|
||||
return true;
|
||||
}
|
||||
if ($board->getAcl() === null) {
|
||||
return false;
|
||||
}
|
||||
return $this->permissionService->userCan($board->getAcl(), Acl::PERMISSION_READ, $user->getUID());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\Deck\Command;
|
||||
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Service\BoardService;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
final class TransferOwnership extends Command {
|
||||
protected $boardService;
|
||||
protected $boardMapper;
|
||||
protected $permissionService;
|
||||
protected $questionHelper;
|
||||
|
||||
public function __construct(BoardService $boardService, BoardMapper $boardMapper, PermissionService $permissionService, QuestionHelper $questionHelper) {
|
||||
parent::__construct();
|
||||
|
||||
$this->boardService = $boardService;
|
||||
$this->boardMapper = $boardMapper;
|
||||
$this->permissionService = $permissionService;
|
||||
$this->questionHelper = $questionHelper;
|
||||
}
|
||||
|
||||
protected function configure() {
|
||||
$this
|
||||
->setName('deck:transfer-ownership')
|
||||
->setDescription('Change owner of deck boards')
|
||||
->addArgument(
|
||||
'owner',
|
||||
InputArgument::REQUIRED,
|
||||
'Owner uid'
|
||||
)
|
||||
->addArgument(
|
||||
'newOwner',
|
||||
InputArgument::REQUIRED,
|
||||
'New owner uid'
|
||||
)
|
||||
->addArgument(
|
||||
'boardId',
|
||||
InputArgument::OPTIONAL,
|
||||
'Single board ID'
|
||||
)
|
||||
->addOption(
|
||||
'remap',
|
||||
'r',
|
||||
InputOption::VALUE_NONE,
|
||||
'Reassign card details of the old owner to the new one'
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$owner = $input->getArgument('owner');
|
||||
$newOwner = $input->getArgument('newOwner');
|
||||
$boardId = $input->getArgument('boardId');
|
||||
|
||||
$remapAssignment = $input->getOption('remap');
|
||||
|
||||
$this->boardService->setUserId($owner);
|
||||
$this->permissionService->setUserId($owner);
|
||||
|
||||
try {
|
||||
$board = $boardId ? $this->boardMapper->find($boardId) : null;
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln("Could not find a board for the provided id.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($boardId !== null && $board->getOwner() !== $owner) {
|
||||
$output->writeln("$owner is not the owner of the board $boardId (" . $board->getTitle() . ")");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($boardId) {
|
||||
$output->writeln("Transfer board " . $board->getTitle() . " from ". $board->getOwner() ." to $newOwner");
|
||||
} else {
|
||||
$output->writeln("Transfer all boards from $owner to $newOwner");
|
||||
}
|
||||
|
||||
$question = new ConfirmationQuestion('Do you really want to continue? (y/n) ', false);
|
||||
if (!$this->questionHelper->ask($input, $output, $question)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($boardId) {
|
||||
$this->boardService->transferBoardOwnership($boardId, $newOwner, $remapAssignment);
|
||||
$output->writeln("<info>Board " . $board->getTitle() . " from ". $board->getOwner() ." transferred to $newOwner completed</info>");
|
||||
return 0;
|
||||
}
|
||||
|
||||
foreach ($this->boardService->transferOwnership($owner, $newOwner, $remapAssignment) as $board) {
|
||||
$output->writeln(" - " . $board->getTitle() . " transferred");
|
||||
}
|
||||
$output->writeln("<info>All boards from $owner to $newOwner transferred</info>");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -24,12 +24,9 @@
|
||||
namespace OCA\Deck\Controller;
|
||||
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Service\BoardService;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCP\AppFramework\ApiController;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\IRequest;
|
||||
|
||||
class BoardController extends ApiController {
|
||||
@@ -153,20 +150,9 @@ class BoardController extends ApiController {
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @param $boardId
|
||||
* @return Board
|
||||
* @return \OCP\Deck\DB\Board
|
||||
*/
|
||||
public function clone($boardId) {
|
||||
return $this->boardService->clone($boardId, $this->userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function transferOwner(int $boardId, string $newOwner): DataResponse {
|
||||
if ($this->permissionService->userIsBoardOwner($boardId, $this->userId)) {
|
||||
return new DataResponse($this->boardService->transferBoardOwnership($boardId, $newOwner), HTTP::STATUS_OK);
|
||||
}
|
||||
|
||||
return new DataResponse([], HTTP::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +94,8 @@ class CardApiController extends ApiController {
|
||||
*
|
||||
* Update a card
|
||||
*/
|
||||
public function update($title, $type, $owner, $description = '', $order = 0, $duedate = null, $archived = null) {
|
||||
$card = $this->cardService->update($this->request->getParam('cardId'), $title, $this->request->getParam('stackId'), $type, $owner, $description, $order, $duedate, 0, $archived);
|
||||
public function update($title, $type, $order = 0, $description = '', $owner, $duedate = null, $archived = null) {
|
||||
$card = $this->cardService->update($this->request->getParam('cardId'), $title, $this->request->getParam('stackId'), $type, $order, $description, $owner, $duedate, 0, $archived);
|
||||
return new DataResponse($card, HTTP::STATUS_OK);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ class CardController extends Controller {
|
||||
* @return \OCP\AppFramework\Db\Entity
|
||||
*/
|
||||
public function update($id, $title, $stackId, $type, $order, $description, $duedate, $deletedAt) {
|
||||
return $this->cardService->update($id, $title, $stackId, $type, $this->userId, $description, $order, $duedate, $deletedAt);
|
||||
return $this->cardService->update($id, $title, $stackId, $type, $order, $description, $this->userId, $duedate, $deletedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,20 +34,12 @@ use OCP\IInitialStateService;
|
||||
use OCP\IRequest;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCP\IURLGenerator;
|
||||
use \OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Service\CardService;
|
||||
|
||||
class PageController extends Controller {
|
||||
private $permissionService;
|
||||
private $initialState;
|
||||
private $configService;
|
||||
private $eventDispatcher;
|
||||
private $cardMapper;
|
||||
private $urlGenerator;
|
||||
private $cardService;
|
||||
|
||||
public function __construct(
|
||||
$AppName,
|
||||
@@ -55,10 +47,7 @@ class PageController extends Controller {
|
||||
PermissionService $permissionService,
|
||||
IInitialStateService $initialStateService,
|
||||
ConfigService $configService,
|
||||
IEventDispatcher $eventDispatcher,
|
||||
CardMapper $cardMapper,
|
||||
IURLGenerator $urlGenerator,
|
||||
CardService $cardService
|
||||
IEventDispatcher $eventDispatcher
|
||||
) {
|
||||
parent::__construct($AppName, $request);
|
||||
|
||||
@@ -66,9 +55,6 @@ class PageController extends Controller {
|
||||
$this->initialState = $initialStateService;
|
||||
$this->configService = $configService;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->cardService = $cardService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,17 +85,4 @@ class PageController extends Controller {
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function redirectToCard($cardId): RedirectResponse {
|
||||
try {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
||||
return new RedirectResponse($this->cardService->getCardUrl($cardId));
|
||||
} catch (\Exception $e) {
|
||||
return new RedirectResponse($this->urlGenerator->linkToRouteAbsolute('deck.page.index'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,6 @@ class DeckWidget implements IWidget {
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function load(): void {
|
||||
\OCP\Util::addScript('deck', 'deck-dashboard');
|
||||
\OCP\Util::addScript('deck', 'dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace OCA\Deck\Db;
|
||||
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
class AclMapper extends DeckMapper implements IPermissionMapper {
|
||||
@@ -45,9 +44,9 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
|
||||
return ($row['owner'] === $userId);
|
||||
}
|
||||
|
||||
public function findBoardId($id): ?int {
|
||||
public function findBoardId($aclId): ?int {
|
||||
try {
|
||||
$entity = $this->find($id);
|
||||
$entity = $this->find($aclId);
|
||||
return $entity->getBoardId();
|
||||
} catch (DoesNotExistException | MultipleObjectsReturnedException $e) {
|
||||
}
|
||||
@@ -58,16 +57,4 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
|
||||
$sql = 'SELECT * from *PREFIX*deck_board_acl WHERE type = ? AND participant = ?';
|
||||
return $this->findEntities($sql, [$type, $participant]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function deleteParticipantFromBoard(int $boardId, int $type, string $participant): void {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->delete('deck_board_acl')
|
||||
->where($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->eq('participant', $qb->createNamedParameter($participant, IQueryBuilder::PARAM_STR)))
|
||||
->andWhere($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)));
|
||||
$qb->executeStatement();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ use OCA\Deck\NotFoundException;
|
||||
use OCA\Deck\Service\CirclesService;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUserManager;
|
||||
@@ -84,8 +83,8 @@ class AssignmentMapper extends QBMapper implements IPermissionMapper {
|
||||
return $this->cardMapper->isOwner($userId, $cardId);
|
||||
}
|
||||
|
||||
public function findBoardId($id): ?int {
|
||||
return $this->cardMapper->findBoardId($id);
|
||||
public function findBoardId($cardId): ?int {
|
||||
return $this->cardMapper->findBoardId($cardId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,39 +146,4 @@ class AssignmentMapper extends QBMapper implements IPermissionMapper {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function remapAssignedUser(int $boardId, string $userId, string $newUserId): void {
|
||||
$subQuery = $this->db->getQueryBuilder();
|
||||
$subQuery->selectAlias('a.id', 'id')
|
||||
->from('deck_assigned_users', 'a')
|
||||
->innerJoin('a', 'deck_cards', 'c', 'c.id = a.card_id')
|
||||
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
|
||||
->where($subQuery->expr()->eq('a.type', $subQuery->createNamedParameter(Assignment::TYPE_USER, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($subQuery->expr()->eq('a.participant', $subQuery->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
|
||||
->andWhere($subQuery->expr()->eq('s.board_id', $subQuery->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
||||
->setMaxResults(1000);
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->update('deck_assigned_users')
|
||||
->set('participant', $qb->createParameter('participant'))
|
||||
->where($qb->expr()->in('id', $qb->createParameter('ids')));
|
||||
|
||||
$moreResults = true;
|
||||
do {
|
||||
$result = $subQuery->executeQuery();
|
||||
$ids = array_map(function ($item) {
|
||||
return $item['id'];
|
||||
}, $result->fetchAll());
|
||||
|
||||
if (count($ids) === 0 || $result->rowCount() === 0) {
|
||||
$moreResults = false;
|
||||
}
|
||||
|
||||
$qb->setParameter('participant', $newUserId, IQueryBuilder::PARAM_STR);
|
||||
$qb->setParameter('ids', $ids, IQueryBuilder::PARAM_INT_ARRAY);
|
||||
$qb->executeStatement();
|
||||
} while ($moreResults === true);
|
||||
|
||||
$result->closeCursor();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,8 @@ class Board extends RelationalEntity {
|
||||
protected $owner;
|
||||
protected $color;
|
||||
protected $archived = false;
|
||||
/** @var Label[]|null */
|
||||
protected $labels = null;
|
||||
/** @var Acl[]|null */
|
||||
protected $acl = null;
|
||||
protected $labels = [];
|
||||
protected $acl = [];
|
||||
protected $permissions = [];
|
||||
protected $users = [];
|
||||
protected $shared;
|
||||
@@ -63,10 +61,6 @@ class Board extends RelationalEntity {
|
||||
if ($this->shared === -1) {
|
||||
unset($json['shared']);
|
||||
}
|
||||
// FIXME: Ideally the API responses should follow the internal data structure and return null if the labels/acls have not been fetched from the db
|
||||
// however this would be a breaking change for consumers of the API
|
||||
$json['acl'] = $this->acl ?? [];
|
||||
$json['labels'] = $this->labels ?? [];
|
||||
return $json;
|
||||
}
|
||||
|
||||
@@ -74,27 +68,21 @@ class Board extends RelationalEntity {
|
||||
* @param Label[] $labels
|
||||
*/
|
||||
public function setLabels($labels) {
|
||||
$this->labels = $labels;
|
||||
foreach ($labels as $l) {
|
||||
$this->labels[] = $l;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Acl[] $acl
|
||||
*/
|
||||
public function setAcl($acl) {
|
||||
$this->acl = $acl;
|
||||
foreach ($acl as $a) {
|
||||
$this->acl[] = $a;
|
||||
}
|
||||
}
|
||||
|
||||
public function getETag() {
|
||||
return md5((string)$this->getLastModified());
|
||||
}
|
||||
|
||||
/** @returns Acl[]|null */
|
||||
public function getAcl(): ?array {
|
||||
return $this->acl;
|
||||
}
|
||||
|
||||
/** @returns Label[]|null */
|
||||
public function getLabels(): ?array {
|
||||
return $this->labels;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,28 +24,23 @@
|
||||
namespace OCA\Deck\Db;
|
||||
|
||||
use OC\Cache\CappedMemoryCache;
|
||||
use OCA\Deck\Service\CirclesService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IGroupManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
class BoardMapper extends DeckMapper implements IPermissionMapper {
|
||||
private $labelMapper;
|
||||
private $aclMapper;
|
||||
private $stackMapper;
|
||||
private $userManager;
|
||||
private $groupManager;
|
||||
private $circlesService;
|
||||
private $logger;
|
||||
|
||||
/** @var CappedMemoryCache */
|
||||
private $circlesEnabled;
|
||||
|
||||
private $userBoardCache;
|
||||
/** @var CappedMemoryCache */
|
||||
private $boardCache;
|
||||
|
||||
public function __construct(
|
||||
IDBConnection $db,
|
||||
@@ -54,7 +49,6 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
StackMapper $stackMapper,
|
||||
IUserManager $userManager,
|
||||
IGroupManager $groupManager,
|
||||
CirclesService $circlesService,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
parent::__construct($db, 'deck_boards', Board::class);
|
||||
@@ -63,11 +57,12 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
$this->stackMapper = $stackMapper;
|
||||
$this->userManager = $userManager;
|
||||
$this->groupManager = $groupManager;
|
||||
$this->circlesService = $circlesService;
|
||||
$this->logger = $logger;
|
||||
|
||||
$this->userBoardCache = new CappedMemoryCache();
|
||||
$this->boardCache = new CappedMemoryCache();
|
||||
|
||||
|
||||
$this->circlesEnabled = \OC::$server->getAppManager()->isEnabledForUser('circles');
|
||||
}
|
||||
|
||||
|
||||
@@ -75,52 +70,40 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
* @param $id
|
||||
* @param bool $withLabels
|
||||
* @param bool $withAcl
|
||||
* @return Board
|
||||
* @return \OCP\AppFramework\Db\Entity
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
* @throws DoesNotExistException
|
||||
*/
|
||||
public function find($id, $withLabels = false, $withAcl = false): Board {
|
||||
if (!isset($this->boardCache[$id])) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('deck_boards')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
||||
->orderBy('id');
|
||||
$this->boardCache[$id] = $this->findEntity($qb);
|
||||
}
|
||||
|
||||
// FIXME is this necessary? it was NOT done with the old mapper
|
||||
// $this->mapOwner($board);
|
||||
public function find($id, $withLabels = false, $withAcl = false) {
|
||||
$sql = 'SELECT id, title, owner, color, archived, deleted_at, last_modified FROM `*PREFIX*deck_boards` ' .
|
||||
'WHERE `id` = ?';
|
||||
$board = $this->findEntity($sql, [$id]);
|
||||
|
||||
// Add labels
|
||||
if ($withLabels && $this->boardCache[$id]->getLabels() === null) {
|
||||
if ($withLabels) {
|
||||
$labels = $this->labelMapper->findAll($id);
|
||||
$this->boardCache[$id]->setLabels($labels);
|
||||
$board->setLabels($labels);
|
||||
}
|
||||
|
||||
// Add acl
|
||||
if ($withAcl && $this->boardCache[$id]->getAcl() === null) {
|
||||
if ($withAcl) {
|
||||
$acl = $this->aclMapper->findAll($id);
|
||||
$this->boardCache[$id]->setAcl($acl);
|
||||
$board->setAcl($acl);
|
||||
}
|
||||
|
||||
return $this->boardCache[$id];
|
||||
return $board;
|
||||
}
|
||||
|
||||
public function findAllForUser(string $userId, ?int $since = null, bool $includeArchived = true, ?int $before = null,
|
||||
?string $term = null): array {
|
||||
$useCache = ($since === -1 && $includeArchived === true && $before === null && $term === null);
|
||||
public function findAllForUser(string $userId, int $since = -1, $includeArchived = true): array {
|
||||
$useCache = ($since === -1 && $includeArchived === true);
|
||||
if (!isset($this->userBoardCache[$userId]) || !$useCache) {
|
||||
$groups = $this->groupManager->getUserGroupIds(
|
||||
$this->userManager->get($userId)
|
||||
);
|
||||
$userBoards = $this->findAllByUser($userId, null, null, $since, $includeArchived, $before, $term);
|
||||
$groupBoards = $this->findAllByGroups($userId, $groups, null, null, $since, $includeArchived, $before, $term);
|
||||
$circleBoards = $this->findAllByCircles($userId, null, null, $since, $includeArchived, $before, $term);
|
||||
$userBoards = $this->findAllByUser($userId, null, null, $since, $includeArchived);
|
||||
$groupBoards = $this->findAllByGroups($userId, $groups, null, null, $since, $includeArchived);
|
||||
$circleBoards = $this->findAllByCircles($userId, null, null, $since, $includeArchived);
|
||||
$allBoards = array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
|
||||
foreach ($allBoards as $board) {
|
||||
$this->boardCache[$board->getId()] = $board;
|
||||
}
|
||||
if ($useCache) {
|
||||
$this->userBoardCache[$userId] = $allBoards;
|
||||
}
|
||||
@@ -137,91 +120,19 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
* @param null $offset
|
||||
* @return array
|
||||
*/
|
||||
public function findAllByUser(string $userId, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
||||
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
|
||||
// FIXME this used to be a UNION to get boards owned by $userId and the user shares in one single query
|
||||
// Is it possible with the query builder?
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||
// this does not work in MySQL/PostgreSQL
|
||||
//->selectAlias('0', 'shared')
|
||||
->from('deck_boards', 'b')
|
||||
->where($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||
public function findAllByUser($userId, $limit = null, $offset = null, $since = -1, $includeArchived = true) {
|
||||
// FIXME: One moving to QBMapper we should allow filtering the boards probably by method chaining for additional where clauses
|
||||
$sql = 'SELECT id, title, owner, color, archived, deleted_at, 0 as shared, last_modified FROM `*PREFIX*deck_boards` WHERE owner = ? AND last_modified > ?';
|
||||
if (!$includeArchived) {
|
||||
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||
$sql .= ' AND NOT archived AND deleted_at = 0';
|
||||
}
|
||||
if ($since !== null) {
|
||||
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($before !== null) {
|
||||
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($term !== null) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->iLike(
|
||||
'title',
|
||||
$qb->createNamedParameter(
|
||||
'%' . $this->db->escapeLikeParameter($term) . '%',
|
||||
IQueryBuilder::PARAM_STR
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$qb->orderBy('b.id');
|
||||
if ($limit !== null) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if ($offset !== null) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
$entries = $this->findEntities($qb);
|
||||
foreach ($entries as $entry) {
|
||||
$entry->setShared(0);
|
||||
}
|
||||
|
||||
// shared with user
|
||||
$qb->resetQueryParts();
|
||||
$qb->select('b.id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||
//->selectAlias('1', 'shared')
|
||||
->from('deck_boards', 'b')
|
||||
->innerJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'))
|
||||
->where($qb->expr()->eq('acl.participant', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
|
||||
->andWhere($qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_USER, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->neq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||
$sql .= ' UNION ' .
|
||||
'SELECT boards.id, title, owner, color, archived, deleted_at, 1 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' .
|
||||
'JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE acl.participant=? AND acl.type=? AND boards.owner != ? AND last_modified > ?';
|
||||
if (!$includeArchived) {
|
||||
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||
$sql .= ' AND NOT archived AND deleted_at = 0';
|
||||
}
|
||||
if ($since !== null) {
|
||||
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($before !== null) {
|
||||
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($term !== null) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->iLike(
|
||||
'title',
|
||||
$qb->createNamedParameter(
|
||||
'%' . $this->db->escapeLikeParameter($term) . '%',
|
||||
IQueryBuilder::PARAM_STR
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$qb->orderBy('b.id');
|
||||
if ($limit !== null) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if ($offset !== null) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
$sharedEntries = $this->findEntities($qb);
|
||||
foreach ($sharedEntries as $entry) {
|
||||
$entry->setShared(1);
|
||||
}
|
||||
$entries = array_merge($entries, $sharedEntries);
|
||||
$entries = $this->findEntities($sql, [$userId, $since, $userId, Acl::PERMISSION_TYPE_USER, $userId, $since], $limit, $offset);
|
||||
/* @var Board $entry */
|
||||
foreach ($entries as $entry) {
|
||||
$acl = $this->aclMapper->findAll($entry->id);
|
||||
@@ -230,19 +141,9 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
return $entries;
|
||||
}
|
||||
|
||||
public function findAllByOwner(string $userId, ?int $limit = null, ?int $offset = null) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('deck_boards')
|
||||
->where($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
|
||||
->orderBy('id');
|
||||
if ($limit !== null) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if ($offset !== null) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
return $this->findEntities($qb);
|
||||
public function findAllByOwner(string $userId, int $limit = null, int $offset = null) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*deck_boards` WHERE owner = ?';
|
||||
return $this->findEntities($sql, [$userId], $limit, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,57 +155,23 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
* @param null $offset
|
||||
* @return array
|
||||
*/
|
||||
public function findAllByGroups(string $userId, array $groups, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
||||
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
|
||||
public function findAllByGroups($userId, $groups, $limit = null, $offset = null, $since = -1,$includeArchived = true) {
|
||||
if (count($groups) <= 0) {
|
||||
return [];
|
||||
}
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('b.id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||
//->selectAlias('2', 'shared')
|
||||
->from('deck_boards', 'b')
|
||||
->innerJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'))
|
||||
->where($qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_GROUP, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->neq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||
$or = $qb->expr()->orx();
|
||||
$sql = 'SELECT boards.id, title, owner, color, archived, deleted_at, 2 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' .
|
||||
'INNER JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE owner != ? AND type=? AND (';
|
||||
for ($i = 0, $iMax = count($groups); $i < $iMax; $i++) {
|
||||
$or->add(
|
||||
$qb->expr()->eq('acl.participant', $qb->createNamedParameter($groups[$i], IQueryBuilder::PARAM_STR))
|
||||
);
|
||||
$sql .= 'acl.participant = ? ';
|
||||
if (count($groups) > 1 && $i < count($groups) - 1) {
|
||||
$sql .= ' OR ';
|
||||
}
|
||||
}
|
||||
$qb->andWhere($or);
|
||||
$sql .= ')';
|
||||
if (!$includeArchived) {
|
||||
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($since !== null) {
|
||||
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($before !== null) {
|
||||
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($term !== null) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->iLike(
|
||||
'title',
|
||||
$qb->createNamedParameter(
|
||||
'%' . $this->db->escapeLikeParameter($term) . '%',
|
||||
IQueryBuilder::PARAM_STR
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$qb->orderBy('b.id');
|
||||
if ($limit !== null) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if ($offset !== null) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
$entries = $this->findEntities($qb);
|
||||
foreach ($entries as $entry) {
|
||||
$entry->setShared(2);
|
||||
$sql .= ' AND NOT archived AND deleted_at = 0';
|
||||
}
|
||||
$entries = $this->findEntities($sql, array_merge([$userId, Acl::PERMISSION_TYPE_GROUP], $groups), $limit, $offset);
|
||||
/* @var Board $entry */
|
||||
foreach ($entries as $entry) {
|
||||
$acl = $this->aclMapper->findAll($entry->id);
|
||||
@@ -313,59 +180,30 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
return $entries;
|
||||
}
|
||||
|
||||
public function findAllByCircles(string $userId, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
||||
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
|
||||
$circles = $this->circlesService->getUserCircles($userId);
|
||||
public function findAllByCircles($userId, $limit = null, $offset = null, $since = -1,$includeArchived = true) {
|
||||
if (!$this->circlesEnabled) {
|
||||
return [];
|
||||
}
|
||||
$circles = array_map(function ($circle) {
|
||||
return $circle->getUniqueId();
|
||||
}, \OCA\Circles\Api\v1\Circles::joinedCircles($userId, true));
|
||||
if (count($circles) === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('b.id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||
//->selectAlias('2', 'shared')
|
||||
->from('deck_boards', 'b')
|
||||
->innerJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'))
|
||||
->where($qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_CIRCLE, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->neq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||
$or = $qb->expr()->orx();
|
||||
$sql = 'SELECT boards.id, title, owner, color, archived, deleted_at, 2 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' .
|
||||
'INNER JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE owner != ? AND type=? AND (';
|
||||
for ($i = 0, $iMax = count($circles); $i < $iMax; $i++) {
|
||||
$or->add(
|
||||
$qb->expr()->eq('acl.participant', $qb->createNamedParameter($circles[$i], IQueryBuilder::PARAM_STR))
|
||||
);
|
||||
$sql .= 'acl.participant = ? ';
|
||||
if (count($circles) > 1 && $i < count($circles) - 1) {
|
||||
$sql .= ' OR ';
|
||||
}
|
||||
}
|
||||
$qb->andWhere($or);
|
||||
$sql .= ')';
|
||||
if (!$includeArchived) {
|
||||
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($since !== null) {
|
||||
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($before !== null) {
|
||||
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($term !== null) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->iLike(
|
||||
'title',
|
||||
$qb->createNamedParameter(
|
||||
'%' . $this->db->escapeLikeParameter($term) . '%',
|
||||
IQueryBuilder::PARAM_STR
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$qb->orderBy('b.id');
|
||||
if ($limit !== null) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if ($offset !== null) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
$entries = $this->findEntities($qb);
|
||||
foreach ($entries as $entry) {
|
||||
$entry->setShared(2);
|
||||
$sql .= ' AND NOT archived AND deleted_at = 0';
|
||||
}
|
||||
$entries = $this->findEntities($sql, array_merge([$userId, Acl::PERMISSION_TYPE_CIRCLE], $circles), $limit, $offset);
|
||||
/* @var Board $entry */
|
||||
foreach ($entries as $entry) {
|
||||
$acl = $this->aclMapper->findAll($entry->id);
|
||||
@@ -374,26 +212,21 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
return $entries;
|
||||
}
|
||||
|
||||
public function findAll(): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id')
|
||||
->from('deck_boards');
|
||||
return $this->findEntities($qb);
|
||||
public function findAll() {
|
||||
$sql = 'SELECT id from *PREFIX*deck_boards;';
|
||||
return $this->findEntities($sql);
|
||||
}
|
||||
|
||||
public function findToDelete() {
|
||||
// add buffer of 5 min
|
||||
$timeLimit = time() - (60 * 5);
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||
->from('deck_boards')
|
||||
->where($qb->expr()->gt('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->lt('deleted_at', $qb->createNamedParameter($timeLimit, IQueryBuilder::PARAM_INT)));
|
||||
return $this->findEntities($qb);
|
||||
$sql = 'SELECT id, title, owner, color, archived, deleted_at, last_modified FROM `*PREFIX*deck_boards` ' .
|
||||
'WHERE `deleted_at` > 0 AND `deleted_at` < ?';
|
||||
return $this->findEntities($sql, [$timeLimit]);
|
||||
}
|
||||
|
||||
public function delete(/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
||||
\OCP\AppFramework\Db\Entity $entity): \OCP\AppFramework\Db\Entity {
|
||||
\OCP\AppFramework\Db\Entity $entity) {
|
||||
// delete acl
|
||||
$acl = $this->aclMapper->findAll($entity->getId());
|
||||
foreach ($acl as $item) {
|
||||
@@ -444,11 +277,11 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
return null;
|
||||
}
|
||||
if ($acl->getType() === Acl::PERMISSION_TYPE_CIRCLE) {
|
||||
if (!$this->circlesService->isCirclesEnabled()) {
|
||||
if (!$this->circlesEnabled) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
$circle = $this->circlesService->getCircle($acl->getParticipant());
|
||||
$circle = \OCA\Circles\Api\v1\Circles::detailsCircle($acl->getParticipant(), true);
|
||||
if ($circle) {
|
||||
return new Circle($circle);
|
||||
}
|
||||
@@ -475,34 +308,4 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function transferOwnership(string $ownerId, string $newOwnerId, $boardId = null): void {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->update('deck_boards')
|
||||
->set('owner', $qb->createNamedParameter($newOwnerId, IQueryBuilder::PARAM_STR))
|
||||
->where($qb->expr()->eq('owner', $qb->createNamedParameter($ownerId, IQueryBuilder::PARAM_STR)));
|
||||
if ($boardId !== null) {
|
||||
$qb->andWhere($qb->expr()->eq('id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
$qb->executeStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset cache for a given board or a given user
|
||||
*/
|
||||
public function flushCache(?int $boardId = null, ?string $userId = null) {
|
||||
if ($boardId) {
|
||||
unset($this->boardCache[$boardId]);
|
||||
} else {
|
||||
$this->boardCache = null;
|
||||
}
|
||||
if ($userId) {
|
||||
unset($this->userBoardCache[$userId]);
|
||||
} else {
|
||||
$this->userBoardCache = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ use OCA\Deck\Search\Query\SearchQuery;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\ICache;
|
||||
use OCP\ICacheFactory;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUser;
|
||||
@@ -48,8 +46,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
private $groupManager;
|
||||
/** @var IManager */
|
||||
private $notificationManager;
|
||||
/** @var ICache */
|
||||
private $cache;
|
||||
private $databaseType;
|
||||
private $database4ByteSupport;
|
||||
|
||||
@@ -59,7 +55,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
IUserManager $userManager,
|
||||
IGroupManager $groupManager,
|
||||
IManager $notificationManager,
|
||||
ICacheFactory $cacheFactory,
|
||||
$databaseType = 'sqlite3',
|
||||
$database4ByteSupport = true
|
||||
) {
|
||||
@@ -68,7 +63,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
$this->userManager = $userManager;
|
||||
$this->groupManager = $groupManager;
|
||||
$this->notificationManager = $notificationManager;
|
||||
$this->cache = $cacheFactory->createDistributed('deck-cardMapper');
|
||||
$this->databaseType = $databaseType;
|
||||
$this->database4ByteSupport = $database4ByteSupport;
|
||||
}
|
||||
@@ -81,9 +75,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
$description = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $entity->getDescription());
|
||||
$entity->setDescription($description);
|
||||
}
|
||||
$entity = parent::insert($entity);
|
||||
$this->cache->remove('findBoardId:' . $entity->getId());
|
||||
return $entity;
|
||||
return parent::insert($entity);
|
||||
}
|
||||
|
||||
public function update(Entity $entity, $updateModified = true): Entity {
|
||||
@@ -115,10 +107,6 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
// Invalidate cache when the card may be moved to a different board
|
||||
if (isset($updatedFields['stackId'])) {
|
||||
$this->cache->remove('findBoardId:' . $entity->getId());
|
||||
}
|
||||
return parent::update($entity);
|
||||
}
|
||||
|
||||
@@ -242,18 +230,16 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
public function findToMeOrNotAssignedCards($boardId, $username) {
|
||||
public function findAssignedCards($boardId, $username) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('c.*')
|
||||
->from('deck_cards', 'c')
|
||||
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
|
||||
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
|
||||
->leftJoin('c', 'deck_assigned_users', 'u', 'c.id = u.card_id')
|
||||
->innerJoin('c', 'deck_assigned_users', 'u', 'c.id = u.card_id')
|
||||
->where($qb->expr()->eq('s.board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->orX(
|
||||
$qb->expr()->eq('u.participant', $qb->createNamedParameter($username, IQueryBuilder::PARAM_STR)),
|
||||
$qb->expr()->isNull('u.participant'))
|
||||
)
|
||||
->andWhere($qb->expr()->eq('u.participant', $qb->createNamedParameter($username, IQueryBuilder::PARAM_STR)))
|
||||
->andWhere($qb->expr()->eq('u.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_USER, IQueryBuilder::PARAM_INT)))
|
||||
// Filter out archived/deleted cards and board
|
||||
->andWhere($qb->expr()->eq('c.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
|
||||
@@ -491,8 +477,8 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
}
|
||||
return $qb->createNamedParameter($dateTime, IQueryBuilder::PARAM_DATE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public function searchRaw($boardIds, $term, $limit = null, $offset = null) {
|
||||
$qb = $this->queryCardsByBoards($boardIds)
|
||||
@@ -518,8 +504,9 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
}
|
||||
|
||||
public function delete(Entity $entity): Entity {
|
||||
// delete assigned labels
|
||||
$this->labelMapper->deleteLabelAssignmentsForCard($entity->getId());
|
||||
$this->cache->remove('findBoardId:' . $entity->getId());
|
||||
// delete card
|
||||
return parent::delete($entity);
|
||||
}
|
||||
|
||||
@@ -557,23 +544,12 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
return ($row['owner'] === $userId);
|
||||
}
|
||||
|
||||
public function findBoardId($id): ?int {
|
||||
$result = $this->cache->get('findBoardId:' . $id);
|
||||
if ($result === null) {
|
||||
try {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('board_id')
|
||||
->from('deck_stacks', 's')
|
||||
->innerJoin('s', 'deck_cards', 'c', 'c.stack_id = s.id')
|
||||
->where($qb->expr()->eq('c.id', $qb->createNamedParameter($id)));
|
||||
$queryResult = $qb->executeQuery();
|
||||
$result = $queryResult->fetchOne();
|
||||
} catch (\Exception $e) {
|
||||
$result = false;
|
||||
}
|
||||
$this->cache->set('findBoardId:' . $id, $result);
|
||||
}
|
||||
return $result !== false ? $result : null;
|
||||
public function findBoardId($cardId): ?int {
|
||||
$sql = 'SELECT id FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_stacks` WHERE id IN (SELECT stack_id FROM `*PREFIX*deck_cards` WHERE id = ?))';
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->bindParam(1, $cardId, \PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
return $stmt->fetchColumn() ?? null;
|
||||
}
|
||||
|
||||
public function mapOwner(Card &$card) {
|
||||
@@ -586,47 +562,4 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public function transferOwnership(string $ownerId, string $newOwnerId, int $boardId = null): void {
|
||||
$params = [
|
||||
'owner' => $ownerId,
|
||||
'newOwner' => $newOwnerId
|
||||
];
|
||||
$sql = "UPDATE `*PREFIX*{$this->tableName}` SET `owner` = :newOwner WHERE `owner` = :owner";
|
||||
$stmt = $this->db->executeQuery($sql, $params);
|
||||
$stmt->closeCursor();
|
||||
}
|
||||
|
||||
public function remapCardOwner(int $boardId, string $userId, string $newUserId): void {
|
||||
$subQuery = $this->db->getQueryBuilder();
|
||||
$subQuery->selectAlias('c.id', 'id')
|
||||
->from('deck_cards', 'c')
|
||||
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
|
||||
->where($subQuery->expr()->eq('c.owner', $subQuery->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
|
||||
->andWhere($subQuery->expr()->eq('s.board_id', $subQuery->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
||||
->setMaxResults(1000);
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->update('deck_cards')
|
||||
->set('owner', $qb->createParameter('owner'))
|
||||
->where($qb->expr()->in('id', $qb->createParameter('ids')));
|
||||
|
||||
$moreResults = true;
|
||||
do {
|
||||
$result = $subQuery->executeQuery();
|
||||
$ids = array_map(function ($item) {
|
||||
return $item['id'];
|
||||
}, $result->fetchAll());
|
||||
|
||||
if (count($ids) === 0 || $result->rowCount() === 0) {
|
||||
$moreResults = false;
|
||||
}
|
||||
|
||||
$qb->setParameter('owner', $newUserId, IQueryBuilder::PARAM_STR);
|
||||
$qb->setParameter('ids', $ids, IQueryBuilder::PARAM_INT_ARRAY);
|
||||
$qb->executeStatement();
|
||||
} while ($moreResults === true);
|
||||
|
||||
$result->closeCursor();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@ class Circle extends RelationalObject {
|
||||
public function getObjectSerialization() {
|
||||
return [
|
||||
'uid' => $this->object->getUniqueId(),
|
||||
'displayname' => $this->object->getDisplayName(),
|
||||
'typeString' => '',
|
||||
'displayname' => $this->object->getName(),
|
||||
'typeString' => $this->object->getTypeString(),
|
||||
'circleOwner' => $this->object->getOwner(),
|
||||
'type' => 7
|
||||
];
|
||||
|
||||
@@ -101,9 +101,9 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
|
||||
return ($row['owner'] === $userId);
|
||||
}
|
||||
|
||||
public function findBoardId($id): ?int {
|
||||
public function findBoardId($labelId): ?int {
|
||||
try {
|
||||
$entity = $this->find($id);
|
||||
$entity = $this->find($labelId);
|
||||
return $entity->getBoardId();
|
||||
} catch (DoesNotExistException $e) {
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
|
||||
@@ -75,9 +75,9 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
|
||||
return ($row['owner'] === $userId);
|
||||
}
|
||||
|
||||
public function findBoardId($id): ?int {
|
||||
public function findBoardId($stackId): ?int {
|
||||
try {
|
||||
$entity = $this->find($id);
|
||||
$entity = $this->find($stackId);
|
||||
return $entity->getBoardId();
|
||||
} catch (DoesNotExistException $e) {
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
|
||||
@@ -51,11 +51,11 @@ class BeforeTemplateRenderedListener implements IEventListener {
|
||||
|
||||
$pathInfo = $this->request->getPathInfo();
|
||||
if (strpos($pathInfo, '/apps/calendar') === 0) {
|
||||
Util::addScript('deck', 'deck-calendar');
|
||||
Util::addScript('deck', 'calendar');
|
||||
}
|
||||
|
||||
if (strpos($pathInfo, '/call/') === 0 || strpos($pathInfo, '/apps/spreed') === 0) {
|
||||
Util::addScript('deck', 'deck-talk');
|
||||
Util::addScript('deck', 'talk');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 Your name <your@email.com>
|
||||
*
|
||||
* @author Your name <your@email.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Migration;
|
||||
|
||||
use Closure;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
class Version10800Date20220422061816 extends SimpleMigrationStep {
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure(): ISchemaWrapper $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
$schema = $schemaClosure();
|
||||
|
||||
$indexAdded = $this->addIndex($schema,
|
||||
'deck_boards',
|
||||
'idx_owner_modified',
|
||||
[ 'owner', 'last_modified' ]
|
||||
);
|
||||
|
||||
$indexAdded = $this->addIndex($schema,
|
||||
'deck_board_acl',
|
||||
'idx_participant_type',
|
||||
[ 'participant', 'type']
|
||||
) || $indexAdded;
|
||||
|
||||
$indexAdded = $this->addIndex($schema,
|
||||
'deck_cards',
|
||||
'idx_due_notified_archived_deleted', [
|
||||
'duedate', 'notified', 'archived', 'deleted_at'
|
||||
],
|
||||
) || $indexAdded;
|
||||
|
||||
$indexAdded = $this->addIndex($schema,
|
||||
'deck_cards',
|
||||
'idx_last_editor', [
|
||||
'last_editor', 'description_prev'
|
||||
], [],
|
||||
// Adding a partial index on the description_prev as it is only used for a NULL check
|
||||
['lengths' => [null, 1]]
|
||||
) || $indexAdded;
|
||||
|
||||
$indexAdded = $this->addIndex($schema,
|
||||
'deck_attachment',
|
||||
'idx_cardid_deletedat',
|
||||
[ 'card_id', 'deleted_at']
|
||||
) || $indexAdded;
|
||||
|
||||
$indexAdded = $this->addIndex($schema,
|
||||
'deck_assigned_users',
|
||||
'idx_card_participant',
|
||||
[ 'card_id', 'participant']
|
||||
) || $indexAdded;
|
||||
|
||||
return $indexAdded ? $schema : null;
|
||||
}
|
||||
|
||||
private function addIndex(ISchemaWrapper $schema, string $table, string $indexName, array $columns, array $flags = [], array $options = []): bool {
|
||||
$table = $schema->getTable($table);
|
||||
if (!$table->hasIndex($indexName)) {
|
||||
$table->addIndex($columns, $indexName, $flags, $options);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -238,7 +238,7 @@ class DeckProvider implements IFullTextSearchProvider {
|
||||
*
|
||||
* @param ISearchRequest $request
|
||||
*/
|
||||
public function improveSearchRequest(ISearchRequest $searchRequest) {
|
||||
public function improveSearchRequest(ISearchRequest $request) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,14 +24,12 @@
|
||||
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use OCA\Deck\Activity\ActivityManager;
|
||||
use OCA\Deck\Activity\ChangeSet;
|
||||
use OCA\Deck\AppInfo\Application;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\AclMapper;
|
||||
use OCA\Deck\Db\AssignmentMapper;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\ChangeHelper;
|
||||
use OCA\Deck\Db\IPermissionMapper;
|
||||
use OCA\Deck\Db\Label;
|
||||
@@ -52,7 +50,6 @@ use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\LabelMapper;
|
||||
use OCP\IUserManager;
|
||||
use OCA\Deck\BadRequestException;
|
||||
use OCP\IURLGenerator;
|
||||
|
||||
class BoardService {
|
||||
private $boardMapper;
|
||||
@@ -71,10 +68,8 @@ class BoardService {
|
||||
private $activityManager;
|
||||
private $eventDispatcher;
|
||||
private $changeHelper;
|
||||
private $cardMapper;
|
||||
|
||||
private $boardsCache = null;
|
||||
private $urlGenerator;
|
||||
|
||||
|
||||
public function __construct(
|
||||
@@ -87,13 +82,11 @@ class BoardService {
|
||||
PermissionService $permissionService,
|
||||
NotificationHelper $notificationHelper,
|
||||
AssignmentMapper $assignedUsersMapper,
|
||||
CardMapper $cardMapper,
|
||||
IUserManager $userManager,
|
||||
IGroupManager $groupManager,
|
||||
ActivityManager $activityManager,
|
||||
IEventDispatcher $eventDispatcher,
|
||||
ChangeHelper $changeHelper,
|
||||
IURLGenerator $urlGenerator,
|
||||
$userId
|
||||
) {
|
||||
$this->boardMapper = $boardMapper;
|
||||
@@ -111,8 +104,6 @@ class BoardService {
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->changeHelper = $changeHelper;
|
||||
$this->userId = $userId;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->cardMapper = $cardMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,9 +118,8 @@ class BoardService {
|
||||
/**
|
||||
* Get all boards that are shared with a user, their groups or circles
|
||||
*/
|
||||
public function getUserBoards(?int $since = null, bool $includeArchived = true, ?int $before = null,
|
||||
?string $term = null): array {
|
||||
return $this->boardMapper->findAllForUser($this->userId, $since, $includeArchived, $before, $term);
|
||||
public function getUserBoards(int $since = -1, bool $includeArchived = true): array {
|
||||
return $this->boardMapper->findAllForUser($this->userId, $since, $includeArchived);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,11 +178,9 @@ class BoardService {
|
||||
/** @var Board $board */
|
||||
$board = $this->boardMapper->find($boardId, true, true);
|
||||
$this->boardMapper->mapOwner($board);
|
||||
if ($board->getAcl() !== null) {
|
||||
foreach ($board->getAcl() as $acl) {
|
||||
if ($acl !== null) {
|
||||
$this->boardMapper->mapAcl($acl);
|
||||
}
|
||||
foreach ($board->getAcl() as &$acl) {
|
||||
if ($acl !== null) {
|
||||
$this->boardMapper->mapAcl($acl);
|
||||
}
|
||||
}
|
||||
$permissions = $this->permissionService->matchPermissions($board);
|
||||
@@ -524,14 +512,11 @@ class BoardService {
|
||||
$acl->setPermissionManage($manage);
|
||||
$newAcl = $this->aclMapper->insert($acl);
|
||||
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE, [], $this->userId);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE);
|
||||
$this->notificationHelper->sendBoardShared((int)$boardId, $acl);
|
||||
$this->boardMapper->mapAcl($newAcl);
|
||||
$this->changeHelper->boardChanged($boardId);
|
||||
|
||||
$board = $this->boardMapper->find($boardId);
|
||||
$this->clearBoardFromCache($board);
|
||||
|
||||
// TODO: use the dispatched event for this
|
||||
try {
|
||||
$resourceProvider = \OC::$server->query(\OCA\Deck\Collaboration\Resources\ResourceProvider::class);
|
||||
@@ -682,43 +667,6 @@ class BoardService {
|
||||
return $newBoard;
|
||||
}
|
||||
|
||||
public function transferBoardOwnership(int $boardId, string $newOwner, bool $changeContent = false): Board {
|
||||
\OC::$server->getDatabaseConnection()->beginTransaction();
|
||||
try {
|
||||
$board = $this->boardMapper->find($boardId);
|
||||
$previousOwner = $board->getOwner();
|
||||
$this->clearBoardFromCache($board);
|
||||
$this->aclMapper->deleteParticipantFromBoard($boardId, Acl::PERMISSION_TYPE_USER, $newOwner);
|
||||
if (!$changeContent) {
|
||||
try {
|
||||
$this->addAcl($boardId, Acl::PERMISSION_TYPE_USER, $previousOwner, true, true, true);
|
||||
} catch (UniqueConstraintViolationException $e) {
|
||||
}
|
||||
}
|
||||
$this->boardMapper->transferOwnership($previousOwner, $newOwner, $boardId);
|
||||
|
||||
// Optionally also change user assignments and card owner information
|
||||
if ($changeContent) {
|
||||
$this->assignedUsersMapper->remapAssignedUser($boardId, $previousOwner, $newOwner);
|
||||
$this->cardMapper->remapCardOwner($boardId, $previousOwner, $newOwner);
|
||||
}
|
||||
\OC::$server->getDatabaseConnection()->commit();
|
||||
return $this->boardMapper->find($boardId);
|
||||
} catch (\Throwable $e) {
|
||||
\OC::$server->getDatabaseConnection()->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function transferOwnership(string $owner, string $newOwner, bool $changeContent = false): \Generator {
|
||||
$boards = $this->boardMapper->findAllByUser($owner);
|
||||
foreach ($boards as $board) {
|
||||
if ($board->getOwner() === $owner) {
|
||||
yield $this->transferBoardOwnership($board->getId(), $newOwner, $changeContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function enrichWithStacks($board, $since = -1) {
|
||||
$stacks = $this->stackMapper->findAll($board->getId(), null, null, $since);
|
||||
|
||||
@@ -746,23 +694,4 @@ class BoardService {
|
||||
}
|
||||
$board->setUsers(array_values($boardUsers));
|
||||
}
|
||||
|
||||
public function getBoardUrl($endpoint) {
|
||||
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . '#' . $endpoint;
|
||||
}
|
||||
|
||||
private function clearBoardsCache() {
|
||||
$this->boardsCache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean a given board data from the Cache
|
||||
*/
|
||||
private function clearBoardFromCache(Board $board) {
|
||||
$boardId = $board->getId();
|
||||
$boardOwnerId = $board->getOwner();
|
||||
|
||||
$this->boardMapper->flushCache($boardId, $boardOwnerId);
|
||||
unset($this->boardsCache[$boardId]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\Event\CardCreatedEvent;
|
||||
use OCA\Deck\Event\CardDeletedEvent;
|
||||
use OCA\Deck\Event\CardUpdatedEvent;
|
||||
use OCA\Deck\NoPermissionException;
|
||||
use OCA\Deck\Notification\NotificationHelper;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\LabelMapper;
|
||||
@@ -46,7 +45,6 @@ use OCA\Deck\BadRequestException;
|
||||
use OCP\Comments\ICommentsManager;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IURLGenerator;
|
||||
|
||||
class CardService {
|
||||
private $cardMapper;
|
||||
@@ -64,7 +62,6 @@ class CardService {
|
||||
private $changeHelper;
|
||||
private $eventDispatcher;
|
||||
private $userManager;
|
||||
private $urlGenerator;
|
||||
|
||||
public function __construct(
|
||||
CardMapper $cardMapper,
|
||||
@@ -81,7 +78,6 @@ class CardService {
|
||||
IUserManager $userManager,
|
||||
ChangeHelper $changeHelper,
|
||||
IEventDispatcher $eventDispatcher,
|
||||
IURLGenerator $urlGenerator,
|
||||
$userId
|
||||
) {
|
||||
$this->cardMapper = $cardMapper;
|
||||
@@ -99,7 +95,6 @@ class CardService {
|
||||
$this->changeHelper = $changeHelper;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->currentUser = $userId;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
public function enrich($card) {
|
||||
@@ -159,12 +154,7 @@ class CardService {
|
||||
}
|
||||
|
||||
public function findCalendarEntries($boardId) {
|
||||
try {
|
||||
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
||||
} catch (NoPermissionException $e) {
|
||||
\OC::$server->getLogger()->error('Unable to check permission for a previously obtained board ' . $boardId, ['exception' => $e]);
|
||||
return [];
|
||||
}
|
||||
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
||||
$cards = $this->cardMapper->findCalendarEntries($boardId);
|
||||
foreach ($cards as $card) {
|
||||
$this->enrich($card);
|
||||
@@ -267,9 +257,9 @@ class CardService {
|
||||
* @param $title
|
||||
* @param $stackId
|
||||
* @param $type
|
||||
* @param $owner
|
||||
* @param $description
|
||||
* @param $order
|
||||
* @param $description
|
||||
* @param $owner
|
||||
* @param $duedate
|
||||
* @return \OCP\AppFramework\Db\Entity
|
||||
* @throws StatusException
|
||||
@@ -278,7 +268,7 @@ class CardService {
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
* @throws BadRequestException
|
||||
*/
|
||||
public function update($id, $title, $stackId, $type, $owner, $description = '', $order = 0, $duedate = null, $deletedAt = null, $archived = null) {
|
||||
public function update($id, $title, $stackId, $type, $order = 0, $description = '', $owner, $duedate = null, $deletedAt = null, $archived = null) {
|
||||
if (is_numeric($id) === false) {
|
||||
throw new BadRequestException('card id must be a number');
|
||||
}
|
||||
@@ -607,13 +597,27 @@ class CardService {
|
||||
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
|
||||
}
|
||||
|
||||
public function getCardUrl($cardId) {
|
||||
$boardId = $this->cardMapper->findBoardId($cardId);
|
||||
/**
|
||||
*
|
||||
* @return array
|
||||
* @throws \OCA\Deck\NoPermissionException
|
||||
* @throws BadRequestException
|
||||
*/
|
||||
public function findAllWithDue($userId) {
|
||||
$cards = $this->cardMapper->findAllWithDue($userId);
|
||||
|
||||
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . "#/board/$boardId/card/$cardId";
|
||||
return $cards;
|
||||
}
|
||||
|
||||
public function getRedirectUrlForCard($cardId) {
|
||||
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . "card/$cardId";
|
||||
/**
|
||||
*
|
||||
* @return array
|
||||
* @throws \OCA\Deck\NoPermissionException
|
||||
* @throws BadRequestException
|
||||
*/
|
||||
public function findAssignedCards($userId) {
|
||||
$cards = $this->cardMapper->findAssignedCards($userId);
|
||||
|
||||
return $cards;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
use OCA\Circles\CirclesManager;
|
||||
use OCA\Circles\Model\Circle;
|
||||
use OCA\Circles\Model\Member;
|
||||
use OCA\Circles\Model\Probes\CircleProbe;
|
||||
use OCA\Circles\Api\v1\Circles;
|
||||
use OCP\App\IAppManager;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Wrapper around circles app API since it is not in a public namespace so we need to make sure that
|
||||
@@ -44,66 +40,24 @@ class CirclesService {
|
||||
$this->circlesEnabled = $appManager->isEnabledForUser('circles');
|
||||
}
|
||||
|
||||
public function isCirclesEnabled(): bool {
|
||||
return $this->circlesEnabled;
|
||||
}
|
||||
|
||||
public function getCircle(string $circleId): ?Circle {
|
||||
public function getCircle($circleId) {
|
||||
if (!$this->circlesEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// Enforce current user condition since we always want the full list of members
|
||||
/** @var CirclesManager $circlesManager */
|
||||
$circlesManager = \OC::$server->get(CirclesManager::class);
|
||||
$circlesManager->startSuperSession();
|
||||
return $circlesManager->getCircle($circleId);
|
||||
} catch (Throwable $e) {
|
||||
}
|
||||
return null;
|
||||
return \OCA\Circles\Api\v1\Circles::detailsCircle($circleId, true);
|
||||
}
|
||||
|
||||
public function isUserInCircle(string $circleId, string $userId): bool {
|
||||
public function isUserInCircle($circleId, $userId): bool {
|
||||
if (!$this->circlesEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var CirclesManager $circlesManager */
|
||||
$circlesManager = \OC::$server->get(CirclesManager::class);
|
||||
$federatedUser = $circlesManager->getFederatedUser($userId, Member::TYPE_USER);
|
||||
$circlesManager->startSession($federatedUser);
|
||||
$circle = $circlesManager->getCircle($circleId);
|
||||
$member = $circle->getInitiator();
|
||||
return $member !== null && $member->getLevel() >= Member::LEVEL_MEMBER;
|
||||
} catch (Throwable $e) {
|
||||
$member = \OCA\Circles\Api\v1\Circles::getMember($circleId, $userId, 1, true);
|
||||
return $member->getLevel() >= Circles::LEVEL_MEMBER;
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userId
|
||||
* @return string[] circle single ids
|
||||
*/
|
||||
public function getUserCircles(string $userId): array {
|
||||
if (!$this->circlesEnabled) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var CirclesManager $circlesManager */
|
||||
$circlesManager = \OC::$server->get(CirclesManager::class);
|
||||
$federatedUser = $circlesManager->getFederatedUser($userId, Member::TYPE_USER);
|
||||
$circlesManager->startSession($federatedUser);
|
||||
$probe = new CircleProbe();
|
||||
$probe->mustBeMember();
|
||||
return array_map(function (Circle $circle) {
|
||||
return $circle->getSingleId();
|
||||
}, $circlesManager->getCircles($probe));
|
||||
} catch (Throwable $e) {
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,29 +46,20 @@ class ConfigService {
|
||||
|
||||
public function __construct(
|
||||
IConfig $config,
|
||||
IGroupManager $groupManager
|
||||
IGroupManager $groupManager,
|
||||
IUserSession $userSession
|
||||
) {
|
||||
// Session is required here in order to make the tests properly inject the userId later on
|
||||
$this->userId = $userSession->getUser() ? $userSession->getUser()->getUID() : null;
|
||||
$this->groupManager = $groupManager;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function getUserId() {
|
||||
if (!$this->userId) {
|
||||
$user = \OC::$server->get(IUserSession::class)->getUser();
|
||||
$this->userId = $user ? $user->getUID() : null;
|
||||
}
|
||||
return $this->userId;
|
||||
}
|
||||
|
||||
public function getAll(): array {
|
||||
if ($this->getUserId() === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$data = [
|
||||
'calendar' => $this->isCalendarEnabled()
|
||||
];
|
||||
if ($this->groupManager->isAdmin($this->getUserId())) {
|
||||
if ($this->groupManager->isAdmin($this->userId)) {
|
||||
$data['groupLimit'] = $this->get('groupLimit');
|
||||
}
|
||||
return $data;
|
||||
@@ -79,47 +70,43 @@ class ConfigService {
|
||||
[$scope] = explode(':', $key, 2);
|
||||
switch ($scope) {
|
||||
case 'groupLimit':
|
||||
if ($this->getUserId() === null || !$this->groupManager->isAdmin($this->getUserId())) {
|
||||
if (!$this->groupManager->isAdmin($this->userId)) {
|
||||
throw new NoPermissionException('You must be admin to get the group limit');
|
||||
}
|
||||
return $this->getGroupLimit();
|
||||
case 'calendar':
|
||||
if ($this->getUserId() === null) {
|
||||
if ($this->userId === null) {
|
||||
return false;
|
||||
}
|
||||
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', true);
|
||||
return (bool)$this->config->getUserValue($this->userId, Application::APP_ID, 'calendar', true);
|
||||
}
|
||||
}
|
||||
|
||||
public function isCalendarEnabled(int $boardId = null): bool {
|
||||
if ($this->getUserId() === null) {
|
||||
if ($this->userId === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', true);
|
||||
$defaultState = (bool)$this->config->getUserValue($this->userId, Application::APP_ID, 'calendar', true);
|
||||
if ($boardId === null) {
|
||||
return $defaultState;
|
||||
}
|
||||
|
||||
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':calendar', $defaultState);
|
||||
return (bool)$this->config->getUserValue($this->userId, Application::APP_ID, 'board:' . $boardId . ':calendar', $defaultState);
|
||||
}
|
||||
|
||||
public function set($key, $value) {
|
||||
if ($this->getUserId() === null) {
|
||||
throw new NoPermissionException('Must be logged in to set user config');
|
||||
}
|
||||
|
||||
$result = null;
|
||||
[$scope] = explode(':', $key, 2);
|
||||
switch ($scope) {
|
||||
case 'groupLimit':
|
||||
if (!$this->groupManager->isAdmin($this->getUserId())) {
|
||||
if (!$this->groupManager->isAdmin($this->userId)) {
|
||||
throw new NoPermissionException('You must be admin to set the group limit');
|
||||
}
|
||||
$result = $this->setGroupLimit($value);
|
||||
break;
|
||||
case 'calendar':
|
||||
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'calendar', (string)$value);
|
||||
$this->config->setUserValue($this->userId, Application::APP_ID, 'calendar', (int)$value);
|
||||
$result = $value;
|
||||
break;
|
||||
case 'board':
|
||||
@@ -127,7 +114,7 @@ class ConfigService {
|
||||
if ($boardConfigKey === 'notify-due' && !in_array($value, [self::SETTING_BOARD_NOTIFICATION_DUE_ALL, self::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED, self::SETTING_BOARD_NOTIFICATION_DUE_OFF], true)) {
|
||||
throw new BadRequestException('Board notification option must be one of: off, assigned, all');
|
||||
}
|
||||
$this->config->setUserValue($this->getUserId(), Application::APP_ID, $key, (string)$value);
|
||||
$this->config->setUserValue($this->userId, Application::APP_ID, $key, $value);
|
||||
$result = $value;
|
||||
}
|
||||
return $result;
|
||||
@@ -169,10 +156,6 @@ class ConfigService {
|
||||
}
|
||||
|
||||
public function getAttachmentFolder(): string {
|
||||
if ($this->getUserId() === null) {
|
||||
throw new NoPermissionException('Must be logged in get the attachment folder');
|
||||
}
|
||||
|
||||
return $this->config->getUserValue($this->getUserId(), 'deck', 'attachment_folder', '/Deck');
|
||||
return $this->config->getUserValue($this->userId, 'deck', 'attachment_folder', '/Deck');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class FullTextSearchService {
|
||||
|
||||
/** @var CardMapper */
|
||||
private $cardMapper;
|
||||
|
||||
|
||||
public function __construct(
|
||||
BoardMapper $boardMapper, StackMapper $stackMapper, CardMapper $cardMapper
|
||||
) {
|
||||
@@ -187,6 +187,6 @@ class FullTextSearchService {
|
||||
* @return Board[]
|
||||
*/
|
||||
private function getBoardsFromUser(string $userId): array {
|
||||
return $this->boardMapper->findAllByUser($userId, null, null, null);
|
||||
return $this->boardMapper->findAllByUser($userId, null, null, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ class OverviewService {
|
||||
$service = $this;
|
||||
|
||||
if (count($userBoard->getAcl()) === 0) {
|
||||
// private board: get cards with due date
|
||||
// get cards with due date
|
||||
$findCards[] = array_map(static function ($card) use ($service, $userBoard, $userId) {
|
||||
$service->enrich($card, $userId);
|
||||
$cardData = $card->jsonSerialize();
|
||||
@@ -122,13 +122,13 @@ class OverviewService {
|
||||
return $cardData;
|
||||
}, $this->cardMapper->findAllWithDue($userBoard->getId()));
|
||||
} else {
|
||||
// shared board: get all my assigned or unassigned cards
|
||||
// get assigned cards
|
||||
$findCards[] = array_map(static function ($card) use ($service, $userBoard, $userId) {
|
||||
$service->enrich($card, $userId);
|
||||
$cardData = $card->jsonSerialize();
|
||||
$cardData['boardId'] = $userBoard->getId();
|
||||
return $cardData;
|
||||
}, $this->cardMapper->findToMeOrNotAssignedCards($userBoard->getId(), $userId));
|
||||
}, $this->cardMapper->findAssignedCards($userBoard->getId(), $userId));
|
||||
}
|
||||
}
|
||||
return $findCards;
|
||||
|
||||
@@ -33,7 +33,6 @@ use OCA\Deck\Db\IPermissionMapper;
|
||||
use OCA\Deck\Db\User;
|
||||
use OCA\Deck\NoPermissionException;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\IConfig;
|
||||
use OCP\IGroupManager;
|
||||
@@ -43,8 +42,6 @@ use OCP\Share\IManager;
|
||||
|
||||
class PermissionService {
|
||||
|
||||
/** @var CirclesService */
|
||||
private $circlesService;
|
||||
/** @var BoardMapper */
|
||||
private $boardMapper;
|
||||
/** @var AclMapper */
|
||||
@@ -64,11 +61,11 @@ class PermissionService {
|
||||
/** @var array */
|
||||
private $users = [];
|
||||
|
||||
private $circlesEnabled = false;
|
||||
private $boardCache;
|
||||
|
||||
public function __construct(
|
||||
ILogger $logger,
|
||||
CirclesService $circlesService,
|
||||
AclMapper $aclMapper,
|
||||
BoardMapper $boardMapper,
|
||||
IUserManager $userManager,
|
||||
@@ -77,7 +74,6 @@ class PermissionService {
|
||||
IConfig $config,
|
||||
$userId
|
||||
) {
|
||||
$this->circlesService = $circlesService;
|
||||
$this->aclMapper = $aclMapper;
|
||||
$this->boardMapper = $boardMapper;
|
||||
$this->logger = $logger;
|
||||
@@ -88,6 +84,9 @@ class PermissionService {
|
||||
$this->userId = $userId;
|
||||
|
||||
$this->boardCache = new CappedMemoryCache();
|
||||
|
||||
$this->circlesEnabled = \OC::$server->getAppManager()->isEnabledForUser('circles') &&
|
||||
(version_compare(\OC::$server->getAppManager()->getAppVersion('circles'), '0.17.1') >= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +116,7 @@ class PermissionService {
|
||||
*/
|
||||
public function matchPermissions(Board $board) {
|
||||
$owner = $this->userIsBoardOwner($board->getId());
|
||||
$acls = $board->getAcl() ?? [];
|
||||
$acls = $board->getAcl();
|
||||
return [
|
||||
Acl::PERMISSION_READ => $owner || $this->userCan($acls, Acl::PERMISSION_READ),
|
||||
Acl::PERMISSION_EDIT => $owner || $this->userCan($acls, Acl::PERMISSION_EDIT),
|
||||
@@ -155,7 +154,7 @@ class PermissionService {
|
||||
}
|
||||
|
||||
try {
|
||||
$acls = $this->getBoard($boardId)->getAcl() ?? [];
|
||||
$acls = $this->getBoard($boardId)->getAcl();
|
||||
$result = $this->userCan($acls, $permission, $userId);
|
||||
if ($result) {
|
||||
return true;
|
||||
@@ -211,11 +210,10 @@ class PermissionService {
|
||||
return $acl->getPermission($permission);
|
||||
}
|
||||
|
||||
if ($this->circlesService->isCirclesEnabled() && $acl->getType() === Acl::PERMISSION_TYPE_CIRCLE) {
|
||||
if ($this->circlesEnabled && $acl->getType() === Acl::PERMISSION_TYPE_CIRCLE) {
|
||||
try {
|
||||
if ($this->circlesService->isUserInCircle($acl->getParticipant(), $userId) && $acl->getPermission($permission)) {
|
||||
return true;
|
||||
}
|
||||
$member = \OCA\Circles\Api\v1\Circles::getMember($acl->getParticipant(), $this->userId, 1, true);
|
||||
return $member->getLevel() >= Member::LEVEL_MEMBER && $acl->getPermission($permission);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->info('Member not found in circle that was accessed. This should not happen.');
|
||||
}
|
||||
@@ -243,7 +241,6 @@ class PermissionService {
|
||||
if (array_key_exists((string) $boardId, $this->users) && !$refresh) {
|
||||
return $this->users[(string) $boardId];
|
||||
}
|
||||
|
||||
try {
|
||||
$board = $this->boardMapper->find($boardId);
|
||||
} catch (DoesNotExistException $e) {
|
||||
@@ -281,19 +278,15 @@ class PermissionService {
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->circlesService->isCirclesEnabled() && $acl->getType() === Acl::PERMISSION_TYPE_CIRCLE) {
|
||||
if ($this->circlesEnabled && $acl->getType() === Acl::PERMISSION_TYPE_CIRCLE) {
|
||||
try {
|
||||
$circle = $this->circlesService->getCircle($acl->getParticipant());
|
||||
$circle = \OCA\Circles\Api\v1\Circles::detailsCircle($acl->getParticipant(), true);
|
||||
if ($circle === null) {
|
||||
$this->logger->info('No circle found for acl rule ' . $acl->getId());
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($circle->getInheritedMembers() as $member) {
|
||||
if ($member->getUserType() !== 1 || $member->getLevel() < Member::LEVEL_MEMBER) {
|
||||
// deck currently only supports user members in circles
|
||||
continue;
|
||||
}
|
||||
foreach ($circle->getMembers() as $member) {
|
||||
$user = $this->userManager->get($member->getUserId());
|
||||
if ($user === null) {
|
||||
$this->logger->info('No user found for circle member ' . $member->getUserId());
|
||||
@@ -311,10 +304,6 @@ class PermissionService {
|
||||
}
|
||||
|
||||
public function canCreate() {
|
||||
if ($this->userId === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$groups = $this->getGroupLimitList();
|
||||
if (count($groups) === 0) {
|
||||
return true;
|
||||
@@ -335,13 +324,4 @@ class PermissionService {
|
||||
}
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a different user than the current one, e.g. when no user is available in occ
|
||||
*
|
||||
* @param string $userId
|
||||
*/
|
||||
public function setUserId(string $userId): void {
|
||||
$this->userId = $userId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,19 +90,25 @@ class SearchService {
|
||||
}
|
||||
|
||||
public function searchBoards(string $term, ?int $limit, ?int $cursor): array {
|
||||
$boards = $this->boardService->getUserBoards(null, true, $cursor, mb_strtolower($term));
|
||||
|
||||
$boards = $this->boardService->getUserBoards();
|
||||
// get boards that have a lastmodified date which is lower than the cursor
|
||||
// and which match the search term
|
||||
$filteredBoards = array_filter($boards, static function (Board $board) use ($term, $cursor) {
|
||||
return (
|
||||
($cursor === null || $board->getLastModified() < $cursor)
|
||||
&& mb_stripos(mb_strtolower($board->getTitle()), mb_strtolower($term)) > -1
|
||||
);
|
||||
});
|
||||
// sort the boards, recently modified first
|
||||
usort($boards, function ($boardA, $boardB) {
|
||||
usort($filteredBoards, function ($boardA, $boardB) {
|
||||
$ta = $boardA->getLastModified();
|
||||
$tb = $boardB->getLastModified();
|
||||
return $ta === $tb
|
||||
? 0
|
||||
: ($ta > $tb ? -1 : 1);
|
||||
});
|
||||
|
||||
// limit the number of results
|
||||
return array_slice($boards, 0, $limit);
|
||||
return array_slice($filteredBoards, 0, $limit);
|
||||
}
|
||||
|
||||
public function searchComments(string $term, ?int $limit = null, ?int $cursor = null): array {
|
||||
|
||||
@@ -35,7 +35,6 @@ use OCA\Deck\Db\ChangeHelper;
|
||||
use OCA\Deck\Db\LabelMapper;
|
||||
use OCA\Deck\Db\Stack;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\NoPermissionException;
|
||||
use OCA\Deck\StatusException;
|
||||
|
||||
class StackService {
|
||||
@@ -143,12 +142,7 @@ class StackService {
|
||||
}
|
||||
|
||||
public function findCalendarEntries($boardId) {
|
||||
try {
|
||||
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ);
|
||||
} catch (NoPermissionException $e) {
|
||||
\OC::$server->getLogger()->error('Unable to check permission for a previously obtained board ' . $boardId, ['exception' => $e]);
|
||||
return [];
|
||||
}
|
||||
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ);
|
||||
return $this->stackMapper->findAll($boardId);
|
||||
}
|
||||
|
||||
@@ -181,7 +175,6 @@ class StackService {
|
||||
if (array_key_exists($card->id, $labels)) {
|
||||
$cards[$cardIndex]->setLabels($labels[$card->id]);
|
||||
}
|
||||
$cards[$cardIndex]->setAttachmentCount($this->attachmentService->count($card->getId()));
|
||||
}
|
||||
$stacks[$stackIndex]->setCards($cards);
|
||||
}
|
||||
|
||||
42195
package-lock.json
generated
42195
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
92
package.json
92
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "deck",
|
||||
"description": "",
|
||||
"version": "1.6.2",
|
||||
"version": "1.4.8",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Julius Härtl",
|
||||
@@ -29,35 +29,34 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@juliushaertl/vue-richtext": "^1.0.1",
|
||||
"@nextcloud/auth": "^1.3.0",
|
||||
"@nextcloud/axios": "^1.7.0",
|
||||
"@nextcloud/dialogs": "^3.1.2",
|
||||
"@nextcloud/event-bus": "^2.1.1",
|
||||
"@nextcloud/files": "^2.1.0",
|
||||
"@nextcloud/initial-state": "^1.2.1",
|
||||
"@nextcloud/axios": "^1.6.0",
|
||||
"@nextcloud/dialogs": "^3.1.1",
|
||||
"@nextcloud/event-bus": "^1.2.0",
|
||||
"@nextcloud/files": "^1.1.0",
|
||||
"@nextcloud/initial-state": "^1.2.0",
|
||||
"@nextcloud/l10n": "^1.4.1",
|
||||
"@nextcloud/moment": "^1.1.1",
|
||||
"@nextcloud/router": "^2.0.0",
|
||||
"@nextcloud/vue": "^4.2.0",
|
||||
"@nextcloud/vue-dashboard": "^2.0.1",
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"dompurify": "^2.3.3",
|
||||
"@nextcloud/router": "^1.2.0",
|
||||
"@nextcloud/vue": "^3.8.0",
|
||||
"@nextcloud/vue-dashboard": "^1.1.0",
|
||||
"blueimp-md5": "^2.18.0",
|
||||
"dompurify": "^2.2.7",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^12.2.0",
|
||||
"markdown-it": "^12.0.4",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"markdown-it-link-attributes": "^3.0.0",
|
||||
"moment": "^2.29.1",
|
||||
"nextcloud-vue-collections": "^0.9.0",
|
||||
"p-queue": "^6.6.2",
|
||||
"url-search-params-polyfill": "^8.1.1",
|
||||
"vue": "^2.6.14",
|
||||
"vue": "^2.6.12",
|
||||
"vue-at": "^2.5.0-beta.2",
|
||||
"vue-click-outside": "^1.1.0",
|
||||
"vue-easymde": "^2.0.0",
|
||||
"vue-easymde": "^1.4.0",
|
||||
"vue-infinite-loading": "^2.4.5",
|
||||
"vue-router": "^3.5.3",
|
||||
"vue-router": "^3.5.1",
|
||||
"vue-smooth-dnd": "^0.8.1",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
@@ -66,20 +65,57 @@
|
||||
"extends @nextcloud/browserslist-config"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nextcloud/babel-config": "^1.0.0",
|
||||
"@nextcloud/browserslist-config": "^2.2.0",
|
||||
"@nextcloud/eslint-config": "^6.1.0",
|
||||
"@nextcloud/stylelint-config": "^1.0.0-beta.0",
|
||||
"@nextcloud/webpack-vue-config": "^4.1.2",
|
||||
"@relative-ci/agent": "^3.0.0",
|
||||
"@vue/test-utils": "^1.2.2",
|
||||
"jest": "^27.3.1",
|
||||
"@babel/core": "^7.13.14",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/preset-env": "^7.13.12",
|
||||
"@nextcloud/browserslist-config": "^1.0.0",
|
||||
"@nextcloud/eslint-config": "^2.2.0",
|
||||
"@nextcloud/eslint-plugin": "^1.5.0",
|
||||
"@nextcloud/webpack-vue-config": "^1.4.1",
|
||||
"@relative-ci/agent": "^1.5.0",
|
||||
"@vue/test-utils": "^1.1.3",
|
||||
"acorn": "^8.1.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
"babel-loader": "^8.2.2",
|
||||
"css-loader": "^4.3.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"eslint-plugin-standard": "^4.1.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"jest": "^26.6.3",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"vue-jest": "^3.0.7"
|
||||
"minimist": "^1.2.5",
|
||||
"node-sass": "^4.14.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass-loader": "^10.1.1",
|
||||
"style-loader": "^1.3.0",
|
||||
"stylelint": "^13.12.0",
|
||||
"stylelint-config-recommended": "^4.0.0",
|
||||
"stylelint-config-recommended-scss": "^4.2.0",
|
||||
"stylelint-scss": "^3.19.0",
|
||||
"stylelint-webpack-plugin": "^2.1.1",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-jest": "^3.0.7",
|
||||
"vue-loader": "^15.9.6",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-merge": "^5.7.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.0.0",
|
||||
"npm": "^7.0.0"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
|
||||
10
src/App.vue
10
src/App.vue
@@ -59,11 +59,6 @@ export default {
|
||||
Content,
|
||||
AppContent,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
boardApi,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addButton: {
|
||||
@@ -117,6 +112,11 @@ export default {
|
||||
this.$router.push({ name: 'board' })
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
boardApi,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -72,10 +72,10 @@
|
||||
</div>
|
||||
<div v-else id="modal-inner">
|
||||
<EmptyContent v-if="creating" icon="icon-loading">
|
||||
{{ t('deck', 'Creating the new card …') }}
|
||||
{{ t('deck', 'Creating the new card…') }}
|
||||
</EmptyContent>
|
||||
<EmptyContent v-else-if="created" icon="icon-checkmark">
|
||||
{{ t('deck', 'Card "{card}" was added to "{board}"', { card: pendingTitle, board: selectedBoard.title }) }}
|
||||
{{ t('deck', '"{card}" was added to "{board}"', { card: pendingTitle, board: selectedBoard.title }) }}
|
||||
<template #desc>
|
||||
<button class="primary" @click="openNewCard">
|
||||
{{ t('deck', 'Open card') }}
|
||||
@@ -168,7 +168,6 @@ export default {
|
||||
|
||||
},
|
||||
close() {
|
||||
this.$emit('close')
|
||||
this.$root.$emit('close')
|
||||
},
|
||||
async select() {
|
||||
|
||||
@@ -79,7 +79,7 @@ export default {
|
||||
parameters.after.name = moment(dateTime).format('L LTS')
|
||||
}
|
||||
|
||||
Object.keys(parameters).forEach(function(key, index) {
|
||||
Object.keys(parameters).map(function(key, index) {
|
||||
const { type } = parameters[key]
|
||||
switch (type) {
|
||||
case 'highlight':
|
||||
|
||||
@@ -84,7 +84,7 @@ export default {
|
||||
params.append('object_id', '' + this.objectId)
|
||||
params.append('limit', ACTIVITY_FETCH_LIMIT)
|
||||
|
||||
const response = await axios.get(generateOcsUrl(`apps/activity/api/v2/activity/${this.filter}`) + '?' + params)
|
||||
const response = await axios.get(generateOcsUrl('apps/activity/api/v2/activity') + this.filter + '?' + params)
|
||||
let activities = response.data.ocs.data
|
||||
if (this.filter === 'deck') {
|
||||
// We need to manually filter activities here, since currently we use two different types and there is no way
|
||||
|
||||
@@ -25,12 +25,6 @@
|
||||
<div v-if="overviewName" class="board-title">
|
||||
<div class="board-bullet icon-calendar-dark" />
|
||||
<h2>{{ overviewName }}</h2>
|
||||
<Actions>
|
||||
<ActionButton icon="icon-add" @click="clickShowAddCardModel">
|
||||
{{ t('deck', 'Add card') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
<CardCreateDialog v-if="showAddCardModal" @close="clickHideAddCardModel" />
|
||||
</div>
|
||||
<div v-else-if="board" class="board-title">
|
||||
<div :style="{backgroundColor: '#' + board.color}" class="board-bullet" />
|
||||
@@ -76,108 +70,110 @@
|
||||
<ActionButton v-else icon="icon-filter" />
|
||||
</Actions>
|
||||
|
||||
<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>
|
||||
<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 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>
|
||||
<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 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>
|
||||
|
||||
<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>
|
||||
@@ -210,12 +206,11 @@
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import { Actions, ActionButton, Popover, Avatar } from '@nextcloud/vue'
|
||||
import labelStyle from '../mixins/labelStyle'
|
||||
import CardCreateDialog from '../CardCreateDialog'
|
||||
|
||||
export default {
|
||||
name: 'Controls',
|
||||
components: {
|
||||
Actions, ActionButton, Popover, Avatar, CardCreateDialog,
|
||||
Actions, ActionButton, Popover, Avatar,
|
||||
},
|
||||
mixins: [labelStyle],
|
||||
props: {
|
||||
@@ -238,7 +233,6 @@ export default {
|
||||
showArchived: false,
|
||||
isAddStackVisible: false,
|
||||
filter: { tags: [], users: [], due: '', unassigned: false },
|
||||
showAddCardModal: false,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -324,12 +318,6 @@ export default {
|
||||
this.$store.dispatch('setFilter', { ...filterReset })
|
||||
this.filter = filterReset
|
||||
},
|
||||
clickShowAddCardModel() {
|
||||
this.showAddCardModal = true
|
||||
},
|
||||
clickHideAddCardModel() {
|
||||
this.showAddCardModal = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -53,9 +53,6 @@
|
||||
<ActionCheckbox v-if="canManage" :checked="acl.permissionManage" @change="clickManageAcl(acl)">
|
||||
{{ t('deck', 'Can manage') }}
|
||||
</ActionCheckbox>
|
||||
<ActionCheckbox v-if="acl.type === 0 && isCurrentUser(board.owner.uid)" :checked="acl.owner" @change="clickTransferOwner(acl.participant.uid)">
|
||||
{{ t('deck', 'Owner') }}
|
||||
</ActionCheckbox>
|
||||
<ActionButton v-if="canManage" icon="icon-delete" @click="clickDeleteAcl(acl)">
|
||||
{{ t('deck', 'Delete') }}
|
||||
</ActionButton>
|
||||
@@ -75,7 +72,7 @@ import { Avatar, Multiselect, Actions, ActionButton, ActionCheckbox } from '@nex
|
||||
import { CollectionList } from 'nextcloud-vue-collections'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
export default {
|
||||
@@ -100,7 +97,6 @@ export default {
|
||||
isSearching: false,
|
||||
addAcl: null,
|
||||
addAclForAPI: null,
|
||||
newOwner: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -198,38 +194,6 @@ export default {
|
||||
clickDeleteAcl(acl) {
|
||||
this.$store.dispatch('deleteAclFromCurrentBoard', acl)
|
||||
},
|
||||
clickTransferOwner(newOwner) {
|
||||
OC.dialogs.confirmDestructive(
|
||||
t('deck', 'Are you sure you want to transfer the board {title} for {user} ?', { title: this.board.title, user: newOwner }),
|
||||
t('deck', 'Transfer the board.'),
|
||||
{
|
||||
type: OC.dialogs.YES_NO_BUTTONS,
|
||||
confirm: t('deck', 'Transfer'),
|
||||
confirmClasses: 'error',
|
||||
cancel: t('deck', 'Cancel'),
|
||||
},
|
||||
async (result) => {
|
||||
if (result) {
|
||||
try {
|
||||
this.isLoading = true
|
||||
await this.$store.dispatch('transferOwnership', {
|
||||
boardId: this.board.id,
|
||||
newOwner,
|
||||
})
|
||||
const successMessage = t('deck', 'Transfer the board for {user} successfully', { user: newOwner })
|
||||
showSuccess(successMessage)
|
||||
this.$router.push({ name: 'main' })
|
||||
} catch (e) {
|
||||
const errorMessage = t('deck', 'Failed to transfer the board for {user}', { user: newOwner.user })
|
||||
showError(errorMessage)
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -264,14 +264,12 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@use 'sass:math';
|
||||
|
||||
@import './../../css/variables';
|
||||
|
||||
.stack {
|
||||
width: $stack-width + $stack-spacing*3;
|
||||
margin-left: math.div($stack-spacing, 2);
|
||||
margin-right: math.div($stack-spacing, 2);
|
||||
margin-left: $stack-spacing/2;
|
||||
margin-right: $stack-spacing/2;
|
||||
}
|
||||
|
||||
.stack__header {
|
||||
|
||||
@@ -43,22 +43,24 @@
|
||||
|
||||
<li v-if="addLabel" class="editing">
|
||||
<!-- New Tag -->
|
||||
<form class="label-form" @submit.prevent="clickAddLabel">
|
||||
<ColorPicker class="color-picker-wrapper" :value="'#' + addLabelObj.color" @input="updateColor">
|
||||
<div :style="{ backgroundColor: '#' + addLabelObj.color }" class="color0 icon-colorpicker" />
|
||||
</ColorPicker>
|
||||
<input v-model="addLabelObj.title" type="text">
|
||||
<input v-tooltip="{content: missingDataLabel, show: !addLabelObjValidated, trigger: 'manual' }"
|
||||
:disabled="!addLabelObjValidated"
|
||||
type="submit"
|
||||
value=""
|
||||
class="icon-confirm">
|
||||
<Actions>
|
||||
<ActionButton icon="icon-close" @click="addLabel=false">
|
||||
{{ t('deck', 'Cancel') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
</form>
|
||||
<template>
|
||||
<form class="label-form" @submit.prevent="clickAddLabel">
|
||||
<ColorPicker class="color-picker-wrapper" :value="'#' + addLabelObj.color" @input="updateColor">
|
||||
<div :style="{ backgroundColor: '#' + addLabelObj.color }" class="color0 icon-colorpicker" />
|
||||
</ColorPicker>
|
||||
<input v-model="addLabelObj.title" type="text">
|
||||
<input v-tooltip="{content: missingDataLabel, show: !addLabelObjValidated, trigger: 'manual' }"
|
||||
:disabled="!addLabelObjValidated"
|
||||
type="submit"
|
||||
value=""
|
||||
class="icon-confirm">
|
||||
<Actions>
|
||||
<ActionButton icon="icon-close" @click="addLabel=false">
|
||||
{{ t('deck', 'Cancel') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
</form>
|
||||
</template>
|
||||
</li>
|
||||
<button v-if="canManage && !isArchived" @click="clickShowAddLabel()">
|
||||
<span class="icon-add" />{{ t('deck', 'Add a new tag') }}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<Actions v-if="selectable">
|
||||
<ActionButton icon="icon-confirm" @click="$emit('select-attachment', attachment)">
|
||||
<ActionButton icon="icon-confirm" @click="$emit('selectAttachment', attachment)">
|
||||
{{ t('deck', 'Add this attachment') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
@@ -89,10 +89,10 @@
|
||||
{{ t('deck', 'Remove attachment') }}
|
||||
</ActionButton>
|
||||
|
||||
<ActionButton v-if="!attachment.extendedData.fileid && attachment.deletedAt === 0" icon="icon-delete" @click="$emit('delete-attachment', attachment)">
|
||||
<ActionButton v-if="!attachment.extendedData.fileid && attachment.deletedAt === 0" icon="icon-delete" @click="$emit('deleteAttachment', attachment)">
|
||||
{{ t('deck', 'Delete Attachment') }}
|
||||
</ActionButton>
|
||||
<ActionButton v-else-if="!attachment.extendedData.fileid" icon="icon-history" @click="$emit('restore-attachment', attachment)">
|
||||
<ActionButton v-else-if="!attachment.extendedData.fileid" icon="icon-history" @click="$emit('restoreAttachment', attachment)">
|
||||
{{ t('deck', 'Restore Attachment') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
@@ -222,13 +222,13 @@ export default {
|
||||
},
|
||||
shareFromFiles() {
|
||||
picker.pick()
|
||||
.then(async (path) => {
|
||||
.then(async(path) => {
|
||||
console.debug(`path ${path} selected for sharing`)
|
||||
if (!path.startsWith('/')) {
|
||||
throw new Error(t('files', 'Invalid path selected'))
|
||||
}
|
||||
|
||||
axios.post(generateOcsUrl('apps/files_sharing/api/v1/shares'), {
|
||||
axios.post(generateOcsUrl('apps/files_sharing/api/v1', 2) + 'shares', {
|
||||
path,
|
||||
shareType: 12,
|
||||
shareWith: '' + this.cardId,
|
||||
@@ -245,7 +245,7 @@ export default {
|
||||
},
|
||||
showViewer(attachment) {
|
||||
if (attachment.extendedData.fileid && window.OCA.Viewer.availableHandlers.map(handler => handler.mimes).flat().includes(attachment.extendedData.mimetype)) {
|
||||
window.OCA.Viewer.open({ path: attachment.extendedData.path })
|
||||
window.OCA.Viewer.open(attachment.extendedData.path)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -205,8 +205,6 @@ export default {
|
||||
max-width: calc(100% - #{$modal-padding*2});
|
||||
padding: 0 14px;
|
||||
max-height: 100%;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
&::v-deep {
|
||||
.app-sidebar-header {
|
||||
position: sticky;
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
<AttachmentList
|
||||
:card-id="card.id"
|
||||
:removable="true"
|
||||
@delete-attachment="deleteAttachment"
|
||||
@restore-attachment="restoreAttachment" />
|
||||
@deleteAttachment="deleteAttachment"
|
||||
@restoreAttachment="restoreAttachment" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -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()">
|
||||
@@ -177,48 +176,6 @@ export default {
|
||||
stringify: this.stringify,
|
||||
parse: this.parse,
|
||||
},
|
||||
shortcuts: [
|
||||
{
|
||||
text: t('deck', 'Today'),
|
||||
onClick() {
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate())
|
||||
date.setHours(23)
|
||||
date.setMinutes(59)
|
||||
return date
|
||||
},
|
||||
},
|
||||
{
|
||||
text: t('deck', 'Tomorrow'),
|
||||
onClick() {
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate() + 1)
|
||||
date.setHours(23)
|
||||
date.setMinutes(59)
|
||||
return date
|
||||
},
|
||||
},
|
||||
{
|
||||
text: t('deck', 'Next week'),
|
||||
onClick() {
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate() + 7)
|
||||
date.setHours(23)
|
||||
date.setMinutes(59)
|
||||
return date
|
||||
},
|
||||
},
|
||||
{
|
||||
text: t('deck', 'Next month'),
|
||||
onClick() {
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate() + 30)
|
||||
date.setHours(23)
|
||||
date.setMinutes(59)
|
||||
return date
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -359,14 +316,6 @@ export default {
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.section-wrapper::v-deep .mx-datepicker-main.mx-datepicker-popup {
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
.section-wrapper::v-deep .mx-datepicker-main.mx-datepicker-popup.mx-datepicker-sidebar {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.section-wrapper {
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
|
||||
@@ -28,11 +28,11 @@
|
||||
:members="members"
|
||||
name-key="displayname"
|
||||
:tab-select="true">
|
||||
<template #item="s">
|
||||
<template v-slot:item="s">
|
||||
<Avatar class="atwho-li--avatar" :user="s.item.uid" :size="24" />
|
||||
<span class="atwho-li--name" v-text="s.item.displayname" />
|
||||
</template>
|
||||
<template #embeddedItem="scope">
|
||||
<template v-slot:embeddedItem="scope">
|
||||
<span>
|
||||
<UserBubble v-if="scope.current.uid"
|
||||
:data-mention-id="scope.current.uid"
|
||||
|
||||
@@ -19,45 +19,47 @@
|
||||
</div>
|
||||
</div>
|
||||
<li v-else class="comment">
|
||||
<div class="comment--header">
|
||||
<Avatar :user="comment.actorId" />
|
||||
<span class="has-tooltip username">
|
||||
{{ comment.actorDisplayName }}
|
||||
</span>
|
||||
<Actions v-show="!edit" :force-menu="true">
|
||||
<ActionButton icon="icon-reply" :close-after-click="true" @click="replyTo()">
|
||||
{{ t('deck', 'Reply') }}
|
||||
</ActionButton>
|
||||
<ActionButton v-if="canEdit"
|
||||
icon="icon-rename"
|
||||
:close-after-click="true"
|
||||
@click="showUpdateForm()">
|
||||
{{ t('deck', 'Update') }}
|
||||
</ActionButton>
|
||||
<ActionButton v-if="canEdit"
|
||||
icon="icon-delete"
|
||||
:close-after-click="true"
|
||||
@click="deleteComment()">
|
||||
{{ t('deck', 'Delete') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
<Actions v-if="edit">
|
||||
<ActionButton icon="icon-close" @click="hideUpdateForm" />
|
||||
</Actions>
|
||||
<div class="spacer" />
|
||||
<div class="timestamp">
|
||||
{{ relativeDate(comment.creationDateTime) }}
|
||||
<template>
|
||||
<div class="comment--header">
|
||||
<Avatar :user="comment.actorId" />
|
||||
<span class="has-tooltip username">
|
||||
{{ comment.actorDisplayName }}
|
||||
</span>
|
||||
<Actions v-show="!edit" :force-menu="true">
|
||||
<ActionButton icon="icon-reply" :close-after-click="true" @click="replyTo()">
|
||||
{{ t('deck', 'Reply') }}
|
||||
</ActionButton>
|
||||
<ActionButton v-if="canEdit"
|
||||
icon="icon-rename"
|
||||
:close-after-click="true"
|
||||
@click="showUpdateForm()">
|
||||
{{ t('deck', 'Update') }}
|
||||
</ActionButton>
|
||||
<ActionButton v-if="canEdit"
|
||||
icon="icon-delete"
|
||||
:close-after-click="true"
|
||||
@click="deleteComment()">
|
||||
{{ t('deck', 'Delete') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
<Actions v-if="edit">
|
||||
<ActionButton icon="icon-close" @click="hideUpdateForm" />
|
||||
</Actions>
|
||||
<div class="spacer" />
|
||||
<div class="timestamp">
|
||||
{{ relativeDate(comment.creationDateTime) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CommentItem v-if="comment.replyTo" :reply="true" :comment="comment.replyTo" />
|
||||
<div v-show="!edit" ref="richTextElement">
|
||||
<RichText
|
||||
class="comment--content"
|
||||
:text="richText(comment)"
|
||||
:arguments="richArgs(comment)"
|
||||
:autolink="true" />
|
||||
</div>
|
||||
<CommentForm v-if="edit" v-model="commentMsg" @submit="updateComment" />
|
||||
<CommentItem v-if="comment.replyTo" :reply="true" :comment="comment.replyTo" />
|
||||
<div v-show="!edit" ref="richTextElement">
|
||||
<RichText
|
||||
class="comment--content"
|
||||
:text="richText(comment)"
|
||||
:arguments="richArgs(comment)"
|
||||
:autolink="true" />
|
||||
</div>
|
||||
<CommentForm v-if="edit" v-model="commentMsg" @submit="updateComment" />
|
||||
</template>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
ref="markdownEditor"
|
||||
v-model="description"
|
||||
:configs="mdeConfig"
|
||||
@update:modelValue="updateDescription"
|
||||
@input="updateDescription"
|
||||
@blur="saveDescription" />
|
||||
|
||||
<Modal v-if="modalShow" :title="t('deck', 'Choose attachment')" @close="modalShow=false">
|
||||
@@ -66,7 +66,7 @@
|
||||
<AttachmentList
|
||||
:card-id="card.id"
|
||||
:selectable="true"
|
||||
@select-attachment="addAttachment" />
|
||||
@selectAttachment="addAttachment" />
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
@@ -75,7 +75,6 @@
|
||||
<script>
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import MarkdownItTaskLists from 'markdown-it-task-lists'
|
||||
import MarkdownItLinkAttributes from 'markdown-it-link-attributes'
|
||||
import AttachmentList from './AttachmentList'
|
||||
import { Actions, ActionButton, Modal } from '@nextcloud/vue'
|
||||
import { formatFileSize } from '@nextcloud/files'
|
||||
@@ -87,13 +86,6 @@ const markdownIt = new MarkdownIt({
|
||||
})
|
||||
markdownIt.use(MarkdownItTaskLists, { enabled: true, label: true, labelAfter: true })
|
||||
|
||||
markdownIt.use(MarkdownItLinkAttributes, {
|
||||
attrs: {
|
||||
target: '_blank',
|
||||
rel: 'noreferrer noopener',
|
||||
},
|
||||
})
|
||||
|
||||
export default {
|
||||
name: 'Description',
|
||||
components: {
|
||||
@@ -231,7 +223,7 @@ export default {
|
||||
updateDescription() {
|
||||
this.descriptionLastEdit = Date.now()
|
||||
clearTimeout(this.descriptionSaveTimeout)
|
||||
this.descriptionSaveTimeout = setTimeout(async () => {
|
||||
this.descriptionSaveTimeout = setTimeout(async() => {
|
||||
await this.saveDescription()
|
||||
}, 2500)
|
||||
},
|
||||
@@ -323,12 +315,6 @@ h5 {
|
||||
border-left: 1px solid var(--color-main-text);
|
||||
}
|
||||
|
||||
.CodeMirror-selected,
|
||||
.CodeMirror-line::selection, .CodeMirror-line>span::selection, .CodeMirror-line>span>span::selection {
|
||||
background: var(--color-primary-element) !important;
|
||||
color: var(--color-primary-text) !important;
|
||||
}
|
||||
|
||||
.editor-preview,
|
||||
.editor-statusbar {
|
||||
display: none;
|
||||
|
||||
@@ -35,14 +35,14 @@
|
||||
<Avatar v-if="user.type === 1"
|
||||
:user="user.participant.uid"
|
||||
:display-name="user.participant.displayname"
|
||||
:tooltip-message="user.participant.displayname + ' ' + t('deck', '(Group)')"
|
||||
:tooltip-message="user.participant.displayname + ' ' + t('deck', '(group)')"
|
||||
:is-no-user="true"
|
||||
:disable-="true"
|
||||
:size="32" />
|
||||
<Avatar v-if="user.type === 7"
|
||||
:user="user.participant.uid"
|
||||
:display-name="user.participant.displayname"
|
||||
:tooltip-message="user.participant.displayname + ' ' + t('deck', '(Circle)')"
|
||||
:tooltip-message="user.participant.displayname + ' ' + t('deck', '(circle)')"
|
||||
:is-no-user="true"
|
||||
:disable-="true"
|
||||
:size="32" />
|
||||
@@ -53,19 +53,6 @@
|
||||
<PopoverMenu :menu="popover" />
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div class="avatar-print-list">
|
||||
<div v-for="user in avatarUsers" :key="user.id" class="avatar-print-list-item">
|
||||
<Avatar
|
||||
class="avatar-print-list-avatar"
|
||||
:user="user.participant.uid"
|
||||
:display-name="user.participant.displayname"
|
||||
:disable-menu="true"
|
||||
:is-no-user="user.type !== 0"
|
||||
:size="24" />
|
||||
{{ user.participant.displayname }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -85,7 +72,7 @@ export default {
|
||||
props: {
|
||||
users: {
|
||||
type: Array,
|
||||
default: () => ([]),
|
||||
default: () => { return {} },
|
||||
},
|
||||
},
|
||||
data() {
|
||||
@@ -129,15 +116,6 @@ export default {
|
||||
}),
|
||||
]
|
||||
},
|
||||
avatarUsers() {
|
||||
if (!this.users) {
|
||||
return []
|
||||
}
|
||||
|
||||
return this.users.filter((user) => {
|
||||
return [0, 1, 7].includes(user.type)
|
||||
})
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
togglePopover() {
|
||||
@@ -154,7 +132,7 @@ export default {
|
||||
margin-top: 5px;
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
::v-deep .popovermenu {
|
||||
/deep/ .popovermenu {
|
||||
margin-right: -4px;
|
||||
img {
|
||||
padding: 0;
|
||||
@@ -174,7 +152,7 @@ export default {
|
||||
padding-right: $avatar-offset;
|
||||
flex-direction: row-reverse;
|
||||
.avatardiv,
|
||||
::v-deep .avatardiv {
|
||||
/deep/ .avatardiv {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
box-sizing: content-box !important;
|
||||
@@ -189,7 +167,7 @@ export default {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
&:hover div:nth-child(n+2) ::v-deep .avatardiv {
|
||||
&:hover div:nth-child(n+2) /deep/ .avatardiv {
|
||||
margin-right: 1px;
|
||||
}
|
||||
}
|
||||
@@ -198,26 +176,4 @@ export default {
|
||||
display: block;
|
||||
margin: 40px -6px;
|
||||
}
|
||||
|
||||
.avatar-print-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.avatar-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.avatar-print-list-item {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.avatar-print-list {
|
||||
display: block;
|
||||
padding-top: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
<AvatarList :users="card.assignedUsers" />
|
||||
|
||||
<CardMenu class="card-menu" :card="card" />
|
||||
<CardMenu :card="card" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@@ -68,7 +68,7 @@ export default {
|
||||
if (this.card.commentsUnread > 0) {
|
||||
return t('deck', '{count} comments, {unread} unread', {
|
||||
count: this.card.commentsCount,
|
||||
unread: this.card.commentsUnread,
|
||||
unread: this.card.commentsUnread
|
||||
})
|
||||
}
|
||||
return null
|
||||
@@ -150,15 +150,4 @@ export default {
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.badges {
|
||||
align-items: flex-start;
|
||||
max-height: none !important;
|
||||
}
|
||||
|
||||
.card-menu {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -66,11 +66,7 @@
|
||||
:placeholder="t('deck', 'Select a list')"
|
||||
:options="stacksFromBoard"
|
||||
:max-height="100"
|
||||
label="title">
|
||||
<span slot="noOptions">
|
||||
{{ t('deck', 'List is empty') }}
|
||||
</span>
|
||||
</Multiselect>
|
||||
label="title" />
|
||||
|
||||
<button :disabled="!isBoardAndStackChoosen" class="primary" @click="moveCard">
|
||||
{{ t('deck', 'Move card') }}
|
||||
@@ -135,7 +131,7 @@ export default {
|
||||
},
|
||||
activeBoards() {
|
||||
return this.$store.getters.boards.filter((item) => item.deletedAt === 0 && item.archived === false)
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openCard() {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<template>
|
||||
<div v-if="card" class="duedate">
|
||||
<transition name="zoom">
|
||||
<div v-if="card.duedate" :class="dueIcon" :title="absoluteDate">
|
||||
<div v-if="card.duedate" :class="dueIcon">
|
||||
<span>{{ relativeDate }}</span>
|
||||
</div>
|
||||
</transition>
|
||||
@@ -62,14 +62,14 @@ export default {
|
||||
}
|
||||
return moment(this.card.duedate).fromNow()
|
||||
},
|
||||
absoluteDate() {
|
||||
return moment(this.card.duedate).format('L')
|
||||
dueDateTooltip() {
|
||||
return moment(this.card.duedate).format('LLLL')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss" coped>
|
||||
.icon.due {
|
||||
background-position: 4px center;
|
||||
border-radius: 3px;
|
||||
@@ -105,7 +105,6 @@ export default {
|
||||
padding: 3px 4px;
|
||||
}
|
||||
|
||||
&::before,
|
||||
span {
|
||||
margin-left: 20px;
|
||||
white-space: nowrap;
|
||||
@@ -113,18 +112,4 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
.icon.due {
|
||||
background-color: transparent !important;
|
||||
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: attr(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -163,7 +163,7 @@ export default {
|
||||
if (this.isAdmin) {
|
||||
this.groupLimit = this.$store.getters.config('groupLimit')
|
||||
this.groupLimitDisabled = false
|
||||
axios.get(generateOcsUrl('cloud/groups')).then((response) => {
|
||||
axios.get(generateOcsUrl('cloud', 2) + 'groups').then((response) => {
|
||||
this.groups = response.data.ocs.data.groups.reduce((obj, item) => {
|
||||
obj.push({
|
||||
id: item,
|
||||
|
||||
@@ -39,9 +39,6 @@
|
||||
<script>
|
||||
import { ColorPicker, ActionButton, Actions, AppNavigationItem } from '@nextcloud/vue'
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function randomColor() {
|
||||
let randomHexColor = ((1 << 24) * Math.random() | 0).toString(16)
|
||||
while (randomHexColor.length < 6) {
|
||||
|
||||
@@ -125,10 +125,7 @@
|
||||
<div :style="{ backgroundColor: getColor }" class="color0 icon-colorpicker app-navigation-entry-bullet" />
|
||||
</ColorPicker>
|
||||
<form @submit.prevent.stop="applyEdit">
|
||||
<input v-model="editTitle"
|
||||
v-focus
|
||||
type="text"
|
||||
required>
|
||||
<input v-model="editTitle" type="text" required>
|
||||
<input type="submit" value="" class="icon-confirm">
|
||||
<Actions><ActionButton icon="icon-close" @click.stop.prevent="cancelEdit" /></Actions>
|
||||
</form>
|
||||
@@ -152,9 +149,6 @@ export default {
|
||||
directives: {
|
||||
ClickOutside,
|
||||
},
|
||||
inject: [
|
||||
'boardApi',
|
||||
],
|
||||
props: {
|
||||
board: {
|
||||
type: Object,
|
||||
@@ -309,6 +303,9 @@ export default {
|
||||
this.updateDueSetting = null
|
||||
},
|
||||
},
|
||||
inject: [
|
||||
'boardApi',
|
||||
],
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
boardsSorted() {
|
||||
return [...this.boards].sort((a, b) => a.title.localeCompare(b.title))
|
||||
return [...this.boards].sort((a, b) => (a.title < b.title) ? -1 : 1)
|
||||
},
|
||||
collapsible() {
|
||||
return this.boards.length > 0
|
||||
|
||||
@@ -63,15 +63,10 @@ import { Actions, ActionButton } from '@nextcloud/vue'
|
||||
|
||||
const createCancelToken = () => axios.CancelToken.source()
|
||||
|
||||
/**
|
||||
* @param root0
|
||||
* @param root0.query
|
||||
* @param root0.cursor
|
||||
*/
|
||||
function search({ query, cursor }) {
|
||||
const cancelToken = createCancelToken()
|
||||
|
||||
const request = async () => axios.get(generateOcsUrl('apps/deck/api/v1.0/search'), {
|
||||
const request = async() => axios.get(generateOcsUrl('apps/deck/api/v1.0', 2) + '/search', {
|
||||
cancelToken: cancelToken.token,
|
||||
params: {
|
||||
term: query,
|
||||
|
||||
@@ -21,11 +21,6 @@ ul {
|
||||
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
|
||||
.task-list-item {
|
||||
margin-left: -20px;
|
||||
list-style-type: none;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
@@ -72,7 +67,7 @@ img {
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
margin: 0px 3px 0px 0px;
|
||||
margin: 0px 10px 0px 0px;
|
||||
line-height: 10px;
|
||||
font-size: 10px;
|
||||
display: inline-block;
|
||||
|
||||
@@ -25,8 +25,9 @@ const buildSelector = (selector, propsData = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const container = document.createElement('div')
|
||||
document.getElementById('body-user').append(container)
|
||||
const ComponentVM = new Vue({
|
||||
render: (h) => h(selector, propsData),
|
||||
const View = Vue.extend(selector)
|
||||
const ComponentVM = new View({
|
||||
propsData,
|
||||
}).$mount(container)
|
||||
ComponentVM.$root.$on('close', () => {
|
||||
ComponentVM.$el.remove()
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
import Vue from 'vue'
|
||||
|
||||
import BoardSelector from './BoardSelector'
|
||||
import CardSelector from './CardSelector'
|
||||
import './../css/collections.css'
|
||||
import FileSharingPicker from './views/FileSharingPicker'
|
||||
import { buildSelector } from './helpers/selector'
|
||||
@@ -43,19 +45,13 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
window.OCP.Collaboration.registerType('deck', {
|
||||
action: () => {
|
||||
const BoardSelector = () => import('./BoardSelector')
|
||||
return buildSelector(BoardSelector)
|
||||
},
|
||||
action: () => buildSelector(BoardSelector),
|
||||
typeString: t('deck', 'Link to a board'),
|
||||
typeIconClass: 'icon-deck',
|
||||
})
|
||||
|
||||
window.OCP.Collaboration.registerType('deck-card', {
|
||||
action: () => {
|
||||
const CardSelector = () => import('./CardSelector')
|
||||
return buildSelector(CardSelector)
|
||||
},
|
||||
action: () => buildSelector(CardSelector),
|
||||
typeString: t('deck', 'Link to a card'),
|
||||
typeIconClass: 'icon-deck',
|
||||
})
|
||||
|
||||
@@ -44,39 +44,15 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
window.OCA.Talk.registerMessageAction({
|
||||
label: t('deck', 'Create a card'),
|
||||
icon: 'icon-deck',
|
||||
async callback({ message: { message, messageParameters, actorDisplayName }, metadata: { name: conversationName, token: conversationToken } }) {
|
||||
const parsedMessage = message.replace(/{[a-z0-9-_]+}/gi, function(parameter) {
|
||||
const parameterName = parameter.substr(1, parameter.length - 2)
|
||||
|
||||
if (messageParameters[parameterName]) {
|
||||
if (messageParameters[parameterName].type === 'file' && messageParameters[parameterName].path) {
|
||||
return messageParameters[parameterName].path
|
||||
}
|
||||
if (messageParameters[parameterName].type === 'user' || messageParameters[parameterName].type === 'call') {
|
||||
return '@' + messageParameters[parameterName].name
|
||||
}
|
||||
if (messageParameters[parameterName].name) {
|
||||
return messageParameters[parameterName].name
|
||||
}
|
||||
}
|
||||
|
||||
// Do not replace so insert with curly braces again
|
||||
return parameter
|
||||
})
|
||||
|
||||
const shortenedMessageCandidate = parsedMessage.replace(/^(.{255}[^\s]*).*/, '$1')
|
||||
const shortenedMessage = shortenedMessageCandidate === '' ? parsedMessage.substr(0, 255) : shortenedMessageCandidate
|
||||
async callback({ message: { message, actorDisplayName }, metadata: { name: conversationName, token: conversationToken } }) {
|
||||
const shortenedMessageCandidate = message.replace(/^(.{255}[^\s]*).*/, '$1')
|
||||
const shortenedMessage = shortenedMessageCandidate === '' ? message.substr(0, 255) : shortenedMessageCandidate
|
||||
try {
|
||||
await buildSelector(CardCreateDialog, {
|
||||
props: {
|
||||
title: shortenedMessage,
|
||||
description: parsedMessage + '\n\n' + '['
|
||||
+ t('deck', 'Message from {author} in {conversationName}', {
|
||||
author: actorDisplayName,
|
||||
conversationName,
|
||||
})
|
||||
+ '](' + window.location.protocol + '//' + window.location.host + generateUrl('/call/' + conversationToken) + ')',
|
||||
},
|
||||
title: shortenedMessage,
|
||||
description: message + '\n\n' + '['
|
||||
+ t('deck', 'Message from {author} in {conversationName}', { author: actorDisplayName, conversationName })
|
||||
+ '](' + generateUrl('/call/' + conversationToken) + ')',
|
||||
})
|
||||
} catch (e) {
|
||||
console.debug('Card creation dialog was canceled')
|
||||
|
||||
@@ -120,7 +120,7 @@ if (!window.OCA.Deck) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} CardRichObject
|
||||
* @typedef {Object} CardRichObject
|
||||
* @property {string} id
|
||||
* @property {string} name
|
||||
* @property {string} boardname
|
||||
@@ -135,14 +135,13 @@ if (!window.OCA.Deck) {
|
||||
|
||||
/**
|
||||
* Frontend message API for adding actions to talk messages.
|
||||
*
|
||||
* @param {*} Object the wrapping object.
|
||||
* @param {string} label the action label.
|
||||
* @param {String} label the action label.
|
||||
* @param {registerActionCallback} callback the callback function. This function will receive
|
||||
* the card as a parameter and be triggered by a click on the
|
||||
* action. The card parameter will be of the format of a rich object string
|
||||
* type "deck-card"
|
||||
* @param {string} icon the action label. E.g. "icon-reply"
|
||||
* @param {String} icon the action label. E.g. "icon-reply"
|
||||
*/
|
||||
window.OCA.Deck.registerCardAction = ({ label, callback, icon }) => {
|
||||
const cardAction = {
|
||||
|
||||
@@ -47,7 +47,7 @@ export default {
|
||||
bodyFormData.append('cardId', this.cardId)
|
||||
bodyFormData.append('type', type)
|
||||
bodyFormData.append('file', file)
|
||||
await queue.add(async () => {
|
||||
await queue.add(async() => {
|
||||
try {
|
||||
await this.$store.dispatch('createAttachment', {
|
||||
cardId: this.cardId,
|
||||
|
||||
@@ -84,7 +84,7 @@ export default {
|
||||
},
|
||||
colorIsValid(hex) {
|
||||
|
||||
const re = /[A-Fa-f0-9]{6}/
|
||||
const re = new RegExp('[A-Fa-f0-9]{6}')
|
||||
if (re.test(hex)) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
/**
|
||||
* Board model
|
||||
*
|
||||
* @typedef {object} Board
|
||||
* @property {string} title
|
||||
* @typedef {Object} Board
|
||||
* @property {String} title
|
||||
* @property {boolean} archived
|
||||
* @property {number} shared 1 (shared) or 0 (not shared)
|
||||
*/
|
||||
@@ -32,8 +32,8 @@
|
||||
/**
|
||||
* Stack model
|
||||
*
|
||||
* @typedef {object} Stack
|
||||
* @property {string} title
|
||||
* @typedef {Object} Stack
|
||||
* @property {String} title
|
||||
* @property {number} boardId
|
||||
* @property {number} order
|
||||
*/
|
||||
@@ -41,15 +41,15 @@
|
||||
/**
|
||||
* Card model
|
||||
*
|
||||
* @typedef {object} Card
|
||||
* @property {string} title
|
||||
* @typedef {Object} Card
|
||||
* @property {String} title
|
||||
* @property {boolean} archived
|
||||
* @property {number} order
|
||||
*/
|
||||
/**
|
||||
* Label model
|
||||
*
|
||||
* @typedef {object} Label
|
||||
* @property {string} title
|
||||
* @property {string} color
|
||||
* @typedef {Object} Label
|
||||
* @property {String} title
|
||||
* @property {String} color
|
||||
*/
|
||||
|
||||
@@ -38,7 +38,7 @@ export class BoardApi {
|
||||
* Updates a board.
|
||||
*
|
||||
* @param {Board} board the board object to update
|
||||
* @return {Promise}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
updateBoard(board) {
|
||||
return axios.put(this.url(`/boards/${board.id}`), board)
|
||||
@@ -57,13 +57,13 @@ export class BoardApi {
|
||||
|
||||
/**
|
||||
* Creates a new board.
|
||||
*
|
||||
* @typedef {object} BoardCreateObject
|
||||
* @typedef {Object} BoardCreateObject
|
||||
* @property {string} title
|
||||
* @property {string} color
|
||||
*
|
||||
* @param {BoardCreateObject} boardData The board data to send.
|
||||
* color the hexadecimal color value formated /[0-9A-F]{6}/i
|
||||
* @return {Promise}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
createBoard(boardData) {
|
||||
return axios.post(this.url('/boards'), boardData)
|
||||
|
||||
@@ -31,7 +31,7 @@ export class CommentApi {
|
||||
}
|
||||
|
||||
async loadComments({ cardId, limit, offset }) {
|
||||
const api = await axios.get(generateOcsUrl(`apps/deck/api/v1.0/cards/${cardId}/comments`), {
|
||||
const api = await axios.get(generateOcsUrl('apps/deck/api/v1.0/cards', 2) + `${cardId}/comments`, {
|
||||
params: { limit, offset },
|
||||
headers: { 'OCS-APIRequest': 'true' },
|
||||
})
|
||||
@@ -39,7 +39,7 @@ export class CommentApi {
|
||||
}
|
||||
|
||||
async createComment({ cardId, comment, replyTo }) {
|
||||
const api = await axios.post(generateOcsUrl(`apps/deck/api/v1.0/cards/${cardId}/comments`), {
|
||||
const api = await axios.post(generateOcsUrl('apps/deck/api/v1.0/cards', 2) + `${cardId}/comments`, {
|
||||
message: `${comment}`,
|
||||
parentId: replyTo ? replyTo.id : null,
|
||||
})
|
||||
@@ -47,14 +47,14 @@ export class CommentApi {
|
||||
}
|
||||
|
||||
async updateComment({ cardId, id, comment }) {
|
||||
const api = await axios.put(generateOcsUrl(`apps/deck/api/v1.0/cards/${cardId}/comments/${id}`), {
|
||||
const api = await axios.put(generateOcsUrl('apps/deck/api/v1.0/cards', 2) + `${cardId}/comments/${id}`, {
|
||||
message: `${comment}`,
|
||||
})
|
||||
return api.data.ocs.data
|
||||
}
|
||||
|
||||
async deleteComment({ cardId, id }) {
|
||||
const api = await axios.delete(generateOcsUrl(`apps/deck/api/v1.0/cards/${cardId}/comments/${id}`))
|
||||
const api = await axios.delete(generateOcsUrl('apps/deck/api/v1.0/cards', 2) + `${cardId}/comments/${id}`)
|
||||
return api.data.ocs.data
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import { generateOcsUrl } from '@nextcloud/router'
|
||||
export class OverviewApi {
|
||||
|
||||
url(url) {
|
||||
return generateOcsUrl(`apps/deck/api/v1.0/${url}`)
|
||||
return generateOcsUrl('apps/deck/api/v1.0') + url
|
||||
}
|
||||
|
||||
get(filter) {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import axios from '@nextcloud/axios'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
|
||||
const shareUrl = generateOcsUrl('apps/files_sharing/api/v1/shares')
|
||||
const shareUrl = generateOcsUrl('apps/files_sharing/api/v1', 2) + 'shares'
|
||||
|
||||
const createShare = async function({ path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label }) {
|
||||
try {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user