Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c934124b88 | ||
|
|
b7bba814e3 | ||
|
|
8c285968af | ||
|
|
b91a5d9adf | ||
|
|
17b281072f | ||
|
|
d47555e3c2 | ||
|
|
3f98f47256 | ||
|
|
00a4d63344 | ||
|
|
82f37df16d | ||
|
|
dfd3e276ab | ||
|
|
3a0a3b49af | ||
|
|
6d7f81fa7e | ||
|
|
154e3e4587 | ||
|
|
372363d254 | ||
|
|
61e6990ab3 | ||
|
|
714298a94e | ||
|
|
a0d05211ff | ||
|
|
4d454de228 | ||
|
|
fcb70b884c | ||
|
|
33c9ea802a | ||
|
|
ab29ac7a5c | ||
|
|
7a1519da43 | ||
|
|
7a839925ea | ||
|
|
60fa211334 | ||
|
|
e318e604b5 | ||
|
|
6ea482a10b | ||
|
|
a9b37db504 | ||
|
|
7f3c074958 | ||
|
|
2e4de2919f | ||
|
|
addcb9c1f6 | ||
|
|
6b3e7f0d25 | ||
|
|
6dbfbc43da | ||
|
|
3e3efa85ea | ||
|
|
5dc94bfa64 | ||
|
|
04c6b6ba36 | ||
|
|
85d0c8b1b9 | ||
|
|
f32472eb24 | ||
|
|
99bed5c616 | ||
|
|
166d9cf860 | ||
|
|
16994c5d36 | ||
|
|
8d849b3d30 | ||
|
|
884eef5407 | ||
|
|
96d8de06da | ||
|
|
d6bab95376 | ||
|
|
a983142ad6 | ||
|
|
98c878b201 | ||
|
|
b2e983fde6 | ||
|
|
724ec5751f | ||
|
|
17618301bc |
+80
-82
@@ -580,7 +580,7 @@ steps:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: before-static-build
|
||||
image: techknowlogick/xgo:latest
|
||||
image: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
pull: always
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
@@ -589,7 +589,7 @@ steps:
|
||||
depends_on: [ fetch-tags, mage ]
|
||||
|
||||
- name: static-build-windows
|
||||
image: techknowlogick/xgo:latest
|
||||
image: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
@@ -605,7 +605,7 @@ steps:
|
||||
- frontend-build
|
||||
|
||||
- name: static-build-linux
|
||||
image: techknowlogick/xgo:latest
|
||||
image: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
@@ -621,7 +621,7 @@ steps:
|
||||
- frontend-build
|
||||
|
||||
- name: static-build-darwin
|
||||
image: techknowlogick/xgo:latest
|
||||
image: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
@@ -647,7 +647,7 @@ steps:
|
||||
- ./mage-static release:compress
|
||||
|
||||
- name: after-build-static
|
||||
image: techknowlogick/xgo:latest
|
||||
image: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
pull: always
|
||||
depends_on:
|
||||
- after-build-compress
|
||||
@@ -676,13 +676,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
strip_prefix: dist/zip/
|
||||
source: dist/zip/*
|
||||
@@ -698,13 +698,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
strip_prefix: dist/zip/
|
||||
source: dist/zip/*
|
||||
@@ -750,13 +750,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
strip_prefix: dist/os-packages/
|
||||
source: dist/os-packages/*
|
||||
@@ -772,13 +772,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
strip_prefix: dist/os-packages/
|
||||
source: dist/os-packages/*
|
||||
@@ -837,7 +837,6 @@ steps:
|
||||
repo: vikunja/vikunja
|
||||
tags: unstable
|
||||
platforms:
|
||||
- linux/386
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
@@ -869,7 +868,6 @@ steps:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/vikunja
|
||||
platforms:
|
||||
- linux/386
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
@@ -931,13 +929,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
source: frontend/vikunja-frontend-unstable.zip
|
||||
target: /
|
||||
@@ -990,13 +988,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
source: frontend/vikunja-frontend-${DRONE_TAG##v}.zip
|
||||
target: /
|
||||
@@ -1095,8 +1093,8 @@ steps:
|
||||
# settings:
|
||||
# restore: true
|
||||
# bucket: kolaente.dev-drone-dependency-cache
|
||||
# endpoint: https://s3.fr-par.scw.cloud
|
||||
# region: fr-par
|
||||
# endpoint: https://fsn1.your-objectstorage.com
|
||||
# region: fsn1
|
||||
# path_style: true
|
||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||
# mount:
|
||||
@@ -1133,8 +1131,8 @@ steps:
|
||||
# settings:
|
||||
# rebuild: true
|
||||
# bucket: kolaente.dev-drone-dependency-cache
|
||||
# endpoint: https://s3.fr-par.scw.cloud
|
||||
# region: fr-par
|
||||
# endpoint: https://fsn1.your-objectstorage.com
|
||||
# region: fsn1
|
||||
# path_style: true
|
||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||
# mount:
|
||||
@@ -1176,8 +1174,8 @@ steps:
|
||||
# settings:
|
||||
# restore: true
|
||||
# bucket: kolaente.dev-drone-dependency-cache
|
||||
# endpoint: https://s3.fr-par.scw.cloud
|
||||
# region: fr-par
|
||||
# endpoint: https://fsn1.your-objectstorage.com
|
||||
# region: fsn1
|
||||
# path_style: true
|
||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||
# mount:
|
||||
@@ -1200,30 +1198,30 @@ steps:
|
||||
- unzip vikunja-frontend-$$VERSION.zip -d frontend
|
||||
- sed -i 's/\\/api\\/v1//g' frontend/index.html
|
||||
- ./bumpp.sh
|
||||
- yarn install
|
||||
- cat package.json
|
||||
- yarn dist --linux --windows
|
||||
|
||||
# - name: rebuild-cache
|
||||
# image: meltwater/drone-cache:dev
|
||||
# pull: true
|
||||
# environment:
|
||||
# AWS_ACCESS_KEY_ID:
|
||||
# from_secret: cache_aws_access_key_id
|
||||
# AWS_SECRET_ACCESS_KEY:
|
||||
# from_secret: cache_aws_secret_access_key
|
||||
# settings:
|
||||
# rebuild: true
|
||||
# bucket: kolaente.dev-drone-dependency-cache
|
||||
# endpoint: https://s3.fr-par.scw.cloud
|
||||
# region: fr-par
|
||||
# path_style: true
|
||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||
# mount:
|
||||
# - '.cache'
|
||||
# depends_on:
|
||||
# - build
|
||||
- corepack enable && pnpm config set store-dir .cache/pnpm
|
||||
- pnpm install --fetch-timeout 100000
|
||||
- pnpm dist --linux --windows
|
||||
|
||||
# - name: rebuild-cache
|
||||
# image: meltwater/drone-cache:dev
|
||||
# pull: true
|
||||
# environment:
|
||||
# AWS_ACCESS_KEY_ID:
|
||||
# from_secret: cache_aws_access_key_id
|
||||
# AWS_SECRET_ACCESS_KEY:
|
||||
# from_secret: cache_aws_secret_access_key
|
||||
# settings:
|
||||
# rebuild: true
|
||||
# bucket: kolaente.dev-drone-dependency-cache
|
||||
# endpoint: https://fsn1.your-objectstorage.com
|
||||
# region: fsn1
|
||||
# path_style: true
|
||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||
# mount:
|
||||
# - '.cache'
|
||||
# depends_on:
|
||||
# - build
|
||||
|
||||
- name: rename-unstable
|
||||
image: bash
|
||||
pull: true
|
||||
@@ -1241,13 +1239,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
strip_prefix: desktop/dist/
|
||||
source: desktop/dist/Vikunja-Desktop*
|
||||
@@ -1263,13 +1261,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
strip_prefix: desktop/dist/
|
||||
source: desktop/dist/*
|
||||
@@ -1298,9 +1296,9 @@ steps:
|
||||
# - name: build
|
||||
# environment:
|
||||
# ACCESS_KEY:
|
||||
# from_secret: aws_access_key_id
|
||||
# from_secret: hetzner_access_key_id
|
||||
# SECRET_KEY:
|
||||
# from_secret: aws_secret_access_key
|
||||
# from_secret: hetzner_secret_access_key
|
||||
# commands:
|
||||
# - git fetch --tags
|
||||
# - export VERSION=${DRONE_TAG##v}
|
||||
@@ -1313,9 +1311,9 @@ steps:
|
||||
# - sed -i '' "s/\$${version}/$$VERSION/g" package.json
|
||||
# - yarn install
|
||||
# - yarn dist --mac
|
||||
# - mc config host add scw-fr-par https://s3.fr-par.scw.cloud $ACCESS_KEY $SECRET_KEY --api S3v4
|
||||
# - mc cp ./dist/*.dmg scw-fr-par/vikunja-releases/desktop/$VERSION/
|
||||
# - mc cp ./dist/*.dmg.blockmap scw-fr-par/vikunja-releases/desktop/$VERSION/
|
||||
# - mc config host add scw-fsn1 https://fsn1.your-objectstorage.com $ACCESS_KEY $SECRET_KEY --api S3v4
|
||||
# - mc cp ./dist/*.dmg scw-fsn1/vikunja/desktop/$VERSION/
|
||||
# - mc cp ./dist/*.dmg.blockmap scw-fsn1/vikunja/desktop/$VERSION/
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
@@ -1352,6 +1350,6 @@ steps:
|
||||
- failure
|
||||
---
|
||||
kind: signature
|
||||
hmac: eff6e40dc763fa1395078daaabcbef2aaa74fe0a36818a87e89015dbc4e9dae4
|
||||
hmac: 02fb3097f3e50facb2579de5c3b94e513b97e535a049a28ddb67f85e494d65b1
|
||||
|
||||
...
|
||||
|
||||
@@ -7,6 +7,79 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
All releases can be found on https://code.vikunja.io/vikunja/releases.
|
||||
|
||||
## [0.24.6] - 2024-12-22
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* *(export)* Update only current user export file id
|
||||
|
||||
### Features
|
||||
|
||||
* Use hetzner object storage for releases ([3f98f47](3f98f47256628ac559a947f6c9541c0bf03001b6))
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
* Release preparation ([17b2810](17b281072f095730a0fbb28458d713fa883f43ec))
|
||||
* Sign drone config ([b91a5d9](b91a5d9adfb7144126f5824224970ad4015dcd6a))
|
||||
* Do not generate config yaml ([8c28596](8c285968afff7ebd087ce824b52b0a9f28a8ec20))
|
||||
|
||||
## [0.24.5] - 2024-11-21
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* *(attachments)* Check permissions when accessing all attachments
|
||||
* *(saved filters)* Check permissions when accessing tasks of a filter
|
||||
* Pin xgo to 1.22.x ([87b2aac](87b2aaccb8cdcbe1ecb6092951a0bfe224ad7006))
|
||||
* Upgrade xgo ([19b63c8](19b63c86c51f67614b867c75a58cda1774685edd))
|
||||
* Upgrade xgo docker image everywhere ([04b40f8](04b40f8a7dcd01a86ddb8b27596073d1e50f9e97))
|
||||
* *(ci)* Do not build linux 368 docker images
|
||||
* Disable 368 releases ([73db10f](73db10fb02268e07d29842493df55f4d645ac503))
|
||||
- **BREAKING**: disable 368 releases
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
* Sign drone config ([17c4878](17c487875b5771c0971ee8bf030807171de2dddc))
|
||||
* Go mod tidy ([9639025](96390257e0911089ae33a9565e8be7fa954c772c))
|
||||
|
||||
## [0.24.4] - 2024-09-29
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* *(attachment)* Do not use image previews
|
||||
* *(checkbox)* Use sibling css selector instead of has
|
||||
* *(files)* Only use service rootpath for files when the files path is not absolute
|
||||
* *(filters)* Explicitly search in json when using postgres
|
||||
* *(task)* Paginate task comments
|
||||
* *(task)* Do not show close button when the task was not opened via modal
|
||||
* *(task)* Improve task delete modal on mobile
|
||||
* *(test)* Use correct selector for modal header
|
||||
* Partial fix to allow list tasks in ios reminders app (#2717)
|
||||
* *(attachments)* Revert "chore(attachments): refactor building image preview"
|
||||
|
||||
### Dependencies
|
||||
|
||||
* *(deps)* Update desktop lockfile
|
||||
* *(deps)* Update dependency vue to v3.5.7
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dependency vue-i18n to v10.0.2
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dependency vue to v3.5.8
|
||||
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v5.1.0
|
||||
* *(deps)* Update dependency vue-i18n to v10.0.3
|
||||
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v5.2.0
|
||||
* *(deps)* Update dependency @sentry/vue to v8.31.0
|
||||
* *(deps)* Update dependency tailwindcss to v3.4.13
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dependency @sentry/vue to v8.32.0
|
||||
* *(deps)* Update tiptap to v2.7.3
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dependency vue to v3.5.9
|
||||
* *(deps)* Update dependency dompurify to v3.1.7
|
||||
* *(deps)* Update tiptap to v2.7.4
|
||||
* *(deps)* Update dependency vue to v3.5.10
|
||||
* *(deps)* Update dev-dependencies
|
||||
|
||||
## [0.24.3] - 2024-09-20
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ RUN corepack enable && \
|
||||
pnpm install && \
|
||||
pnpm run build
|
||||
|
||||
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.21.x AS apibuilder
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/techknowlogick/xgo:go-1.23.x AS apibuilder
|
||||
|
||||
RUN go install github.com/magefile/mage@latest && \
|
||||
mv /go/bin/mage /usr/local/go/bin
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://drone.kolaente.de/vikunja/vikunja)
|
||||
[](LICENSE)
|
||||
[](https://vikunja.io/docs/installing)
|
||||
[](https://vikunja.io/docs/installing)
|
||||
[](https://hub.docker.com/r/vikunja/vikunja/)
|
||||
[](https://try.vikunja.io/api/v1/docs)
|
||||
[](https://goreportcard.com/report/kolaente.dev/vikunja/vikunja)
|
||||
|
||||
Generated
+1541
-1242
File diff suppressed because it is too large
Load Diff
@@ -148,7 +148,7 @@ describe('Project View Kanban', () => {
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
||||
.contains('Delete')
|
||||
.click()
|
||||
cy.get('.modal-mask .modal-container .modal-content .header')
|
||||
cy.get('.modal-mask .modal-container .modal-content .modal-header')
|
||||
.should('contain', 'Delete the bucket')
|
||||
cy.get('.modal-mask .modal-container .modal-content .actions .button')
|
||||
.contains('Do it!')
|
||||
@@ -251,7 +251,7 @@ describe('Project View Kanban', () => {
|
||||
.should('be.visible')
|
||||
.contains('Delete')
|
||||
.click()
|
||||
cy.get('.modal-mask .modal-container .modal-content .header')
|
||||
cy.get('.modal-mask .modal-container .modal-content .modal-header')
|
||||
.should('contain', 'Delete this task')
|
||||
cy.get('.modal-mask .modal-container .modal-content .actions .button')
|
||||
.contains('Do it!')
|
||||
|
||||
@@ -356,7 +356,7 @@ describe('Task', () => {
|
||||
.should('be.visible')
|
||||
.contains('Delete')
|
||||
.click()
|
||||
cy.get('.modal-mask .modal-container .modal-content .header')
|
||||
cy.get('.modal-mask .modal-container .modal-content .modal-header')
|
||||
.should('contain', 'Delete this task')
|
||||
cy.get('.modal-mask .modal-container .modal-content .actions .button')
|
||||
.contains('Do it!')
|
||||
|
||||
+49
-49
@@ -56,44 +56,44 @@
|
||||
"@fortawesome/vue-fontawesome": "3.0.8",
|
||||
"@github/hotkey": "3.1.1",
|
||||
"@infectoone/vue-ganttastic": "2.3.2",
|
||||
"@intlify/unplugin-vue-i18n": "5.0.0",
|
||||
"@intlify/unplugin-vue-i18n": "5.2.0",
|
||||
"@kyvg/vue3-notification": "3.3.0",
|
||||
"@sentry/tracing": "7.114.0",
|
||||
"@sentry/vue": "8.30.0",
|
||||
"@tiptap/core": "2.7.2",
|
||||
"@tiptap/extension-blockquote": "2.7.2",
|
||||
"@tiptap/extension-bold": "2.7.2",
|
||||
"@tiptap/extension-bullet-list": "2.7.2",
|
||||
"@tiptap/extension-code": "2.7.2",
|
||||
"@tiptap/extension-code-block": "2.7.2",
|
||||
"@tiptap/extension-code-block-lowlight": "2.7.2",
|
||||
"@tiptap/extension-document": "2.7.2",
|
||||
"@tiptap/extension-dropcursor": "2.7.2",
|
||||
"@tiptap/extension-gapcursor": "2.7.2",
|
||||
"@tiptap/extension-hard-break": "2.7.2",
|
||||
"@tiptap/extension-heading": "2.7.2",
|
||||
"@tiptap/extension-history": "2.7.2",
|
||||
"@tiptap/extension-horizontal-rule": "2.7.2",
|
||||
"@tiptap/extension-image": "2.7.2",
|
||||
"@tiptap/extension-italic": "2.7.2",
|
||||
"@tiptap/extension-link": "2.7.2",
|
||||
"@tiptap/extension-list-item": "2.7.2",
|
||||
"@tiptap/extension-ordered-list": "2.7.2",
|
||||
"@tiptap/extension-paragraph": "2.7.2",
|
||||
"@tiptap/extension-placeholder": "2.7.2",
|
||||
"@tiptap/extension-strike": "2.7.2",
|
||||
"@tiptap/extension-table": "2.7.2",
|
||||
"@tiptap/extension-table-cell": "2.7.2",
|
||||
"@tiptap/extension-table-header": "2.7.2",
|
||||
"@tiptap/extension-table-row": "2.7.2",
|
||||
"@tiptap/extension-task-item": "2.7.2",
|
||||
"@tiptap/extension-task-list": "2.7.2",
|
||||
"@tiptap/extension-text": "2.7.2",
|
||||
"@tiptap/extension-typography": "2.7.2",
|
||||
"@tiptap/extension-underline": "2.7.2",
|
||||
"@tiptap/pm": "2.7.2",
|
||||
"@tiptap/suggestion": "2.7.2",
|
||||
"@tiptap/vue-3": "2.7.2",
|
||||
"@sentry/vue": "8.32.0",
|
||||
"@tiptap/core": "2.7.4",
|
||||
"@tiptap/extension-blockquote": "2.7.4",
|
||||
"@tiptap/extension-bold": "2.7.4",
|
||||
"@tiptap/extension-bullet-list": "2.7.4",
|
||||
"@tiptap/extension-code": "2.7.4",
|
||||
"@tiptap/extension-code-block": "2.7.4",
|
||||
"@tiptap/extension-code-block-lowlight": "2.7.4",
|
||||
"@tiptap/extension-document": "2.7.4",
|
||||
"@tiptap/extension-dropcursor": "2.7.4",
|
||||
"@tiptap/extension-gapcursor": "2.7.4",
|
||||
"@tiptap/extension-hard-break": "2.7.4",
|
||||
"@tiptap/extension-heading": "2.7.4",
|
||||
"@tiptap/extension-history": "2.7.4",
|
||||
"@tiptap/extension-horizontal-rule": "2.7.4",
|
||||
"@tiptap/extension-image": "2.7.4",
|
||||
"@tiptap/extension-italic": "2.7.4",
|
||||
"@tiptap/extension-link": "2.7.4",
|
||||
"@tiptap/extension-list-item": "2.7.4",
|
||||
"@tiptap/extension-ordered-list": "2.7.4",
|
||||
"@tiptap/extension-paragraph": "2.7.4",
|
||||
"@tiptap/extension-placeholder": "2.7.4",
|
||||
"@tiptap/extension-strike": "2.7.4",
|
||||
"@tiptap/extension-table": "2.7.4",
|
||||
"@tiptap/extension-table-cell": "2.7.4",
|
||||
"@tiptap/extension-table-header": "2.7.4",
|
||||
"@tiptap/extension-table-row": "2.7.4",
|
||||
"@tiptap/extension-task-item": "2.7.4",
|
||||
"@tiptap/extension-task-list": "2.7.4",
|
||||
"@tiptap/extension-text": "2.7.4",
|
||||
"@tiptap/extension-typography": "2.7.4",
|
||||
"@tiptap/extension-underline": "2.7.4",
|
||||
"@tiptap/pm": "2.7.4",
|
||||
"@tiptap/suggestion": "2.7.4",
|
||||
"@tiptap/vue-3": "2.7.4",
|
||||
"@vueuse/core": "11.1.0",
|
||||
"@vueuse/router": "11.1.0",
|
||||
"axios": "1.7.7",
|
||||
@@ -102,7 +102,7 @@
|
||||
"change-case": "5.4.4",
|
||||
"date-fns": "4.1.0",
|
||||
"dayjs": "1.11.13",
|
||||
"dompurify": "3.1.6",
|
||||
"dompurify": "3.1.7",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"flatpickr": "4.6.13",
|
||||
"flexsearch": "0.7.43",
|
||||
@@ -113,13 +113,13 @@
|
||||
"pinia": "2.2.2",
|
||||
"register-service-worker": "1.7.2",
|
||||
"sortablejs": "1.15.3",
|
||||
"tailwindcss": "3.4.12",
|
||||
"tailwindcss": "3.4.13",
|
||||
"tippy.js": "6.3.7",
|
||||
"ufo": "1.5.4",
|
||||
"vue": "3.5.6",
|
||||
"vue": "3.5.10",
|
||||
"vue-advanced-cropper": "2.8.9",
|
||||
"vue-flatpickr-component": "11.0.5",
|
||||
"vue-i18n": "10.0.1",
|
||||
"vue-i18n": "10.0.3",
|
||||
"vue-router": "4.4.5",
|
||||
"vuemoji-picker": "0.3.1",
|
||||
"workbox-precaching": "7.1.0",
|
||||
@@ -137,9 +137,9 @@
|
||||
"@types/codemirror": "5.60.15",
|
||||
"@types/dompurify": "3.0.5",
|
||||
"@types/flexsearch": "0.7.6",
|
||||
"@types/is-touch-device": "1.0.2",
|
||||
"@types/is-touch-device": "1.0.3",
|
||||
"@types/lodash.clonedeep": "4.5.9",
|
||||
"@types/node": "20.16.5",
|
||||
"@types/node": "20.16.10",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "7.18.0",
|
||||
"@typescript-eslint/parser": "7.18.0",
|
||||
@@ -149,11 +149,11 @@
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"@vue/tsconfig": "0.5.1",
|
||||
"autoprefixer": "10.4.20",
|
||||
"browserslist": "4.23.3",
|
||||
"caniuse-lite": "1.0.30001662",
|
||||
"browserslist": "4.24.0",
|
||||
"caniuse-lite": "1.0.30001664",
|
||||
"csstype": "3.1.3",
|
||||
"cypress": "13.14.2",
|
||||
"esbuild": "0.23.1",
|
||||
"cypress": "13.15.0",
|
||||
"esbuild": "0.24.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-plugin-vue": "9.28.0",
|
||||
"happy-dom": "14.12.3",
|
||||
@@ -162,13 +162,13 @@
|
||||
"postcss-easing-gradients": "3.0.1",
|
||||
"postcss-easings": "4.0.0",
|
||||
"postcss-preset-env": "9.6.0",
|
||||
"rollup": "4.22.0",
|
||||
"rollup": "4.22.5",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"sass": "1.79.2",
|
||||
"sass": "1.79.4",
|
||||
"start-server-and-test": "2.0.8",
|
||||
"typescript": "5.6.2",
|
||||
"unplugin-inject-preload": "2.0.5",
|
||||
"vite": "5.4.6",
|
||||
"vite": "5.4.8",
|
||||
"vite-plugin-pwa": "0.20.5",
|
||||
"vite-plugin-sentry": "1.4.0",
|
||||
"vite-svg-loader": "5.1.0",
|
||||
|
||||
Generated
+794
-806
File diff suppressed because it is too large
Load Diff
@@ -3,33 +3,22 @@
|
||||
v-cy="'checkbox'"
|
||||
class="base-checkbox"
|
||||
>
|
||||
<input
|
||||
:id="checkboxId"
|
||||
type="checkbox"
|
||||
class="is-sr-only"
|
||||
:checked="modelValue"
|
||||
:disabled="disabled || undefined"
|
||||
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
|
||||
<label
|
||||
class="base-checkbox__label"
|
||||
>
|
||||
|
||||
<slot
|
||||
name="label"
|
||||
:checkbox-id="checkboxId"
|
||||
>
|
||||
<label
|
||||
:for="checkboxId"
|
||||
class="base-checkbox__label"
|
||||
<input
|
||||
type="checkbox"
|
||||
class="is-sr-only"
|
||||
:checked="modelValue"
|
||||
:disabled="disabled || undefined"
|
||||
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
|
||||
>
|
||||
<slot />
|
||||
</label>
|
||||
</slot>
|
||||
<slot />
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue'
|
||||
import {createRandomID} from '@/helpers/randomId'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
modelValue?: boolean,
|
||||
disabled: boolean,
|
||||
@@ -38,10 +27,8 @@ withDefaults(defineProps<{
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: boolean): void
|
||||
(event: 'update:modelValue', value: boolean): void
|
||||
}>()
|
||||
|
||||
const checkboxId = ref(`checkbox_${createRandomID()}`)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -53,7 +40,7 @@ const checkboxId = ref(`checkbox_${createRandomID()}`)
|
||||
}
|
||||
|
||||
.base-checkbox:has(input:disabled) .base-checkbox__label {
|
||||
cursor:not-allowed;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<nav
|
||||
v-if="totalPages > 1"
|
||||
aria-label="pagination"
|
||||
class="pagination is-centered p-4"
|
||||
role="navigation"
|
||||
>
|
||||
<slot
|
||||
name="previous"
|
||||
:disabled="currentPage === 1"
|
||||
>
|
||||
{{ $t('misc.previous') }}
|
||||
</slot>
|
||||
<slot
|
||||
name="next"
|
||||
:disabled="currentPage === totalPages"
|
||||
>
|
||||
{{ $t('misc.next') }}
|
||||
</slot>
|
||||
<ul class="pagination-list">
|
||||
<li
|
||||
v-for="(p, i) in pages"
|
||||
:key="`page-${i}`"
|
||||
>
|
||||
<span
|
||||
v-if="p.isEllipsis"
|
||||
class="pagination-ellipsis"
|
||||
>…</span>
|
||||
<slot
|
||||
v-else
|
||||
name="page-link"
|
||||
:page="p"
|
||||
:is-current="p.number === currentPage"
|
||||
>
|
||||
{{ p.number }}
|
||||
</slot>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed} from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
totalPages: number,
|
||||
currentPage: number
|
||||
}>()
|
||||
|
||||
function createPagination(totalPages: number, currentPage: number) {
|
||||
const pages = []
|
||||
for (let i = 0; i < totalPages; i++) {
|
||||
if (
|
||||
i > 0 &&
|
||||
(i + 1) < totalPages &&
|
||||
((i + 1) > currentPage + 1 || (i + 1) < currentPage - 1)
|
||||
) {
|
||||
if (pages[i - 1] && !pages[i - 1].isEllipsis) {
|
||||
pages.push({
|
||||
number: 0,
|
||||
isEllipsis: true,
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
pages.push({
|
||||
number: i + 1,
|
||||
isEllipsis: false,
|
||||
})
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
const pages = computed(() => createPagination(props.totalPages, props.currentPage))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pagination {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.pagination-previous,
|
||||
.pagination-next {
|
||||
&:not(:disabled):hover {
|
||||
background: $scheme-main;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-list {
|
||||
&, & li {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -38,7 +38,6 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fancy-checkbox {
|
||||
display: inline-block;
|
||||
@@ -70,8 +69,8 @@ const emit = defineEmits<{
|
||||
}
|
||||
}
|
||||
|
||||
.fancy-checkbox:not(:has(input:disabled)):hover .fancy-checkbox__icon,
|
||||
.fancy-checkbox:has(input:checked) .fancy-checkbox__icon {
|
||||
.fancy-checkbox:hover input:not(:disabled) + .fancy-checkbox__icon,
|
||||
.fancy-checkbox input:checked + .fancy-checkbox__icon {
|
||||
--stroke-color: var(--primary);
|
||||
}
|
||||
</style>
|
||||
@@ -80,13 +79,13 @@ const emit = defineEmits<{
|
||||
// Since css-has-pseudo doesn't work with deep classes,
|
||||
// the following rules can't be scoped
|
||||
|
||||
.fancy-checkbox:has(:not(input:checked)) .fancy-checkbox__icon {
|
||||
.fancy-checkbox :not(input:checked) + .fancy-checkbox__icon {
|
||||
path {
|
||||
transition-delay: 0.05s;
|
||||
}
|
||||
}
|
||||
|
||||
.fancy-checkbox:has(input:checked) .fancy-checkbox__icon {
|
||||
.fancy-checkbox input:checked + .fancy-checkbox__icon {
|
||||
path {
|
||||
stroke-dashoffset: 60;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
}"
|
||||
>
|
||||
<slot>
|
||||
<div class="header">
|
||||
<div class="modal-header">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div class="content">
|
||||
@@ -140,7 +140,7 @@ $modal-width: 1024px;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
.modal-header {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -241,6 +241,14 @@ $modal-width: 1024px;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content:has(.modal-header) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 0 1rem;
|
||||
min-height: 100vh
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -1,89 +1,49 @@
|
||||
<template>
|
||||
<nav
|
||||
v-if="totalPages > 1"
|
||||
aria-label="pagination"
|
||||
class="pagination is-centered p-4"
|
||||
role="navigation"
|
||||
<BasePagination
|
||||
:total-pages="totalPages"
|
||||
:current-page="currentPage"
|
||||
>
|
||||
<RouterLink
|
||||
:disabled="currentPage === 1 || undefined"
|
||||
:to="getRouteForPagination(currentPage - 1)"
|
||||
class="pagination-previous"
|
||||
>
|
||||
{{ $t('misc.previous') }}
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
:disabled="currentPage === totalPages || undefined"
|
||||
:to="getRouteForPagination(currentPage + 1)"
|
||||
class="pagination-next"
|
||||
>
|
||||
{{ $t('misc.next') }}
|
||||
</RouterLink>
|
||||
<ul class="pagination-list">
|
||||
<li
|
||||
v-for="(p, i) in pages"
|
||||
:key="`page-${i}`"
|
||||
<template #previous="{ disabled }">
|
||||
<RouterLink
|
||||
:disabled="disabled || undefined"
|
||||
:to="getRouteForPagination(currentPage - 1)"
|
||||
class="pagination-previous"
|
||||
>
|
||||
<span
|
||||
v-if="p.isEllipsis"
|
||||
class="pagination-ellipsis"
|
||||
>…</span>
|
||||
<RouterLink
|
||||
v-else
|
||||
class="pagination-link"
|
||||
:aria-label="'Goto page ' + p.number"
|
||||
:class="{ 'is-current': p.number === currentPage }"
|
||||
:to="getRouteForPagination(p.number)"
|
||||
>
|
||||
{{ p.number }}
|
||||
</RouterLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{{ $t('misc.previous') }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
<template #next="{ disabled }">
|
||||
<RouterLink
|
||||
:disabled="disabled || undefined"
|
||||
:to="getRouteForPagination(currentPage + 1)"
|
||||
class="pagination-next"
|
||||
>
|
||||
{{ $t('misc.next') }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
<template #page-link="{ page, isCurrent }">
|
||||
<RouterLink
|
||||
class="pagination-link"
|
||||
:aria-label="'Goto page ' + page.number"
|
||||
:class="{ 'is-current': isCurrent }"
|
||||
:to="getRouteForPagination(page.number)"
|
||||
>
|
||||
{{ page.number }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
</BasePagination>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed} from 'vue'
|
||||
import BasePagination from '@/components/base/BasePagination.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
withDefaults(defineProps<{
|
||||
totalPages: number,
|
||||
currentPage?: number
|
||||
}>(), {
|
||||
currentPage: 0,
|
||||
})
|
||||
|
||||
function createPagination(totalPages: number, currentPage: number) {
|
||||
const pages = []
|
||||
for (let i = 0; i < totalPages; i++) {
|
||||
|
||||
// Show ellipsis instead of all pages
|
||||
if (
|
||||
i > 0 && // Always at least the first page
|
||||
(i + 1) < totalPages && // And the last page
|
||||
(
|
||||
// And the current with current + 1 and current - 1
|
||||
(i + 1) > currentPage + 1 ||
|
||||
(i + 1) < currentPage - 1
|
||||
)
|
||||
) {
|
||||
// Only add an ellipsis if the last page isn't already one
|
||||
if (pages[i - 1] && !pages[i - 1].isEllipsis) {
|
||||
pages.push({
|
||||
number: 0,
|
||||
isEllipsis: true,
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
pages.push({
|
||||
number: i + 1,
|
||||
isEllipsis: false,
|
||||
})
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
function getRouteForPagination(page = 1, type = null) {
|
||||
return {
|
||||
name: type,
|
||||
@@ -95,20 +55,4 @@ function getRouteForPagination(page = 1, type = null) {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const pages = computed(() => createPagination(props.totalPages, props.currentPage))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pagination {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.pagination-previous,
|
||||
.pagination-next {
|
||||
&:not(:disabled):hover {
|
||||
background: $scheme-main;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</script>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<BasePagination
|
||||
:total-pages="totalPages"
|
||||
:current-page="currentPage"
|
||||
>
|
||||
<template #previous="{ disabled }">
|
||||
<BaseButton
|
||||
:disabled="disabled"
|
||||
class="pagination-previous"
|
||||
@click="changePage(currentPage - 1)"
|
||||
>
|
||||
{{ $t('misc.previous') }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
<template #next="{ disabled }">
|
||||
<BaseButton
|
||||
:disabled="disabled"
|
||||
class="pagination-next"
|
||||
@click="changePage(currentPage + 1)"
|
||||
>
|
||||
{{ $t('misc.next') }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
<template #page-link="{ page, isCurrent }">
|
||||
<BaseButton
|
||||
class="pagination-link"
|
||||
:aria-label="'Goto page ' + page.number"
|
||||
:class="{ 'is-current': isCurrent }"
|
||||
@click="changePage(page.number)"
|
||||
>
|
||||
{{ page.number }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
</BasePagination>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BasePagination from '@/components/base/BasePagination.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
totalPages: number,
|
||||
currentPage: number
|
||||
}>(), {
|
||||
currentPage: 1,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['pageChanged'])
|
||||
|
||||
function changePage(page: number) {
|
||||
if (page >= 1 && page <= props.totalPages) {
|
||||
emit('pageChanged', page)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -180,7 +180,7 @@ import ProgressBar from '@/components/misc/ProgressBar.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
||||
import AttachmentService from '@/services/attachment'
|
||||
import {canPreview} from '@/models/attachment'
|
||||
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
|
||||
import type {IAttachment} from '@/modelTypes/IAttachment'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
|
||||
@@ -274,6 +274,10 @@ async function viewOrDownload(attachment: IAttachment) {
|
||||
}
|
||||
}
|
||||
|
||||
function canPreview(attachment: IAttachment): boolean {
|
||||
return SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))
|
||||
}
|
||||
|
||||
const copy = useCopyToClipboard()
|
||||
|
||||
function copyUrl(attachment: IAttachment) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="enabled"
|
||||
class="content details"
|
||||
ref="commentsRef"
|
||||
class="content details comments-container"
|
||||
>
|
||||
<h3
|
||||
v-if="canWrite || comments.length > 0"
|
||||
@@ -15,7 +16,7 @@
|
||||
<div class="comments">
|
||||
<span
|
||||
v-if="taskCommentService.loading && saving === null && !creating"
|
||||
class="is-inline-flex is-align-items-center"
|
||||
class="is-flex is-align-items-center my-4 ml-2"
|
||||
>
|
||||
<span class="loader is-inline-block mr-2" />
|
||||
{{ $t('task.comment.loading') }}
|
||||
@@ -107,6 +108,14 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PaginationEmit
|
||||
v-if="taskCommentService.totalPages > 1"
|
||||
:total-pages="taskCommentService.totalPages"
|
||||
:current-page="currentPage"
|
||||
@pageChanged="changePage"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="canWrite"
|
||||
class="media comment d-print-none"
|
||||
@@ -160,6 +169,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<Modal
|
||||
:enabled="showDeleteModal"
|
||||
@close="showDeleteModal = false"
|
||||
@@ -185,6 +195,7 @@ import {useI18n} from 'vue-i18n'
|
||||
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import Editor from '@/components/input/AsyncEditor'
|
||||
import PaginationEmit from '@/components/misc/PaginationEmit.vue'
|
||||
|
||||
import TaskCommentService from '@/services/taskComment'
|
||||
import TaskCommentModel from '@/models/taskComment'
|
||||
@@ -242,6 +253,12 @@ const actions = computed(() => {
|
||||
])))
|
||||
})
|
||||
|
||||
const frontendUrl = computed(() => configStore.frontendUrl)
|
||||
|
||||
const currentPage = ref(1)
|
||||
|
||||
const commentsRef = ref<HTMLElement | null>(null)
|
||||
|
||||
async function attachmentUpload(files: File[] | FileList): (Promise<string[]>) {
|
||||
|
||||
const uploadPromises: Promise<string>[] = []
|
||||
@@ -267,12 +284,21 @@ async function loadComments(taskId: ITask['id']) {
|
||||
newComment.taskId = taskId
|
||||
commentEdit.taskId = taskId
|
||||
commentToDelete.taskId = taskId
|
||||
comments.value = await taskCommentService.getAll({taskId})
|
||||
comments.value = await taskCommentService.getAll({taskId}, {}, currentPage.value)
|
||||
}
|
||||
|
||||
async function changePage(page: number) {
|
||||
commentsRef.value?.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' })
|
||||
currentPage.value = page
|
||||
await loadComments(props.taskId)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.taskId,
|
||||
loadComments,
|
||||
() => {
|
||||
currentPage.value = 1 // Reset to first page when task changes
|
||||
loadComments(props.taskId)
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
@@ -404,4 +430,8 @@ async function deleteComment(commentToDelete: ITaskComment) {
|
||||
.media-content {
|
||||
width: calc(100% - 48px - 2rem);
|
||||
}
|
||||
|
||||
.comments-container {
|
||||
scroll-margin-top: 4rem;
|
||||
}
|
||||
</style>
|
||||
@@ -22,7 +22,7 @@
|
||||
import {ref, shallowReactive, watchEffect} from 'vue'
|
||||
import AttachmentService from '@/services/attachment'
|
||||
import type {IAttachment} from '@/modelTypes/IAttachment'
|
||||
import {canPreview} from '@/models/attachment'
|
||||
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: IAttachment
|
||||
@@ -33,9 +33,13 @@ const blobUrl = ref<string | undefined>(undefined)
|
||||
|
||||
watchEffect(async () => {
|
||||
if (props.modelValue && canPreview(props.modelValue)) {
|
||||
blobUrl.value = await attachmentService.getBlobUrl(props.modelValue, PREVIEW_SIZE.MD)
|
||||
blobUrl.value = await attachmentService.getBlobUrl(props.modelValue)
|
||||
}
|
||||
})
|
||||
|
||||
function canPreview(attachment: IAttachment): boolean {
|
||||
return SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
:is-done="task.done"
|
||||
/>
|
||||
<BaseButton
|
||||
v-if="hasClose"
|
||||
class="close"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
@@ -33,6 +34,7 @@
|
||||
{{ task.title.trim() }}
|
||||
</h1>
|
||||
<BaseButton
|
||||
v-if="hasClose"
|
||||
class="close"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
@@ -78,8 +80,10 @@ import {getHexColor, getTaskIdentifier} from '@/models/task'
|
||||
const props = withDefaults(defineProps<{
|
||||
task: ITask,
|
||||
canWrite: boolean | undefined,
|
||||
hasClose: boolean | undefined,
|
||||
}>(), {
|
||||
canWrite: false,
|
||||
hasClose: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -165,7 +165,7 @@ async function maybeDownloadCoverImage() {
|
||||
}
|
||||
|
||||
const attachmentService = new AttachmentService()
|
||||
coverImageBlobUrl.value = await attachmentService.getBlobUrl(attachment, PREVIEW_SIZE.LG)
|
||||
coverImageBlobUrl.value = await attachmentService.getBlobUrl(attachment)
|
||||
}
|
||||
|
||||
watch(
|
||||
|
||||
@@ -7,10 +7,6 @@ import type { IAttachment } from '@/modelTypes/IAttachment'
|
||||
|
||||
export const SUPPORTED_IMAGE_SUFFIX = ['.jpg', '.png', '.bmp', '.gif']
|
||||
|
||||
export function canPreview(attachment: IAttachment): boolean {
|
||||
return SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))
|
||||
}
|
||||
|
||||
export default class AttachmentModel extends AbstractModel<IAttachment> implements IAttachment {
|
||||
id = 0
|
||||
taskId = 0
|
||||
|
||||
@@ -37,13 +37,8 @@ export default class AttachmentService extends AbstractService<IAttachment> {
|
||||
return data
|
||||
}
|
||||
|
||||
getBlobUrl(model: IAttachment, size?: PREVIEW_SIZE) {
|
||||
let mainUrl = '/tasks/' + model.taskId + '/attachments/' + model.id
|
||||
if (size !== undefined) {
|
||||
mainUrl += `?preview_size=${size}`
|
||||
}
|
||||
|
||||
return AbstractService.prototype.getBlobUrl.call(this, mainUrl)
|
||||
getBlobUrl(model: IAttachment) {
|
||||
return AbstractService.prototype.getBlobUrl.call(this, '/tasks/' + model.taskId + '/attachments/' + model.id)
|
||||
}
|
||||
|
||||
async download(model: IAttachment) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
ref="heading"
|
||||
:task="task"
|
||||
:can-write="canWrite"
|
||||
:has-close="isModal"
|
||||
@update:task="Object.assign(task, $event)"
|
||||
@close="$emit('close')"
|
||||
/>
|
||||
@@ -572,8 +573,10 @@
|
||||
</template>
|
||||
|
||||
<template #text>
|
||||
<p>
|
||||
{{ $t('task.detail.delete.text1') }}<br>
|
||||
<p class="tw-text-balance !tw-mb-0">
|
||||
{{ $t('task.detail.delete.text1') }}
|
||||
</p>
|
||||
<p class="tw-text-balance">
|
||||
{{ $t('task.detail.delete.text2') }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
@@ -78,7 +78,7 @@ require (
|
||||
gopkg.in/d4l3k/messagediff.v1 v1.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
src.techknowlogick.com/xgo v1.7.1-0.20240403232151-e01c4fbef884
|
||||
src.techknowlogick.com/xgo v1.8.1-0.20241105013731-313dedef864f
|
||||
src.techknowlogick.com/xormigrate v1.7.1
|
||||
xorm.io/builder v0.3.13
|
||||
xorm.io/xorm v1.3.9
|
||||
|
||||
@@ -723,8 +723,8 @@ mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
|
||||
mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
src.techknowlogick.com/xgo v1.7.1-0.20240403232151-e01c4fbef884 h1:Ttvt8FCpUXfC8r3+LgSPrBUIr/JkHmYQtvmOwEET8qE=
|
||||
src.techknowlogick.com/xgo v1.7.1-0.20240403232151-e01c4fbef884/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
|
||||
src.techknowlogick.com/xgo v1.8.1-0.20241105013731-313dedef864f h1:Dy7qQ31o3z4EV+0ISDH1IldkPxzubOAvV7DW+9HnbNg=
|
||||
src.techknowlogick.com/xgo v1.8.1-0.20241105013731-313dedef864f/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
|
||||
src.techknowlogick.com/xormigrate v1.7.1 h1:RKGLLUAqJ+zO8iZ7eOc7oLH7f0cs2gfXSZSvBRBHnlY=
|
||||
src.techknowlogick.com/xormigrate v1.7.1/go.mod h1:YGNBdj8prENlySwIKmfoEXp7ILGjAltyKFXD0qLgD7U=
|
||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
|
||||
+13
-1
@@ -547,7 +547,19 @@ func (Release) Windows() error {
|
||||
|
||||
// Builds binaries for linux
|
||||
func (Release) Linux() error {
|
||||
return runXgo("linux/*")
|
||||
targets := []string{
|
||||
"linux/amd64",
|
||||
"linux/arm-5",
|
||||
"linux/arm-6",
|
||||
"linux/arm-7",
|
||||
"linux/arm64",
|
||||
"linux/mips",
|
||||
"linux/mipsle",
|
||||
"linux/mips64",
|
||||
"linux/mips64le",
|
||||
"linux/riscv64",
|
||||
}
|
||||
return runXgo(strings.Join(targets, ","))
|
||||
}
|
||||
|
||||
// Builds binaries for darwin
|
||||
|
||||
+6
-1
@@ -22,6 +22,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
@@ -58,8 +59,12 @@ func (*File) TableName() string {
|
||||
}
|
||||
|
||||
func (f *File) getAbsoluteFilePath() string {
|
||||
base := ""
|
||||
if !strings.HasPrefix(config.FilesBasePath.GetString(), "/") {
|
||||
base = config.ServiceRootpath.GetString()
|
||||
}
|
||||
return filepath.Join(
|
||||
config.ServiceRootpath.GetString(),
|
||||
base,
|
||||
config.FilesBasePath.GetString(),
|
||||
strconv.FormatInt(f.ID, 10),
|
||||
)
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
@@ -106,7 +107,10 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
|
||||
|
||||
// Save the file id with the user
|
||||
u.ExportFileID = exportFile.ID
|
||||
_, err = s.Cols("export_file_id").Update(u)
|
||||
_, err = s.
|
||||
Where("id = ?", u.ID).
|
||||
Cols("export_file_id").
|
||||
Update(u)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -360,8 +360,13 @@ func RegisterAddTaskToFilterViewCron() {
|
||||
defer s.Close()
|
||||
|
||||
// Get all filters with a date clause and a manual kanban view
|
||||
where := "filters LIKE '%_date%'"
|
||||
if db.GetDialect() == builder.POSTGRES {
|
||||
where = "filters::jsonb ?| array['due_date', 'start_date', 'end_date']"
|
||||
}
|
||||
|
||||
filters := map[int64]*SavedFilter{}
|
||||
err := s.Where("filters LIKE '%_date%'").Find(&filters)
|
||||
err := s.Where(where).Find(&filters)
|
||||
if err != nil {
|
||||
log.Errorf("%sError fetching filters: %s", logPrefix, err)
|
||||
return
|
||||
|
||||
+11
-113
@@ -17,21 +17,14 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/png"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/events"
|
||||
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/modules/keyvalue"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
@@ -139,7 +132,16 @@ func (ta *TaskAttachment) ReadOne(s *xorm.Session, _ web.Auth) (err error) {
|
||||
// @Failure 404 {object} models.Message "The task does not exist."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{id}/attachments [get]
|
||||
func (ta *TaskAttachment) ReadAll(s *xorm.Session, _ web.Auth, _ string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
|
||||
func (ta *TaskAttachment) ReadAll(s *xorm.Session, a web.Auth, _ string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
|
||||
task := Task{ID: ta.TaskID}
|
||||
canRead, _, err := task.CanRead(s, a)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
if !canRead {
|
||||
return nil, 0, 0, ErrGenericForbidden{}
|
||||
}
|
||||
|
||||
attachments := []*TaskAttachment{}
|
||||
|
||||
limit, start := getLimitFromPageIndex(page, perPage)
|
||||
@@ -192,110 +194,6 @@ func (ta *TaskAttachment) ReadAll(s *xorm.Session, _ web.Auth, _ string, page in
|
||||
return attachments, len(attachments), numberOfTotalItems, err
|
||||
}
|
||||
|
||||
func cacheKeyForTaskAttachmentPreview(id int64, size PreviewSize) string {
|
||||
return "task_attachment_preview_" + strconv.FormatInt(id, 10) + "_size_" + string(size)
|
||||
}
|
||||
|
||||
func (ta *TaskAttachment) GetPreviewFromCache(previewSize PreviewSize) []byte {
|
||||
cacheKey := cacheKeyForTaskAttachmentPreview(ta.ID, previewSize)
|
||||
|
||||
var cached []byte
|
||||
exists, err := keyvalue.GetWithValue(cacheKey, &cached)
|
||||
|
||||
// If the preview is not cached, return nil
|
||||
if err != nil || !exists || cached == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return cached
|
||||
}
|
||||
|
||||
type PreviewSize string
|
||||
|
||||
const (
|
||||
PreviewSizeUnknown PreviewSize = "unknown"
|
||||
PreviewSmall PreviewSize = "sm"
|
||||
PreviewMedium PreviewSize = "md"
|
||||
PreviewLarge PreviewSize = "lg"
|
||||
PreviewExtraLarge PreviewSize = "xl"
|
||||
)
|
||||
|
||||
func (previewSize PreviewSize) GetSize() int {
|
||||
switch previewSize {
|
||||
case PreviewSmall:
|
||||
return 100
|
||||
case PreviewMedium:
|
||||
return 200
|
||||
case PreviewLarge:
|
||||
return 400
|
||||
case PreviewExtraLarge:
|
||||
return 800
|
||||
case PreviewSizeUnknown:
|
||||
return 0
|
||||
default:
|
||||
return 200
|
||||
}
|
||||
}
|
||||
|
||||
func GetPreviewSizeFromString(size string) PreviewSize {
|
||||
switch size {
|
||||
case "sm":
|
||||
return PreviewSmall
|
||||
case "md":
|
||||
return PreviewMedium
|
||||
case "lg":
|
||||
return PreviewLarge
|
||||
case "xl":
|
||||
return PreviewExtraLarge
|
||||
}
|
||||
|
||||
return PreviewSizeUnknown
|
||||
}
|
||||
|
||||
func resizeImage(img image.Image, width int) *image.NRGBA {
|
||||
resizedImg := imaging.Resize(img, width, 0, imaging.Lanczos)
|
||||
log.Debugf(
|
||||
"Resized attachment image from %vx%v to %vx%v for a preview",
|
||||
img.Bounds().Size().X,
|
||||
img.Bounds().Size().Y,
|
||||
resizedImg.Bounds().Size().X,
|
||||
resizedImg.Bounds().Size().Y,
|
||||
)
|
||||
|
||||
return resizedImg
|
||||
}
|
||||
|
||||
func (ta *TaskAttachment) GenerateAndSavePreviewToCache(previewSize PreviewSize) []byte {
|
||||
img, _, err := image.Decode(ta.File.File)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Scale down the image to a minimum size
|
||||
resizedImg := resizeImage(img, previewSize.GetSize())
|
||||
|
||||
// Get the raw bytes of the resized image
|
||||
buf := &bytes.Buffer{}
|
||||
if err := png.Encode(buf, resizedImg); err != nil {
|
||||
return nil
|
||||
}
|
||||
previewImage, err := io.ReadAll(buf)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Store the preview image in the cache
|
||||
cacheKey := cacheKeyForTaskAttachmentPreview(ta.ID, previewSize)
|
||||
err = keyvalue.Put(cacheKey, previewImage)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("Attachment image preview for task attachment %v of size %v created and cached", ta.ID, previewSize)
|
||||
|
||||
return previewImage
|
||||
}
|
||||
|
||||
// Delete removes an attachment
|
||||
// @Summary Delete an attachment
|
||||
// @Description Delete an attachment.
|
||||
|
||||
@@ -233,6 +233,14 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
canRead, _, err := sf.CanRead(s, a)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
if !canRead {
|
||||
return nil, 0, 0, ErrGenericForbidden{}
|
||||
}
|
||||
|
||||
// By prepending sort options before the saved ones from the filter, we make sure the supplied sort
|
||||
// options via query take precedence over the rest.
|
||||
|
||||
|
||||
@@ -17,14 +17,11 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
auth2 "code.vikunja.io/api/pkg/modules/auth"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
@@ -117,7 +114,6 @@ func UploadTaskAttachment(c echo.Context) error {
|
||||
// @Produce octet-stream
|
||||
// @Param id path int true "Task ID"
|
||||
// @Param attachmentID path int true "Attachment ID"
|
||||
// @Param preview_size query string false "The size of the preview image. Can be sm = 100px, md = 200px, lg = 400px or xl = 800px. If provided, a preview image will be returned if the attachment is an image."
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {file} blob "The attachment file."
|
||||
// @Failure 403 {object} models.Message "No access to this task."
|
||||
@@ -156,17 +152,6 @@ func GetTaskAttachment(c echo.Context) error {
|
||||
return handler.HandleHTTPError(err)
|
||||
}
|
||||
|
||||
// If the preview query parameter is set and the preview was already generated and cached, return the cached preview image
|
||||
previewSize := models.GetPreviewSizeFromString(c.QueryParam("preview_size"))
|
||||
if previewSize != models.PreviewSizeUnknown && strings.HasPrefix(taskAttachment.File.Mime, "image") {
|
||||
previewFileBytes := taskAttachment.GetPreviewFromCache(previewSize)
|
||||
if previewFileBytes != nil {
|
||||
log.Debugf("Cached attachment image preview found for task attachment %v", taskAttachment.ID)
|
||||
|
||||
return c.Blob(http.StatusOK, "image/png", previewFileBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Open and send the file to the client
|
||||
err = taskAttachment.File.LoadFileByID()
|
||||
if err != nil {
|
||||
@@ -179,14 +164,6 @@ func GetTaskAttachment(c echo.Context) error {
|
||||
return handler.HandleHTTPError(err)
|
||||
}
|
||||
|
||||
// If a preview is requested and the preview was not cached, we create the preview and cache it
|
||||
if previewSize != models.PreviewSizeUnknown {
|
||||
previewFileBytes := taskAttachment.GenerateAndSavePreviewToCache(previewSize)
|
||||
if previewFileBytes != nil {
|
||||
return c.Blob(http.StatusOK, "image/png", previewFileBytes)
|
||||
}
|
||||
}
|
||||
|
||||
http.ServeContent(c.Response(), c.Request(), taskAttachment.File.Name, taskAttachment.File.Created, taskAttachment.File.File)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ type VikunjaCaldavProjectStorage struct {
|
||||
}
|
||||
|
||||
// GetResources returns either all projects, links to the principal, or only one project, depending on the request
|
||||
func (vcls *VikunjaCaldavProjectStorage) GetResources(rpath string, _ bool) ([]data.Resource, error) {
|
||||
func (vcls *VikunjaCaldavProjectStorage) GetResources(rpath string, withChildren bool) ([]data.Resource, error) {
|
||||
|
||||
// It looks like we need to have the same handler for returning both the calendar home set and the user principal
|
||||
// Since the client seems to ignore the whatever is being returned in the first request and just makes a second one
|
||||
@@ -92,6 +92,24 @@ func (vcls *VikunjaCaldavProjectStorage) GetResources(rpath string, _ bool) ([]d
|
||||
}
|
||||
r := data.NewResource(rpath, &rr)
|
||||
r.Name = vcls.project.Title
|
||||
|
||||
// If the request is withChildren (Depth: 1), we need to return all tasks of the project
|
||||
if withChildren {
|
||||
resources := []data.Resource{r}
|
||||
|
||||
// Check if there are tasks to iterate over
|
||||
if vcls.project.Tasks != nil {
|
||||
for i := range vcls.project.Tasks {
|
||||
taskResource := VikunjaProjectResourceAdapter{
|
||||
project: vcls.project,
|
||||
task: &vcls.project.Tasks[i].Task,
|
||||
isCollection: false,
|
||||
}
|
||||
addTaskResource(&vcls.project.Tasks[i].Task, &taskResource, &resources)
|
||||
}
|
||||
}
|
||||
return resources, nil
|
||||
}
|
||||
return []data.Resource{r}, nil
|
||||
}
|
||||
|
||||
@@ -163,9 +181,7 @@ func (vcls *VikunjaCaldavProjectStorage) GetResourcesByList(rpaths []string) (re
|
||||
rr := VikunjaProjectResourceAdapter{
|
||||
task: t,
|
||||
}
|
||||
r := data.NewResource(getTaskURL(t), &rr)
|
||||
r.Name = t.Title
|
||||
resources = append(resources, r)
|
||||
addTaskResource(t, &rr, &resources)
|
||||
}
|
||||
|
||||
return
|
||||
@@ -671,3 +687,9 @@ func (vcls *VikunjaCaldavProjectStorage) getProjectRessource(isCollection bool)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func addTaskResource(task *models.Task, rr *VikunjaProjectResourceAdapter, resources *[]data.Resource) {
|
||||
taskResourceInstance := data.NewResource(getTaskURL(task), rr)
|
||||
taskResourceInstance.Name = task.Title
|
||||
*resources = append(*resources, taskResourceInstance)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user