Compare commits
204
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d684992c6 | ||
|
|
4dd9d5de67 | ||
|
|
ec902b051b | ||
|
|
4b2b8e3b83 | ||
|
|
bc52da4029 | ||
|
|
2123da49a3 | ||
|
|
c53a761396 | ||
|
|
c84b50b3ee | ||
|
|
c5b82fc591 | ||
|
|
1f00180af6 | ||
|
|
33e851f543 | ||
|
|
16d7386975 | ||
|
|
6d9bda8cc1 | ||
|
|
abf92e29fa | ||
|
|
f4d628550f | ||
|
|
1d352fcbf3 | ||
|
|
a1105434bf | ||
|
|
acc7c9f8f5 | ||
|
|
cc64ca6406 | ||
|
|
89f78cd369 | ||
|
|
14c3b13823 | ||
|
|
c2a7438814 | ||
|
|
8fe37fd900 | ||
|
|
88b4ab1768 | ||
|
|
c252c8f0cd | ||
|
|
410ff28b71 | ||
|
|
d98dd7ecbb | ||
|
|
fc73085876 | ||
|
|
c71f7334b4 | ||
|
|
6a5342bd49 | ||
|
|
b4ea1bb86a | ||
|
|
378d0ae9bb | ||
|
|
6f27e1401a | ||
|
|
bf5cafc03f | ||
|
|
f4a7326b68 | ||
|
|
1451f6e46f | ||
|
|
427eb2a618 | ||
|
|
e4b541e653 | ||
|
|
45ff5907e6 | ||
|
|
5a7c3927f3 | ||
|
|
f425d98b4d | ||
|
|
4c55016c1a | ||
|
|
1a8f12ac13 | ||
|
|
7b873ec31c | ||
|
|
8b77832af2 | ||
|
|
14710d0bba | ||
|
|
945f25b818 | ||
|
|
f53b93d98a | ||
|
|
97e030a1fc | ||
|
|
b708db7025 | ||
|
|
6bfe71b30e | ||
|
|
db196bdffe | ||
|
|
9428acd5e1 | ||
|
|
58a60fb4ae | ||
|
|
f4ca22a6ad | ||
|
|
7e38a9f442 | ||
|
|
a0a77818b1 | ||
|
|
bdccd633fe | ||
|
|
1f3eb8f2a3 | ||
|
|
8891ef347e | ||
|
|
3b8ccbad4f | ||
|
|
cc1b4bbd1b | ||
|
|
9dd95101f9 | ||
|
|
20724f6fb5 | ||
|
|
e1ea84fcd4 | ||
|
|
8d730543c3 | ||
|
|
a3d133bfee | ||
|
|
7b37ad0594 | ||
|
|
4aeb228c3a | ||
|
|
b52d6dbf59 | ||
|
|
76f7797e56 | ||
|
|
55dd7d2981 | ||
|
|
44a43b9f86 | ||
|
|
eb95caf757 | ||
|
|
1085a6583b | ||
|
|
e698ac5a34 | ||
|
|
020fea686d | ||
|
|
addf8523f4 | ||
|
|
044aa1c742 | ||
|
|
552e1ce796 | ||
|
|
c1246d73d4 | ||
|
|
85fa397a46 | ||
|
|
af47e43c34 | ||
|
|
2539f15dfb | ||
|
|
5c1b2846a1 | ||
|
|
af8100d262 | ||
|
|
63f85c97ae | ||
|
|
2b1e3ffd2a | ||
|
|
5f470afd7c | ||
|
|
37c6f94ba2 | ||
|
|
b42cd5786e | ||
|
|
5072ab3d72 | ||
|
|
4b5ec018f5 | ||
|
|
efe5c7b3cb | ||
|
|
ba0c32aacf | ||
|
|
b6e142e8d0 | ||
|
|
750d565477 | ||
|
|
2b8515b91d | ||
|
|
68636f27da | ||
|
|
cf94cc8cab | ||
|
|
669b7452cd | ||
|
|
1601c9098d | ||
|
|
f132b0e718 | ||
|
|
5d12986e3f | ||
|
|
74e0197dc6 | ||
|
|
2dd133fb4c | ||
|
|
02c1de55c4 | ||
|
|
cbc63c853d | ||
|
|
b5c4a3a80c | ||
|
|
a8ea5e11ab | ||
|
|
812430b9cf | ||
|
|
9d7b6d3d9a | ||
|
|
c2b116de70 | ||
|
|
22e594e253 | ||
|
|
0d9c03e0f2 | ||
|
|
72bb6609a1 | ||
|
|
b259072e6a | ||
|
|
ffcc48ec87 | ||
|
|
5aa0b6a0cf | ||
|
|
b60efbd259 | ||
|
|
34ac29fcce | ||
|
|
75f3e930cd | ||
|
|
4ff8815fe1 | ||
|
|
850ec7efb0 | ||
|
|
115d1c3618 | ||
|
|
6d26497bbb | ||
|
|
06305eb6b3 | ||
|
|
7bd84a845c | ||
|
|
165ee9e7f3 | ||
|
|
2c9becec10 | ||
|
|
1463bce24c | ||
|
|
5c56d07215 | ||
|
|
6fb314b326 | ||
|
|
9b01666ec6 | ||
|
|
14c862d284 | ||
|
|
bcfd72c645 | ||
|
|
499f66b7ae | ||
|
|
8e37d5cb76 | ||
|
|
57ba073874 | ||
|
|
329de3aab3 | ||
|
|
74a74b7ec7 | ||
|
|
2063da9eec | ||
|
|
7a7e97770c | ||
|
|
bea131cfd9 | ||
|
|
5a0d1f1dc6 | ||
|
|
b5035f9d2b | ||
|
|
07bd9de0c6 | ||
|
|
efde364224 | ||
|
|
fde1763eef | ||
|
|
429c7ca2c1 | ||
|
|
bf56311faa | ||
|
|
c38aaeb224 | ||
|
|
1b9139b9b0 | ||
|
|
13763e3540 | ||
|
|
56bab7485f | ||
|
|
8bfd0493b2 | ||
|
|
435cb2e7f7 | ||
|
|
9ed33f5c08 | ||
|
|
83669fd9e0 | ||
|
|
35578e0021 | ||
|
|
e281de30b5 | ||
|
|
ec2c2e74f0 | ||
|
|
b031c9772f | ||
|
|
f22420d502 | ||
|
|
ebfd5f54d2 | ||
|
|
28b4eaee31 | ||
|
|
b83448b7a6 | ||
|
|
eed783e42f | ||
|
|
1d624612ee | ||
|
|
9576f72d7c | ||
|
|
7efc4d1bc8 | ||
|
|
670e605275 | ||
|
|
871b3ce071 | ||
|
|
ad3c5fcee5 | ||
|
|
e2a87036e0 | ||
|
|
40bb86bee5 | ||
|
|
4a4ed2cf89 | ||
|
|
7751e124a7 | ||
|
|
6137d07138 | ||
|
|
66d316f0b5 | ||
|
|
bf2d56c9d4 | ||
|
|
413798b321 | ||
|
|
bf1c8c6119 | ||
|
|
49fa0a8468 | ||
|
|
784cd42a7a | ||
|
|
4f74be0247 | ||
|
|
7ef1e0a3e5 | ||
|
|
bc5fd380e5 | ||
|
|
bcd306b84d | ||
|
|
ad2870335d | ||
|
|
917748777f | ||
|
|
324c966dda | ||
|
|
0984821b41 | ||
|
|
e8be657d97 | ||
|
|
75ce261f74 | ||
|
|
5f9d0fe763 | ||
|
|
bbfd5270db | ||
|
|
1e632397d2 | ||
|
|
d35454c099 | ||
|
|
2a6ba7e7f0 | ||
|
|
1e523a1a39 | ||
|
|
fb91e73a3c | ||
|
|
ebd6139c1f | ||
|
|
6990be705c |
+82
-80
@@ -580,7 +580,7 @@ steps:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: before-static-build
|
||||
image: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: always
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
@@ -589,7 +589,7 @@ steps:
|
||||
depends_on: [ fetch-tags, mage ]
|
||||
|
||||
- name: static-build-windows
|
||||
image: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
image: techknowlogick/xgo:latest
|
||||
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: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
image: techknowlogick/xgo:latest
|
||||
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: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
image: techknowlogick/xgo:latest
|
||||
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: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: always
|
||||
depends_on:
|
||||
- after-build-compress
|
||||
@@ -676,13 +676,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: hetzner_access_key_id
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
strip_prefix: dist/zip/
|
||||
source: dist/zip/*
|
||||
@@ -698,13 +698,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: hetzner_access_key_id
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
strip_prefix: dist/zip/
|
||||
source: dist/zip/*
|
||||
@@ -750,13 +750,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: hetzner_access_key_id
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
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
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: hetzner_access_key_id
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
strip_prefix: dist/os-packages/
|
||||
source: dist/os-packages/*
|
||||
@@ -837,6 +837,7 @@ steps:
|
||||
repo: vikunja/vikunja
|
||||
tags: unstable
|
||||
platforms:
|
||||
- linux/386
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
@@ -868,6 +869,7 @@ steps:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/vikunja
|
||||
platforms:
|
||||
- linux/386
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
@@ -929,13 +931,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: hetzner_access_key_id
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
source: frontend/vikunja-frontend-unstable.zip
|
||||
target: /
|
||||
@@ -988,13 +990,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: hetzner_access_key_id
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
source: frontend/vikunja-frontend-${DRONE_TAG##v}.zip
|
||||
target: /
|
||||
@@ -1093,8 +1095,8 @@ steps:
|
||||
# settings:
|
||||
# restore: true
|
||||
# bucket: kolaente.dev-drone-dependency-cache
|
||||
# endpoint: https://fsn1.your-objectstorage.com
|
||||
# region: fsn1
|
||||
# endpoint: https://s3.fr-par.scw.cloud
|
||||
# region: fr-par
|
||||
# path_style: true
|
||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||
# mount:
|
||||
@@ -1131,8 +1133,8 @@ steps:
|
||||
# settings:
|
||||
# rebuild: true
|
||||
# bucket: kolaente.dev-drone-dependency-cache
|
||||
# endpoint: https://fsn1.your-objectstorage.com
|
||||
# region: fsn1
|
||||
# endpoint: https://s3.fr-par.scw.cloud
|
||||
# region: fr-par
|
||||
# path_style: true
|
||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||
# mount:
|
||||
@@ -1174,8 +1176,8 @@ steps:
|
||||
# settings:
|
||||
# restore: true
|
||||
# bucket: kolaente.dev-drone-dependency-cache
|
||||
# endpoint: https://fsn1.your-objectstorage.com
|
||||
# region: fsn1
|
||||
# endpoint: https://s3.fr-par.scw.cloud
|
||||
# region: fr-par
|
||||
# path_style: true
|
||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||
# mount:
|
||||
@@ -1198,30 +1200,30 @@ steps:
|
||||
- unzip vikunja-frontend-$$VERSION.zip -d frontend
|
||||
- sed -i 's/\\/api\\/v1//g' frontend/index.html
|
||||
- ./bumpp.sh
|
||||
- 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
|
||||
- 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
|
||||
|
||||
- name: rename-unstable
|
||||
image: bash
|
||||
pull: true
|
||||
@@ -1239,13 +1241,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: hetzner_access_key_id
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
strip_prefix: desktop/dist/
|
||||
source: desktop/dist/Vikunja-Desktop*
|
||||
@@ -1261,13 +1263,13 @@ steps:
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: hetzner_access_key_id
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
strip_prefix: desktop/dist/
|
||||
source: desktop/dist/*
|
||||
@@ -1296,9 +1298,9 @@ steps:
|
||||
# - name: build
|
||||
# environment:
|
||||
# ACCESS_KEY:
|
||||
# from_secret: hetzner_access_key_id
|
||||
# from_secret: aws_access_key_id
|
||||
# SECRET_KEY:
|
||||
# from_secret: hetzner_secret_access_key
|
||||
# from_secret: aws_secret_access_key
|
||||
# commands:
|
||||
# - git fetch --tags
|
||||
# - export VERSION=${DRONE_TAG##v}
|
||||
@@ -1311,9 +1313,9 @@ steps:
|
||||
# - sed -i '' "s/\$${version}/$$VERSION/g" package.json
|
||||
# - yarn install
|
||||
# - yarn dist --mac
|
||||
# - 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/
|
||||
# - 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/
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
@@ -1350,6 +1352,6 @@ steps:
|
||||
- failure
|
||||
---
|
||||
kind: signature
|
||||
hmac: 02fb3097f3e50facb2579de5c3b94e513b97e535a049a28ddb67f85e494d65b1
|
||||
hmac: eff6e40dc763fa1395078daaabcbef2aaa74fe0a36818a87e89015dbc4e9dae4
|
||||
|
||||
...
|
||||
|
||||
-261
@@ -7,267 +7,6 @@ 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
|
||||
|
||||
* *(a11y)* Hide unfocusable buttons
|
||||
* *(api)* Return 404 response when using a token and the route does not exist
|
||||
* *(auth)* Restrict max password length to 72 bytes
|
||||
* *(caldav)* Make sure colors are correctly saved and returned
|
||||
* *(caldav)* Reject invalid project id with error 400
|
||||
* *(editor)* Restore the current value, not the one from a previous task
|
||||
* *(files)* Use absolute path everywhere
|
||||
* *(filter)* Do not replace labels keyword when the value is 'label'
|
||||
* *(filter)* Make sure tasks are in a correct bucket and position when they are part of a date filter
|
||||
* *(filters)* Immediately propagate changes
|
||||
* *(filters)* Do not replace filter or project values when the id value resolves to undefined
|
||||
* *(filters)* Correctly transform and populate saved filter when creating and editing
|
||||
* *(home)* Explicitly use filter for tasks on home page when one is set
|
||||
* *(kanban)* Save updated position to store
|
||||
* *(kanban)* Make task creation loading spinner actually visible
|
||||
* *(kanban)* Make kanban full width on mobile
|
||||
* *(kanban)* Do not mark first bucked as done bucket in filter bucket mode
|
||||
* *(kanban)* Correctly paginate filtered kanban buckets
|
||||
* *(label)* Ignore existing ID during creation
|
||||
* *(labels)* Trigger task.updated event when removing a label from a task
|
||||
* *(labels)* Test error assertion
|
||||
* *(labels)* Remove input interactivity when label edit is disabled
|
||||
* *(labels)* Trigger task updated for bulk label task update
|
||||
* *(modal)* Make sure modal and its content scrolls properly on mobile
|
||||
* *(modal)* Do not prevent scrolling on mobile
|
||||
* *(modal)* Make scrolling on iOS Safari work
|
||||
* *(multiselect)* Make selectPlaceholder optional
|
||||
* *(notifications)* Only add project subscription as task subscription when the user is not already subscribed to the task
|
||||
* *(password)* Validate password before sending request to api
|
||||
* *(project)* Show description in title attribute without html
|
||||
* *(project)* Reset id before creating
|
||||
* *(projects)* Do not hide 6th project on project overview
|
||||
* *(projects)* Description not visible on mobile
|
||||
* *(reminders)* Notify subscribed users as well
|
||||
* *(service worker)* Use correct workbox version
|
||||
* *(subscription)* Always return task subscription when subscribed to task and project
|
||||
* *(subscriptions)* Ignore task subscription when the user is subscribed to the project
|
||||
* *(subscriptions)* Correctly inherit subscriptions
|
||||
* *(subscriptions)* Cleanup and simplify fetching subscribers for tasks and projects logic
|
||||
* *(subscriptions)* Do not panic when a task does not have a subscription
|
||||
* *(table)* Make sorting for two-word properties work
|
||||
* *(task)* Set done at date when moving a task to the done bucket
|
||||
* *(task)* Specify task index when creating multiple tasks at once
|
||||
* *(task)* Cyclomatic complexity
|
||||
* *(task)* Make print styles work when printing task detail view from kanban
|
||||
* *(task)* Multiple overlapping defer due date popups
|
||||
* *(task)* Align task title on mobile popup
|
||||
* *(task)* Dragging and dropping on mobile
|
||||
* *(task)* Add task to filter view after it was updated
|
||||
* *(task)* Cleanup old task positions and task buckets when adding an updated or created task to filter
|
||||
* *(task)* Mark related task as done from the task detail view
|
||||
* *(task)* Open focused task when pressing enter
|
||||
* *(test)* Cypress test selector
|
||||
* *(typesense)* Only fail silently when a project was not found during indexing
|
||||
* *(typesense)* Add new tasks to typesense properly
|
||||
* *(typesense)* Make sure task positions are recreated properly when updating them
|
||||
* *(typesense)* Use emplace instead of upsert to update documents
|
||||
* *(typesense)* Index tasks one by one
|
||||
* *(typesense)* Force position to always be float instead of auto-inferring
|
||||
* *(typesense)* Use typesense bulk insert, log all errors
|
||||
* *(user)* Do not create user with existing id
|
||||
* *(view)* Do not crash when saving a view
|
||||
* *(view)* Correctly resolve label for filtered views or buckets
|
||||
* *(view)* Correctly resolve bucket filter when paginating
|
||||
* *(view)* Correctly get paginated task results
|
||||
* *(views)* Add migration for filtered kanban buckets* Lint ([53d62d3](53d62d35f4488940a96d755de93ded64b8ac34a3))
|
||||
* Reset id before creating ([93f7dd6](93f7dd611ad288a149f5da5463867d224334815f))
|
||||
* Test selector ([063aa7a](063aa7afec717c3ed05be9d2ca73bde3d0bd8d35))
|
||||
|
||||
### Dependencies
|
||||
|
||||
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v5
|
||||
* *(deps)* Update dependency @kyvg/vue3-notification to v3.3.0
|
||||
* *(deps)* Update dependency @sentry/vue to v8.28.0
|
||||
* *(deps)* Update dependency @sentry/vue to v8.29.0
|
||||
* *(deps)* Update dependency @sentry/vue to v8.30.0
|
||||
* *(deps)* Update dependency axios to v1.7.7
|
||||
* *(deps)* Update dependency date-fns to v4
|
||||
* *(deps)* Update dependency dayjs to v1.11.13
|
||||
* *(deps)* Update dependency express to v4.20.0
|
||||
* *(deps)* Update dependency express to v4.21.0
|
||||
* *(deps)* Update dependency go to v1.23.1
|
||||
* *(deps)* Update dependency pinia to v2.2.2
|
||||
* *(deps)* Update dependency sortablejs to v1.15.3
|
||||
* *(deps)* Update dependency tailwindcss to v3.4.10
|
||||
* *(deps)* Update dependency tailwindcss to v3.4.11
|
||||
* *(deps)* Update dependency tailwindcss to v3.4.12
|
||||
* *(deps)* Update dependency vue to v3.5.3
|
||||
* *(deps)* Update dependency vue to v3.5.4
|
||||
* *(deps)* Update dependency vue to v3.5.5
|
||||
* *(deps)* Update dependency vue to v3.5.6
|
||||
* *(deps)* Update dependency vue-i18n to v10
|
||||
* *(deps)* Update dependency vue-i18n to v10.0.1
|
||||
* *(deps)* Update dependency vue-i18n to v9.14.0
|
||||
* *(deps)* Update dependency vue-router to v4.4.3
|
||||
* *(deps)* Update dependency vue-router to v4.4.4
|
||||
* *(deps)* Update dependency vue-router to v4.4.5
|
||||
* *(deps)* Update dependency vuemoji-picker to v0.3.1* Chore(deps): update goreleaser/nfpm docker tag to v2.40.0 (#2647)
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update github.com/wneessen/go-mail to v0.4.4
|
||||
* *(deps)* Update golangci
|
||||
* *(deps)* Update module dario.cat/mergo to v1.0.1
|
||||
* *(deps)* Update module github.com/gabriel-vasile/mimetype to v1.4.5
|
||||
* *(deps)* Update module github.com/getsentry/sentry-go to v0.29.0
|
||||
* *(deps)* Update module github.com/mattn/go-sqlite3 to v1.14.23
|
||||
* *(deps)* Update module github.com/prometheus/client_golang to v1.20.3
|
||||
* *(deps)* Update module github.com/prometheus/client_golang to v1.20.4
|
||||
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.6.1
|
||||
* *(deps)* Update module github.com/threedotslabs/watermill to v1.3.7
|
||||
* *(deps)* Update module github.com/typesense/typesense-go to v2
|
||||
* *(deps)* Update module github.com/typesense/typesense-go to v2
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.27.0
|
||||
* *(deps)* Update module golang.org/x/image to v0.20.0
|
||||
* *(deps)* Update module golang.org/x/oauth2 to v0.23.0
|
||||
* *(deps)* Update module golang.org/x/term to v0.24.0
|
||||
* *(deps)* Update module golang.org/x/text to v0.18.0
|
||||
* *(deps)* Update pnpm to v9.10.0
|
||||
* *(deps)* Update tiptap to 2.6.6
|
||||
* *(deps)* Update tiptap to v2.7.0
|
||||
* *(deps)* Update tiptap to v2.7.1
|
||||
* *(deps)* Update tiptap to v2.7.2
|
||||
* *(deps)* Update vueuse to v11
|
||||
* *(deps)* Update vueuse to v11.1.0
|
||||
* *(deps)*: update dependency flexsearch to v0.7.43 (#2095)
|
||||
* *(deps)*: update golangci/golangci-lint docker tag to v1.61.0 (#2678)
|
||||
|
||||
### Documentation
|
||||
|
||||
* *(api)* Use correct return type for the /user endpoint
|
||||
|
||||
### Features
|
||||
|
||||
* *(event)* Simplify dispatching task updated event from only a task id
|
||||
* *(navigation)* Use focus-visible for nav items
|
||||
* *(task)* Use focus-visible for task focus styles
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
* *(attachments)* Refactor building image preview
|
||||
* *(devenv)* Do not install cypress on darwin
|
||||
* *(docker)* Use new env format
|
||||
* *(docs)* Clarify usage of related model creation
|
||||
* *(errors)* Always add internal error to echo error
|
||||
* *(files)* Use absolute file path to retrieve and save files
|
||||
* *(i18n)* Update translations via Crowdin
|
||||
* *(i18n)* Update translations via Crowdin
|
||||
* *(i18n)* Update translations via Crowdin
|
||||
* *(i18n)* Update translations via Crowdin
|
||||
* *(i18n)* Update translations via Crowdin
|
||||
* *(i18n)* Update translations via Crowdin
|
||||
* *(i18n)* Update translations via Crowdin
|
||||
* *(i18n)* Update translations via Crowdin
|
||||
* *(i18n)* Update translations via Crowdin
|
||||
* *(i18n)* Update translations via Crowdin
|
||||
* *(i18n)* Update translations via Crowdin
|
||||
* *(i18n)* Update translations via Crowdin
|
||||
* *(logging)* Simplify log template string
|
||||
* *(magefile)* Use tx.Sync instead of Sync2
|
||||
* *(subscription)* Return subscription entity type using json Marshaler
|
||||
* *(tasks)* Move drag options to direct attributes instead of v-bind
|
||||
* *(typesense)* Add more debug logging
|
||||
* *(web)* Move web handler package to Vikunja
|
||||
* *(web)* Remove unused echo context
|
||||
* *(web)* Use errors.As instead of type assertion
|
||||
* *(web)* Remove redundant use of fmt.Sprintf
|
||||
* *(web)* Directly use new db session
|
||||
* *(web)* Use config directly
|
||||
* *(web)* Use web auth factory directly
|
||||
* *(web)* Use logger directly
|
||||
* *(web)* Always set internal error* Remove console.log ([40105ee](40105ee4ced980f52565baec4c3219b0ddd4f6ec))
|
||||
* Fix comment ([1df4a4e](1df4a4ea2e2ca4332347468e8973a2dcbab06ed7))
|
||||
* Add go and direnv to recommended vscode extensions ([6ab12b9](6ab12b9dd133b52ed7267b6e9334081c2f9719ca))
|
||||
* Remove console.log ([1e7d9c9](1e7d9c982d3d472e9b4082991b41e6567556f2b2))
|
||||
* Rearrange cron registers ([4857bfb](4857bfbbdb8401b6ef02b1dc8de93f2a09e8bc3a))
|
||||
|
||||
### Other
|
||||
|
||||
* *(other)* [skip ci] Updated swagger docs
|
||||
|
||||
## [0.24.2] - 2024-08-12
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
+9
-2
@@ -13,7 +13,7 @@ RUN corepack enable && \
|
||||
pnpm install && \
|
||||
pnpm run build
|
||||
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/techknowlogick/xgo:go-1.23.x AS apibuilder
|
||||
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.21.x AS apibuilder
|
||||
|
||||
RUN go install github.com/magefile/mage@latest && \
|
||||
mv /go/bin/mage /usr/local/go/bin
|
||||
@@ -35,7 +35,14 @@ RUN export PATH=$PATH:$GOPATH/bin && \
|
||||
|
||||
# The actual image
|
||||
FROM scratch
|
||||
LABEL maintainer="maintainers@vikunja.io"
|
||||
|
||||
LABEL org.opencontainers.image.authors='maintainers@vikunja.io'
|
||||
LABEL org.opencontainers.image.url='https://vikunja.io'
|
||||
LABEL org.opencontainers.image.documentation='https://vikunja.io/docs'
|
||||
LABEL org.opencontainers.image.source='https://code.vikunja.io/vikunja'
|
||||
LABEL org.opencontainers.image.licenses='AGPLv3'
|
||||
LABEL org.opencontainers.image.title='Vikunja'
|
||||
|
||||
WORKDIR /app/vikunja
|
||||
ENTRYPOINT [ "/app/vikunja/vikunja" ]
|
||||
EXPOSE 3456
|
||||
|
||||
@@ -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
+1242
-1541
File diff suppressed because it is too large
Load Diff
@@ -29,4 +29,9 @@ in {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
services.mailpit = {
|
||||
enable = true;
|
||||
package = pkgs-unstable.mailpit;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 .modal-header')
|
||||
cy.get('.modal-mask .modal-container .modal-content .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 .modal-header')
|
||||
cy.get('.modal-mask .modal-container .modal-content .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 .modal-header')
|
||||
cy.get('.modal-mask .modal-container .modal-content .header')
|
||||
.should('contain', 'Delete this task')
|
||||
cy.get('.modal-mask .modal-container .modal-content .actions .button')
|
||||
.contains('Do it!')
|
||||
|
||||
+50
-50
@@ -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.2.0",
|
||||
"@intlify/unplugin-vue-i18n": "5.0.0",
|
||||
"@kyvg/vue3-notification": "3.3.0",
|
||||
"@sentry/tracing": "7.114.0",
|
||||
"@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",
|
||||
"@sentry/vue": "8.30.0",
|
||||
"@tiptap/core": "2.7.1",
|
||||
"@tiptap/extension-blockquote": "2.7.1",
|
||||
"@tiptap/extension-bold": "2.7.1",
|
||||
"@tiptap/extension-bullet-list": "2.7.1",
|
||||
"@tiptap/extension-code": "2.7.1",
|
||||
"@tiptap/extension-code-block": "2.7.1",
|
||||
"@tiptap/extension-code-block-lowlight": "2.7.1",
|
||||
"@tiptap/extension-document": "2.7.1",
|
||||
"@tiptap/extension-dropcursor": "2.7.1",
|
||||
"@tiptap/extension-gapcursor": "2.7.1",
|
||||
"@tiptap/extension-hard-break": "2.7.1",
|
||||
"@tiptap/extension-heading": "2.7.1",
|
||||
"@tiptap/extension-history": "2.7.1",
|
||||
"@tiptap/extension-horizontal-rule": "2.7.1",
|
||||
"@tiptap/extension-image": "2.7.1",
|
||||
"@tiptap/extension-italic": "2.7.1",
|
||||
"@tiptap/extension-link": "2.7.1",
|
||||
"@tiptap/extension-list-item": "2.7.1",
|
||||
"@tiptap/extension-ordered-list": "2.7.1",
|
||||
"@tiptap/extension-paragraph": "2.7.1",
|
||||
"@tiptap/extension-placeholder": "2.7.1",
|
||||
"@tiptap/extension-strike": "2.7.1",
|
||||
"@tiptap/extension-table": "2.7.1",
|
||||
"@tiptap/extension-table-cell": "2.7.1",
|
||||
"@tiptap/extension-table-header": "2.7.1",
|
||||
"@tiptap/extension-table-row": "2.7.1",
|
||||
"@tiptap/extension-task-item": "2.7.1",
|
||||
"@tiptap/extension-task-list": "2.7.1",
|
||||
"@tiptap/extension-text": "2.7.1",
|
||||
"@tiptap/extension-typography": "2.7.1",
|
||||
"@tiptap/extension-underline": "2.7.1",
|
||||
"@tiptap/pm": "2.7.1",
|
||||
"@tiptap/suggestion": "2.7.1",
|
||||
"@tiptap/vue-3": "2.7.1",
|
||||
"@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.7",
|
||||
"dompurify": "3.1.6",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"flatpickr": "4.6.13",
|
||||
"flexsearch": "0.7.43",
|
||||
@@ -113,15 +113,15 @@
|
||||
"pinia": "2.2.2",
|
||||
"register-service-worker": "1.7.2",
|
||||
"sortablejs": "1.15.3",
|
||||
"tailwindcss": "3.4.13",
|
||||
"tailwindcss": "3.4.12",
|
||||
"tippy.js": "6.3.7",
|
||||
"ufo": "1.5.4",
|
||||
"vue": "3.5.10",
|
||||
"vue": "3.5.6",
|
||||
"vue-advanced-cropper": "2.8.9",
|
||||
"vue-flatpickr-component": "11.0.5",
|
||||
"vue-i18n": "10.0.3",
|
||||
"vue-i18n": "10.0.1",
|
||||
"vue-router": "4.4.5",
|
||||
"vuemoji-picker": "0.3.1",
|
||||
"vuemoji-picker": "0.2.1",
|
||||
"workbox-precaching": "7.1.0",
|
||||
"zhyswan-vuedraggable": "4.1.3"
|
||||
},
|
||||
@@ -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.3",
|
||||
"@types/is-touch-device": "1.0.2",
|
||||
"@types/lodash.clonedeep": "4.5.9",
|
||||
"@types/node": "20.16.10",
|
||||
"@types/node": "20.16.5",
|
||||
"@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.24.0",
|
||||
"caniuse-lite": "1.0.30001664",
|
||||
"browserslist": "4.23.3",
|
||||
"caniuse-lite": "1.0.30001660",
|
||||
"csstype": "3.1.3",
|
||||
"cypress": "13.15.0",
|
||||
"esbuild": "0.24.0",
|
||||
"cypress": "13.14.2",
|
||||
"esbuild": "0.23.1",
|
||||
"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.5",
|
||||
"rollup": "4.21.3",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"sass": "1.79.4",
|
||||
"sass": "1.79.1",
|
||||
"start-server-and-test": "2.0.8",
|
||||
"typescript": "5.6.2",
|
||||
"unplugin-inject-preload": "2.0.5",
|
||||
"vite": "5.4.8",
|
||||
"vite": "5.4.6",
|
||||
"vite-plugin-pwa": "0.20.5",
|
||||
"vite-plugin-sentry": "1.4.0",
|
||||
"vite-svg-loader": "5.1.0",
|
||||
|
||||
Generated
+814
-802
File diff suppressed because it is too large
Load Diff
@@ -3,22 +3,33 @@
|
||||
v-cy="'checkbox'"
|
||||
class="base-checkbox"
|
||||
>
|
||||
<label
|
||||
class="base-checkbox__label"
|
||||
<input
|
||||
:id="checkboxId"
|
||||
type="checkbox"
|
||||
class="is-sr-only"
|
||||
:checked="modelValue"
|
||||
:disabled="disabled || undefined"
|
||||
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="is-sr-only"
|
||||
:checked="modelValue"
|
||||
:disabled="disabled || undefined"
|
||||
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
|
||||
|
||||
<slot
|
||||
name="label"
|
||||
:checkbox-id="checkboxId"
|
||||
>
|
||||
<label
|
||||
:for="checkboxId"
|
||||
class="base-checkbox__label"
|
||||
>
|
||||
<slot />
|
||||
</label>
|
||||
<slot />
|
||||
</label>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue'
|
||||
import {createRandomID} from '@/helpers/randomId'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
modelValue?: boolean,
|
||||
disabled: boolean,
|
||||
@@ -27,8 +38,10 @@ 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>
|
||||
@@ -40,7 +53,7 @@ const emit = defineEmits<{
|
||||
}
|
||||
|
||||
.base-checkbox:has(input:disabled) .base-checkbox__label {
|
||||
cursor: not-allowed;
|
||||
cursor:not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,97 +0,0 @@
|
||||
<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,6 +38,7 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fancy-checkbox {
|
||||
display: inline-block;
|
||||
@@ -69,8 +70,8 @@ const emit = defineEmits<{
|
||||
}
|
||||
}
|
||||
|
||||
.fancy-checkbox:hover input:not(:disabled) + .fancy-checkbox__icon,
|
||||
.fancy-checkbox input:checked + .fancy-checkbox__icon {
|
||||
.fancy-checkbox:not(:has(input:disabled)):hover .fancy-checkbox__icon,
|
||||
.fancy-checkbox:has(input:checked) .fancy-checkbox__icon {
|
||||
--stroke-color: var(--primary);
|
||||
}
|
||||
</style>
|
||||
@@ -79,13 +80,13 @@ const emit = defineEmits<{
|
||||
// Since css-has-pseudo doesn't work with deep classes,
|
||||
// the following rules can't be scoped
|
||||
|
||||
.fancy-checkbox :not(input:checked) + .fancy-checkbox__icon {
|
||||
.fancy-checkbox:has(:not(input:checked)) .fancy-checkbox__icon {
|
||||
path {
|
||||
transition-delay: 0.05s;
|
||||
}
|
||||
}
|
||||
|
||||
.fancy-checkbox input:checked + .fancy-checkbox__icon {
|
||||
.fancy-checkbox:has(input:checked) .fancy-checkbox__icon {
|
||||
path {
|
||||
stroke-dashoffset: 60;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
}"
|
||||
>
|
||||
<slot>
|
||||
<div class="modal-header">
|
||||
<div class="header">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div class="content">
|
||||
@@ -140,7 +140,7 @@ $modal-width: 1024px;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
.header {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -241,14 +241,6 @@ $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,49 +1,89 @@
|
||||
<template>
|
||||
<BasePagination
|
||||
:total-pages="totalPages"
|
||||
:current-page="currentPage"
|
||||
<nav
|
||||
v-if="totalPages > 1"
|
||||
aria-label="pagination"
|
||||
class="pagination is-centered p-4"
|
||||
role="navigation"
|
||||
>
|
||||
<template #previous="{ disabled }">
|
||||
<RouterLink
|
||||
:disabled="disabled || undefined"
|
||||
:to="getRouteForPagination(currentPage - 1)"
|
||||
class="pagination-previous"
|
||||
<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}`"
|
||||
>
|
||||
{{ $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>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BasePagination from '@/components/base/BasePagination.vue'
|
||||
import {computed} from 'vue'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
const props = 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,
|
||||
@@ -55,4 +95,20 @@ function getRouteForPagination(page = 1, type = null) {
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
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>
|
||||
@@ -1,55 +0,0 @@
|
||||
<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>
|
||||
@@ -569,15 +569,15 @@ async function addTaskToBucket(bucketId: IBucket['id']) {
|
||||
toggleShowNewTaskInput(bucketId)
|
||||
newTaskText.value = ''
|
||||
kanbanStore.addTaskToBucket(task)
|
||||
scrollTaskContainerToBottom(bucketId)
|
||||
scrollTaskContainerToTop(bucketId)
|
||||
}
|
||||
|
||||
function scrollTaskContainerToBottom(bucketId: IBucket['id']) {
|
||||
function scrollTaskContainerToTop(bucketId: IBucket['id']) {
|
||||
const bucketEl = taskContainerRefs.value[bucketId]
|
||||
if (!bucketEl) {
|
||||
return
|
||||
}
|
||||
bucketEl.scrollTop = bucketEl.scrollHeight
|
||||
bucketEl.scrollTop = 0
|
||||
}
|
||||
|
||||
async function createNewBucket() {
|
||||
|
||||
@@ -180,7 +180,7 @@ import ProgressBar from '@/components/misc/ProgressBar.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
||||
import AttachmentService from '@/services/attachment'
|
||||
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
|
||||
import {canPreview} from '@/models/attachment'
|
||||
import type {IAttachment} from '@/modelTypes/IAttachment'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
|
||||
@@ -274,10 +274,6 @@ 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,8 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="enabled"
|
||||
ref="commentsRef"
|
||||
class="content details comments-container"
|
||||
class="content details"
|
||||
>
|
||||
<h3
|
||||
v-if="canWrite || comments.length > 0"
|
||||
@@ -16,13 +15,14 @@
|
||||
<div class="comments">
|
||||
<span
|
||||
v-if="taskCommentService.loading && saving === null && !creating"
|
||||
class="is-flex is-align-items-center my-4 ml-2"
|
||||
class="is-inline-flex is-align-items-center"
|
||||
>
|
||||
<span class="loader is-inline-block mr-2" />
|
||||
{{ $t('task.comment.loading') }}
|
||||
</span>
|
||||
<div
|
||||
v-for="c in comments"
|
||||
:id="`comment-${c.id}`"
|
||||
:key="c.id"
|
||||
class="media comment"
|
||||
>
|
||||
@@ -57,6 +57,13 @@
|
||||
>
|
||||
· {{ $t('task.comment.edited', {date: formatDateSince(c.updated)}) }}
|
||||
</span>
|
||||
<a
|
||||
v-tooltip="$t('task.comment.permalink')"
|
||||
:href="`#comment-${c.id}`"
|
||||
class="comment-permalink"
|
||||
:title="$t('task.comment.permalink')"
|
||||
@click.prevent.stop="copy(getCommentUrl(`${c.id}`))"
|
||||
><Icon icon="link" /></a>
|
||||
<CustomTransition name="fade">
|
||||
<span
|
||||
v-if="
|
||||
@@ -108,14 +115,6 @@
|
||||
/>
|
||||
</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"
|
||||
@@ -169,7 +168,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<Modal
|
||||
:enabled="showDeleteModal"
|
||||
@close="showDeleteModal = false"
|
||||
@@ -195,7 +193,6 @@ 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'
|
||||
@@ -210,6 +207,7 @@ import {getAvatarUrl, getDisplayName} from '@/models/user'
|
||||
import {useConfigStore} from '@/stores/config'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import Reactions from '@/components/input/Reactions.vue'
|
||||
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
taskId: number,
|
||||
@@ -218,6 +216,8 @@ const props = withDefaults(defineProps<{
|
||||
canWrite: true,
|
||||
})
|
||||
|
||||
const copy = useCopyToClipboard()
|
||||
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
const configStore = useConfigStore()
|
||||
const authStore = useAuthStore()
|
||||
@@ -255,10 +255,6 @@ 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>[] = []
|
||||
@@ -284,21 +280,12 @@ async function loadComments(taskId: ITask['id']) {
|
||||
newComment.taskId = taskId
|
||||
commentEdit.taskId = taskId
|
||||
commentToDelete.taskId = 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)
|
||||
comments.value = await taskCommentService.getAll({taskId})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.taskId,
|
||||
() => {
|
||||
currentPage.value = 1 // Reset to first page when task changes
|
||||
loadComments(props.taskId)
|
||||
},
|
||||
loadComments,
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
@@ -390,6 +377,10 @@ async function deleteComment(commentToDelete: ITaskComment) {
|
||||
showDeleteModal.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getCommentUrl(commentId: string) {
|
||||
return `${frontendUrl.value}${location.pathname}${location.search}#comment-${commentId}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -417,10 +408,23 @@ async function deleteComment(commentToDelete: ITaskComment) {
|
||||
}
|
||||
|
||||
|
||||
span {
|
||||
span,
|
||||
.comment-permalink {
|
||||
font-size: .75rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.comment-permalink {
|
||||
font-size: 1rem;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.25rem;
|
||||
border-radius: 1rem;
|
||||
color: var(--grey, hsl(0, 0%, 48%));
|
||||
}
|
||||
.comment-permalink:hover {
|
||||
color: var(--grey-dark, hsl(0, 0%, 29%));
|
||||
border-color: var(--grey-dark, hsl(0, 0%, 29%));
|
||||
}
|
||||
}
|
||||
|
||||
.image.is-avatar {
|
||||
@@ -430,8 +434,4 @@ async function deleteComment(commentToDelete: ITaskComment) {
|
||||
.media-content {
|
||||
width: calc(100% - 48px - 2rem);
|
||||
}
|
||||
|
||||
.comments-container {
|
||||
scroll-margin-top: 4rem;
|
||||
}
|
||||
</style>
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, shallowReactive, watchEffect} from 'vue'
|
||||
import AttachmentService from '@/services/attachment'
|
||||
import AttachmentService, {PREVIEW_SIZE} from '@/services/attachment'
|
||||
import type {IAttachment} from '@/modelTypes/IAttachment'
|
||||
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
|
||||
import {canPreview} from '@/models/attachment'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: IAttachment
|
||||
@@ -33,13 +33,9 @@ const blobUrl = ref<string | undefined>(undefined)
|
||||
|
||||
watchEffect(async () => {
|
||||
if (props.modelValue && canPreview(props.modelValue)) {
|
||||
blobUrl.value = await attachmentService.getBlobUrl(props.modelValue)
|
||||
blobUrl.value = await attachmentService.getBlobUrl(props.modelValue, PREVIEW_SIZE.MD)
|
||||
}
|
||||
})
|
||||
|
||||
function canPreview(attachment: IAttachment): boolean {
|
||||
return SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
:is-done="task.done"
|
||||
/>
|
||||
<BaseButton
|
||||
v-if="hasClose"
|
||||
class="close"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
@@ -34,7 +33,6 @@
|
||||
{{ task.title.trim() }}
|
||||
</h1>
|
||||
<BaseButton
|
||||
v-if="hasClose"
|
||||
class="close"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
@@ -80,10 +78,8 @@ 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<{
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, computed, watch} from 'vue'
|
||||
import {computed, ref, watch} from 'vue'
|
||||
import {useRouter} from 'vue-router'
|
||||
|
||||
import PriorityLabel from '@/components/tasks/partials/PriorityLabel.vue'
|
||||
@@ -105,9 +105,9 @@ import ChecklistSummary from './ChecklistSummary.vue'
|
||||
import {getHexColor} from '@/models/task'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
|
||||
import AttachmentService from '@/services/attachment'
|
||||
import AttachmentService, {PREVIEW_SIZE} from '@/services/attachment'
|
||||
|
||||
import {formatDateLong, formatISO, formatDateSince} from '@/helpers/time/formatDate'
|
||||
import {formatDateLong, formatDateSince, formatISO} from '@/helpers/time/formatDate'
|
||||
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
||||
import {useTaskStore} from '@/stores/tasks'
|
||||
import AssigneeList from '@/components/tasks/partials/AssigneeList.vue'
|
||||
@@ -165,7 +165,7 @@ async function maybeDownloadCoverImage() {
|
||||
}
|
||||
|
||||
const attachmentService = new AttachmentService()
|
||||
coverImageBlobUrl.value = await attachmentService.getBlobUrl(attachment)
|
||||
coverImageBlobUrl.value = await attachmentService.getBlobUrl(attachment, PREVIEW_SIZE.LG)
|
||||
}
|
||||
|
||||
watch(
|
||||
|
||||
@@ -91,9 +91,8 @@
|
||||
</time>
|
||||
</BaseButton>
|
||||
</template>
|
||||
<template #content="{isOpen}">
|
||||
<template #content>
|
||||
<DeferTask
|
||||
v-if="isOpen"
|
||||
v-model="task"
|
||||
/>
|
||||
</template>
|
||||
@@ -353,7 +352,7 @@ function hasTextSelected() {
|
||||
|
||||
function openTaskDetail(event: MouseEvent | KeyboardEvent) {
|
||||
if (event.target instanceof HTMLElement) {
|
||||
const isInteractiveElement = event.target.closest('a, button, .favorite, [role="button"]')
|
||||
const isInteractiveElement = event.target.closest('a, button, input, .favorite, [role="button"]')
|
||||
if (isInteractiveElement || hasTextSelected()) {
|
||||
return
|
||||
}
|
||||
@@ -378,7 +377,7 @@ function openTaskDetail(event: MouseEvent | KeyboardEvent) {
|
||||
background-color: var(--grey-100);
|
||||
}
|
||||
|
||||
&:has(*:focus-visible) {
|
||||
&:focus-within {
|
||||
box-shadow: 0 0 0 2px hsla(var(--primary-hsl), 0.5);
|
||||
|
||||
a.task-link {
|
||||
@@ -388,7 +387,7 @@ function openTaskDetail(event: MouseEvent | KeyboardEvent) {
|
||||
|
||||
@supports not selector(:focus-within) {
|
||||
:focus {
|
||||
box-shadow: 0 0 0 2px hsla(var(--primary-hsl), 0.5);
|
||||
box-shadow: 0 0 0 2px hsla(var(--primary-hsl), 0.5);
|
||||
|
||||
a.task-link {
|
||||
box-shadow: none;
|
||||
@@ -412,15 +411,6 @@ function openTaskDetail(event: MouseEvent | KeyboardEvent) {
|
||||
.dueDate {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
|
||||
&:focus-visible {
|
||||
box-shadow: none;
|
||||
|
||||
time {
|
||||
box-shadow: 0 0 0 1px hsla(var(--primary-hsl), 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overdue {
|
||||
@@ -466,7 +456,6 @@ function openTaskDetail(event: MouseEvent | KeyboardEvent) {
|
||||
text-align: center;
|
||||
width: 27px;
|
||||
transition: opacity $transition, color $transition;
|
||||
border-radius: $radius;
|
||||
|
||||
&:hover {
|
||||
color: var(--warning);
|
||||
|
||||
@@ -25,6 +25,7 @@ export const SUPPORTED_LOCALES = {
|
||||
'pt-BR': 'Português Brasileiro',
|
||||
'hr-HR': 'Hrvatski',
|
||||
'uk-UA': 'Українська',
|
||||
'lt-LT': 'Lietuvių Kalba',
|
||||
// IMPORTANT: Also add new languages to useDayjsLanguageSync
|
||||
} as const
|
||||
|
||||
|
||||
@@ -848,7 +848,8 @@
|
||||
"delete": "Delete this comment",
|
||||
"deleteText1": "Are you sure you want to delete this comment?",
|
||||
"deleteSuccess": "The comment was deleted successfully.",
|
||||
"addedSuccess": "The comment was added successfully."
|
||||
"addedSuccess": "The comment was added successfully.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Defer due date",
|
||||
|
||||
@@ -25,6 +25,7 @@ export const DAYJS_LOCALE_MAPPING = {
|
||||
'pt-BR': 'pt',
|
||||
'hr-HR': 'hr',
|
||||
'uk-UA': 'uk',
|
||||
'lt-LT': 'lt',
|
||||
} as Record<SupportedLocale, ISOLanguage>
|
||||
|
||||
export const DAYJS_LANGUAGE_IMPORTS = {
|
||||
@@ -48,6 +49,7 @@ export const DAYJS_LANGUAGE_IMPORTS = {
|
||||
'sl-si': () => import('dayjs/locale/sl'),
|
||||
'pt-br': () => import('dayjs/locale/pt-br'),
|
||||
'uk-ua': () => import('dayjs/locale/uk'),
|
||||
'lt-lt': () => import('dayjs/locale/lt'),
|
||||
} as Record<SupportedLocale, () => Promise<ILocale>>
|
||||
|
||||
export function useDayjsLanguageSync(dayjsGlobal: typeof dayjs) {
|
||||
|
||||
@@ -7,6 +7,10 @@ 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
|
||||
|
||||
@@ -5,6 +5,13 @@ import type { IAttachment } from '@/modelTypes/IAttachment'
|
||||
|
||||
import {downloadBlob} from '@/helpers/downloadBlob'
|
||||
|
||||
export enum PREVIEW_SIZE {
|
||||
SM = 'sm',
|
||||
MD = 'md',
|
||||
LG = 'lg',
|
||||
XL = 'xl',
|
||||
}
|
||||
|
||||
export default class AttachmentService extends AbstractService<IAttachment> {
|
||||
constructor() {
|
||||
super({
|
||||
@@ -37,8 +44,13 @@ export default class AttachmentService extends AbstractService<IAttachment> {
|
||||
return data
|
||||
}
|
||||
|
||||
getBlobUrl(model: IAttachment) {
|
||||
return AbstractService.prototype.getBlobUrl.call(this, '/tasks/' + model.taskId + '/attachments/' + model.id)
|
||||
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)
|
||||
}
|
||||
|
||||
async download(model: IAttachment) {
|
||||
|
||||
@@ -194,8 +194,8 @@ export const useKanbanStore = defineStore('kanban', () => {
|
||||
...oldBucket,
|
||||
count: (oldBucket?.count || 0) + 1,
|
||||
tasks: [
|
||||
...oldBucket.tasks,
|
||||
task,
|
||||
...oldBucket.tasks,
|
||||
],
|
||||
}
|
||||
buckets.value[bucketIndex] = newBucket
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
ref="heading"
|
||||
:task="task"
|
||||
:can-write="canWrite"
|
||||
:has-close="isModal"
|
||||
@update:task="Object.assign(task, $event)"
|
||||
@close="$emit('close')"
|
||||
/>
|
||||
@@ -573,10 +572,8 @@
|
||||
</template>
|
||||
|
||||
<template #text>
|
||||
<p class="tw-text-balance !tw-mb-0">
|
||||
{{ $t('task.detail.delete.text1') }}
|
||||
</p>
|
||||
<p class="tw-text-balance">
|
||||
<p>
|
||||
{{ $t('task.detail.delete.text1') }}<br>
|
||||
{{ $t('task.detail.delete.text2') }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
@@ -64,6 +64,7 @@ require (
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/swaggo/swag v1.16.3
|
||||
github.com/tkuchiki/go-timezone v0.2.3
|
||||
github.com/typesense/typesense-go v1.1.0
|
||||
github.com/typesense/typesense-go/v2 v2.0.0
|
||||
github.com/ulule/limiter/v3 v3.11.2
|
||||
github.com/wneessen/go-mail v0.4.4
|
||||
@@ -78,7 +79,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.8.1-0.20241105013731-313dedef864f
|
||||
src.techknowlogick.com/xgo v1.7.1-0.20240403232151-e01c4fbef884
|
||||
src.techknowlogick.com/xormigrate v1.7.1
|
||||
xorm.io/builder v0.3.13
|
||||
xorm.io/xorm v1.3.9
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
@@ -19,6 +21,8 @@ github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||
github.com/ThreeDotsLabs/watermill v1.3.5 h1:50JEPEhMGZQMh08ct0tfO1PsgMOAOhV3zxK2WofkbXg=
|
||||
github.com/ThreeDotsLabs/watermill v1.3.5/go.mod h1:O/u/Ptyrk5MPTxSeWM5vzTtZcZfxXfO9PK9eXTYiFZY=
|
||||
github.com/ThreeDotsLabs/watermill v1.3.7 h1:NV0PSTmuACVEOV4dMxRnmGXrmbz8U83LENOvpHekN7o=
|
||||
github.com/ThreeDotsLabs/watermill v1.3.7/go.mod h1:lBnrLbxOjeMRgcJbv+UiZr8Ylz8RkJ4m6i/VN/Nk+to=
|
||||
github.com/adlio/trello v1.12.0 h1:JqOE2GFHQ9YtEviRRRSnicSxPbt4WFOxhqXzjMOw8lw=
|
||||
@@ -50,6 +54,8 @@ github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXye
|
||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
|
||||
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
||||
@@ -91,12 +97,18 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
|
||||
github.com/ganigeorgiev/fexpr v0.4.1 h1:hpUgbUEEWIZhSDBtf4M9aUNfQQ0BZkGRaMePy7Gcx5k=
|
||||
github.com/ganigeorgiev/fexpr v0.4.1/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
|
||||
github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k=
|
||||
github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg=
|
||||
github.com/getsentry/sentry-go v0.29.0 h1:YtWluuCFg9OfcqnaujpY918N/AhCCwarIDWOYSBAjCA=
|
||||
github.com/getsentry/sentry-go v0.29.0/go.mod h1:jhPesDAL0Q0W2+2YEuVOvdWmVtdsr1+jtBrlDEVWwLY=
|
||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
@@ -270,6 +282,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible h1:q7DbyV+sFjEoTuuUdRDNl2nlyfztkZgxVVCV7JhzIkY=
|
||||
@@ -284,8 +298,6 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/labstack/echo-jwt/v4 v4.2.0 h1:odSISV9JgcSCuhgQSV/6Io3i7nUmfM/QkBeR5GVJj5c=
|
||||
github.com/labstack/echo-jwt/v4 v4.2.0/go.mod h1:MA2RqdXdEn4/uEglx0HcUOgQSyBaTh5JcaHIan3biwU=
|
||||
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
||||
@@ -329,6 +341,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
|
||||
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
@@ -385,14 +399,28 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
|
||||
github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
|
||||
github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU=
|
||||
github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
|
||||
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
@@ -426,6 +454,8 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
|
||||
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
|
||||
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
@@ -471,7 +501,8 @@ github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
|
||||
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
|
||||
github.com/tkuchiki/go-timezone v0.2.3 h1:D3TVdIPrFsu9lxGxqNX2wsZwn1MZtTqTW0mdevMozHc=
|
||||
github.com/tkuchiki/go-timezone v0.2.3/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY=
|
||||
github.com/typesense/typesense-go/v2 v2.0.0 h1:+MksOnrVioDqsGpz8RXkOUqhVN+yFxZwJlGDQHr/64I=
|
||||
github.com/typesense/typesense-go v1.1.0 h1:QocehDarVXRArMIosPIdawiVFZZbnRkPJxwnAGOFkzw=
|
||||
github.com/typesense/typesense-go v1.1.0/go.mod h1:KcPODU7ltrcUFC/gygMTkAAfZ9M8/q6ayrdl1MnE1kI=
|
||||
github.com/typesense/typesense-go/v2 v2.0.0/go.mod h1:7V1ZBSfmdciL6yb2bPtWha+W53gV5WZhyOSpVgDJfao=
|
||||
github.com/ulule/limiter/v3 v3.11.2 h1:P4yOrxoEMJbOTfRJR2OzjL90oflzYPPmWg+dvwN2tHA=
|
||||
github.com/ulule/limiter/v3 v3.11.2/go.mod h1:QG5GnFOCV+k7lrL5Y8kgEeeflPH3+Cviqlqa8SVSQxI=
|
||||
@@ -529,11 +560,15 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
|
||||
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@@ -561,6 +596,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -569,6 +606,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -598,12 +637,16 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -613,6 +656,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -650,6 +695,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -723,8 +770,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.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/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/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=
|
||||
|
||||
+21
-13
@@ -464,6 +464,26 @@ func (Build) Build() {
|
||||
runAndStreamOutput("go", "build", Goflags[0], "-tags", Tags, "-ldflags", "-s -w "+Ldflags, "-o", Executable)
|
||||
}
|
||||
|
||||
func (Build) SaveVersionToFile() error {
|
||||
// Open the file for writing. If the file doesn't exist, create it.
|
||||
// If it exists, truncate it.
|
||||
file, err := os.Create("VERSION")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating VERSION file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Write the version number to the file
|
||||
_, err = file.WriteString(VersionNumber)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing to VERSION file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Version number saved successfully to VERSION file")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Release mg.Namespace
|
||||
|
||||
// Runs all steps in the right order to create release packages for various platforms
|
||||
@@ -547,19 +567,7 @@ func (Release) Windows() error {
|
||||
|
||||
// Builds binaries for linux
|
||||
func (Release) Linux() error {
|
||||
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, ","))
|
||||
return runXgo("linux/*")
|
||||
}
|
||||
|
||||
// Builds binaries for darwin
|
||||
|
||||
+16
-1
@@ -17,11 +17,14 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/modules/dump"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -29,6 +32,8 @@ func init() {
|
||||
rootCmd.AddCommand(dumpCmd)
|
||||
}
|
||||
|
||||
var dumpPathFlag string
|
||||
|
||||
var dumpCmd = &cobra.Command{
|
||||
Use: "dump",
|
||||
Short: "Dump all vikunja data into a zip file. Includes config, files and db.",
|
||||
@@ -37,8 +42,18 @@ var dumpCmd = &cobra.Command{
|
||||
},
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
filename := "vikunja-dump_" + time.Now().Format("2006-01-02_15-03-05") + ".zip"
|
||||
if err := dump.Dump(filename); err != nil {
|
||||
|
||||
path := config.ServiceRootpath.GetString()
|
||||
if dumpPathFlag != "" {
|
||||
path = dumpPathFlag
|
||||
}
|
||||
|
||||
if err := dump.Dump(filepath.Join(path, filename)); err != nil {
|
||||
log.Critical(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
dumpCmd.Flags().StringVarP(&dumpPathFlag, "path", "p", "", "The folder path where the dump file should be saved. Vikunja will use the configured root path or the binary location if the flag is not provided.")
|
||||
}
|
||||
|
||||
+1
-6
@@ -22,7 +22,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
@@ -59,12 +58,8 @@ func (*File) TableName() string {
|
||||
}
|
||||
|
||||
func (f *File) getAbsoluteFilePath() string {
|
||||
base := ""
|
||||
if !strings.HasPrefix(config.FilesBasePath.GetString(), "/") {
|
||||
base = config.ServiceRootpath.GetString()
|
||||
}
|
||||
return filepath.Join(
|
||||
base,
|
||||
config.ServiceRootpath.GetString(),
|
||||
config.FilesBasePath.GetString(),
|
||||
strconv.FormatInt(f.ID, 10),
|
||||
)
|
||||
|
||||
+3
-3
@@ -26,13 +26,13 @@ import (
|
||||
)
|
||||
|
||||
// ErrFmt holds the format for all the console logging
|
||||
const ErrFmt = `${time_rfc3339}: ${level} ` + "\t" + `▶ ${prefix} ${short_file}:${line}`
|
||||
const ErrFmt = `${time_rfc3339_nano}: ${level} ` + "\t" + `▶ ${prefix} ${short_file}:${line}`
|
||||
|
||||
// WebFmt holds the format for all logging related to web requests
|
||||
const WebFmt = `${time_rfc3339}: WEB ` + "\t" + `▶ ${remote_ip} ${id} ${method} ${status} ${uri} ${latency_human} - ${user_agent}`
|
||||
const WebFmt = `${time_rfc3339_nano}: WEB ` + "\t" + `▶ ${remote_ip} ${id} ${method} ${status} ${uri} ${latency_human} - ${user_agent}`
|
||||
|
||||
// Fmt is the general log format
|
||||
const Fmt = `%{color}%{time:` + time.RFC3339 + `}: %{level}` + "\t" + `▶ %{id:03x}%{color:reset} %{message}`
|
||||
const Fmt = `%{color}%{time:` + time.RFC3339Nano + `}: %{level}` + "\t" + `▶ %{shortpkg}/%{shortfunc} %{id:03x}%{color:reset} %{message}`
|
||||
|
||||
const logModule = `vikunja`
|
||||
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-present Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee 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 Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type projectView20240919130957BucketConfiguration struct {
|
||||
Title string `json:"title"`
|
||||
Filter string `json:"filter"`
|
||||
}
|
||||
|
||||
type projectView20240919130957Lowercase struct {
|
||||
ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"view"`
|
||||
BucketConfiguration []*projectView20240919130957BucketConfiguration `xorm:"json" json:"bucket_configuration"`
|
||||
}
|
||||
|
||||
func (projectView20240919130957Lowercase) TableName() string {
|
||||
return "project_views"
|
||||
}
|
||||
|
||||
type projectView20240919130957TitleCase struct {
|
||||
ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"view"`
|
||||
BucketConfiguration []*struct {
|
||||
Title string `json:"Title"`
|
||||
Filter string `json:"Filter"`
|
||||
} `xorm:"json" json:"bucket_configuration"`
|
||||
}
|
||||
|
||||
func (projectView20240919130957TitleCase) TableName() string {
|
||||
return "project_views"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20240919130957",
|
||||
Description: "",
|
||||
Migrate: func(tx *xorm.Engine) (err error) {
|
||||
oldViews := []*projectView20240919130957TitleCase{}
|
||||
// 1 is manual
|
||||
err = tx.Where("bucket_configuration_mode != 1 AND view_kind = 3").Find(&oldViews)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(oldViews) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, view := range oldViews {
|
||||
newView := &projectView20240919130957Lowercase{
|
||||
ID: view.ID,
|
||||
BucketConfiguration: make([]*projectView20240919130957BucketConfiguration, 0),
|
||||
}
|
||||
|
||||
for _, bc := range view.BucketConfiguration {
|
||||
newView.BucketConfiguration = append(newView.BucketConfiguration, &projectView20240919130957BucketConfiguration{
|
||||
Filter: bc.Filter,
|
||||
Title: bc.Title,
|
||||
})
|
||||
}
|
||||
_, err = tx.Where("id = ?", view.ID).Update(newView)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -36,7 +36,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
@@ -107,10 +106,7 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
|
||||
|
||||
// Save the file id with the user
|
||||
u.ExportFileID = exportFile.ID
|
||||
_, err = s.
|
||||
Where("id = ?", u.ID).
|
||||
Cols("export_file_id").
|
||||
Update(u)
|
||||
_, err = s.Cols("export_file_id").Update(u)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
+48
-8
@@ -533,10 +533,55 @@ func (l *AddTaskToTypesense) Handle(msg *message.Message) (err error) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
task := make(map[int64]*Task, 1)
|
||||
task[event.Task.ID] = event.Task // Will be filled with all data by the Typesense connector
|
||||
positionsMap, err := getPositionsForTask(s, event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return reindexTasksInTypesense(s, task)
|
||||
bucketsMap, err := getBucketsForTask(s, event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ttask, err := getTypesenseTaskForTask(s, event.Task, nil, positionsMap, bucketsMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = typesenseClient.Collection("tasks").
|
||||
Documents().
|
||||
Create(context.Background(), ttask)
|
||||
return
|
||||
}
|
||||
|
||||
func getPositionsForTask(s *xorm.Session, event *TaskCreatedEvent) (positionsMap map[int64][]*TaskPositionWithView, err error) {
|
||||
positions := []*TaskPositionWithView{}
|
||||
err = s.
|
||||
Table("project_views").
|
||||
Where("project_views.project_id = ?", event.Task.ProjectID).
|
||||
Join("LEFT", "task_positions", "project_views.id = task_positions.project_view_id AND task_positions.task_id = ?", event.Task.ID).
|
||||
Find(&positions)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
positionsMap = make(map[int64][]*TaskPositionWithView, 1)
|
||||
positionsMap[event.Task.ID] = positions
|
||||
return
|
||||
}
|
||||
|
||||
func getBucketsForTask(s *xorm.Session, event *TaskCreatedEvent) (bucketsMap map[int64][]*TaskBucket, err error) {
|
||||
buckets := []*TaskBucket{}
|
||||
err = s.
|
||||
Where("task_id = ?", event.Task.ID).
|
||||
Find(&buckets)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bucketsMap = make(map[int64][]*TaskBucket, 1)
|
||||
bucketsMap[event.Task.ID] = buckets
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateTaskInTypesense represents a listener
|
||||
@@ -727,11 +772,6 @@ func (l *UpdateTaskInSavedFilterViews) Handle(msg *message.Message) (err error)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
task := make(map[int64]*Task, 1)
|
||||
task[event.Task.ID] = event.Task // Will be filled with all data by the Typesense connector
|
||||
|
||||
return reindexTasksInTypesense(s, task)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+24
-61
@@ -19,8 +19,6 @@ package models
|
||||
import (
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
|
||||
"code.vikunja.io/api/pkg/cron"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
@@ -360,13 +358,8 @@ 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(where).Find(&filters)
|
||||
err := s.Where("filters LIKE '%_date%'").Find(&filters)
|
||||
if err != nil {
|
||||
log.Errorf("%sError fetching filters: %s", logPrefix, err)
|
||||
return
|
||||
@@ -403,7 +396,6 @@ func RegisterAddTaskToFilterViewCron() {
|
||||
newTaskBuckets := []*TaskBucket{}
|
||||
newTaskPositions := []*TaskPosition{}
|
||||
deleteCond := []builder.Cond{}
|
||||
taskIDsToRemove := []int64{}
|
||||
for _, view := range kanbanFilterViews {
|
||||
filterID := getSavedFilterIDFromProjectID(view.ProjectID)
|
||||
filter, exists := filters[filterID]
|
||||
@@ -488,64 +480,35 @@ func RegisterAddTaskToFilterViewCron() {
|
||||
builder.Eq{"task_id": taskID},
|
||||
builder.Eq{"project_view_id": view.ID},
|
||||
))
|
||||
taskIDsToRemove = append(taskIDsToRemove, taskID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
upsertRelatedTaskProperties(s, logPrefix, newTaskBuckets, newTaskPositions, deleteCond, taskIDsToRemove)
|
||||
if len(newTaskBuckets) > 0 {
|
||||
_, err = s.Insert(newTaskBuckets)
|
||||
if err != nil {
|
||||
log.Errorf("%sError inserting task buckets: %s", logPrefix, err)
|
||||
}
|
||||
}
|
||||
if len(newTaskPositions) > 0 {
|
||||
_, err = s.Insert(newTaskPositions)
|
||||
if err != nil {
|
||||
log.Errorf("%sError inserting task positions: %s", logPrefix, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(deleteCond) > 0 {
|
||||
_, err = s.Where(builder.Or(deleteCond...)).Delete(&TaskBucket{})
|
||||
if err != nil {
|
||||
log.Errorf("%sError deleting task buckets: %s", logPrefix, err)
|
||||
}
|
||||
_, err = s.Where(builder.Or(deleteCond...)).Delete(&TaskPosition{})
|
||||
if err != nil {
|
||||
log.Errorf("%sError deleting task positions: %s", logPrefix, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Could register add task to filter view cron: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func upsertRelatedTaskProperties(s *xorm.Session, logPrefix string, newTaskBuckets []*TaskBucket, newTaskPositions []*TaskPosition, deleteCond []builder.Cond, taskIDsToRemove []int64) {
|
||||
var err error
|
||||
if len(newTaskBuckets) > 0 {
|
||||
_, err = s.Insert(newTaskBuckets)
|
||||
if err != nil {
|
||||
log.Errorf("%sError inserting task buckets: %s", logPrefix, err)
|
||||
}
|
||||
}
|
||||
if len(newTaskPositions) > 0 {
|
||||
_, err = s.Insert(newTaskPositions)
|
||||
if err != nil {
|
||||
log.Errorf("%sError inserting task positions: %s", logPrefix, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(deleteCond) > 0 {
|
||||
_, err = s.Where(builder.Or(deleteCond...)).Delete(&TaskBucket{})
|
||||
if err != nil {
|
||||
log.Errorf("%sError deleting task buckets: %s", logPrefix, err)
|
||||
}
|
||||
_, err = s.Where(builder.Or(deleteCond...)).Delete(&TaskPosition{})
|
||||
if err != nil {
|
||||
log.Errorf("%sError deleting task positions: %s", logPrefix, err)
|
||||
}
|
||||
}
|
||||
|
||||
if config.TypesenseEnabled.GetBool() && (len(newTaskPositions) > 0 || len(taskIDsToRemove) > 0) {
|
||||
taskIDs := []int64{}
|
||||
for _, position := range newTaskPositions {
|
||||
taskIDs = append(taskIDs, position.TaskID)
|
||||
}
|
||||
taskIDs = append(taskIDs, taskIDsToRemove...)
|
||||
tasks, err := GetTasksSimpleByIDs(s, taskIDs)
|
||||
if err != nil {
|
||||
log.Errorf("%sError fetching tasks: %s", logPrefix, err)
|
||||
return
|
||||
}
|
||||
taskMap := make(map[int64]*Task)
|
||||
for _, t := range tasks {
|
||||
taskMap[t.ID] = t
|
||||
}
|
||||
|
||||
err = reindexTasksInTypesense(s, taskMap)
|
||||
if err != nil {
|
||||
log.Errorf("%sError reindexing tasks into Typesense: %s", logPrefix, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+113
-11
@@ -17,14 +17,21 @@
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -132,16 +139,7 @@ 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, 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{}
|
||||
}
|
||||
|
||||
func (ta *TaskAttachment) ReadAll(s *xorm.Session, _ web.Auth, _ string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
|
||||
attachments := []*TaskAttachment{}
|
||||
|
||||
limit, start := getLimitFromPageIndex(page, perPage)
|
||||
@@ -194,6 +192,110 @@ func (ta *TaskAttachment) ReadAll(s *xorm.Session, a 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,14 +233,6 @@ 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.
|
||||
|
||||
|
||||
@@ -181,3 +181,39 @@ func getPositionsForView(s *xorm.Session, view *ProjectView) (positions []*TaskP
|
||||
Find(&positions)
|
||||
return
|
||||
}
|
||||
|
||||
func calculateNewPositionForTask(s *xorm.Session, a web.Auth, t *Task, view *ProjectView) (*TaskPosition, error) {
|
||||
if t.Position == 0 {
|
||||
lowestPosition := &TaskPosition{}
|
||||
exists, err := s.Where("project_view_id = ?", view.ID).
|
||||
OrderBy("position asc").
|
||||
Get(lowestPosition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists {
|
||||
if lowestPosition.Position == 0 {
|
||||
err = RecalculateTaskPositions(s, view, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lowestPosition = &TaskPosition{}
|
||||
_, err = s.Where("project_view_id = ?", view.ID).
|
||||
OrderBy("position asc").
|
||||
Get(lowestPosition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
t.Position = lowestPosition.Position / 2
|
||||
}
|
||||
}
|
||||
|
||||
return &TaskPosition{
|
||||
TaskID: t.ID,
|
||||
ProjectViewID: view.ID,
|
||||
Position: calculateDefaultPosition(t.Index, t.Position),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ import (
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/web"
|
||||
"github.com/typesense/typesense-go/typesense/api"
|
||||
"github.com/typesense/typesense-go/typesense/api/pointer"
|
||||
|
||||
"github.com/typesense/typesense-go/v2/typesense/api"
|
||||
"github.com/typesense/typesense-go/v2/typesense/api/pointer"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
@@ -565,8 +565,8 @@ func (t *typesenseTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task,
|
||||
}
|
||||
|
||||
params := &api.SearchCollectionParams{
|
||||
Q: pointer.String(opts.search),
|
||||
QueryBy: pointer.String("title, identifier, description, comments.comment"),
|
||||
Q: opts.search,
|
||||
QueryBy: "title, identifier, description, comments.comment",
|
||||
Page: pointer.Int(opts.page),
|
||||
ExhaustiveSearch: pointer.True(),
|
||||
FilterBy: pointer.String(strings.Join(filterBy, " && ")),
|
||||
|
||||
+11
-10
@@ -36,7 +36,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
clone "github.com/huandu/go-clone/generic"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/typesense/typesense-go/v2/typesense"
|
||||
"github.com/typesense/typesense-go/typesense"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
@@ -812,11 +812,12 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool, setB
|
||||
})
|
||||
}
|
||||
|
||||
positions = append(positions, &TaskPosition{
|
||||
TaskID: t.ID,
|
||||
ProjectViewID: view.ID,
|
||||
Position: calculateDefaultPosition(t.Index, t.Position),
|
||||
})
|
||||
newPosition, err := calculateNewPositionForTask(s, a, t, view)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
positions = append(positions, newPosition)
|
||||
}
|
||||
|
||||
if len(positions) > 0 {
|
||||
@@ -976,11 +977,11 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
tp := TaskPosition{
|
||||
TaskID: t.ID,
|
||||
ProjectViewID: view.ID,
|
||||
Position: calculateDefaultPosition(t.Index, t.Position),
|
||||
tp, err := calculateNewPositionForTask(s, a, t, view)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tp.Update(s, a)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+9
-28
@@ -27,9 +27,9 @@ import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
|
||||
"github.com/typesense/typesense-go/v2/typesense"
|
||||
"github.com/typesense/typesense-go/v2/typesense/api"
|
||||
"github.com/typesense/typesense-go/v2/typesense/api/pointer"
|
||||
"github.com/typesense/typesense-go/typesense"
|
||||
"github.com/typesense/typesense-go/typesense/api"
|
||||
"github.com/typesense/typesense-go/typesense/api/pointer"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
@@ -184,10 +184,6 @@ func CreateTypesenseCollections() error {
|
||||
Name: "positions",
|
||||
Type: "object",
|
||||
},
|
||||
{
|
||||
Name: "positions.view_.*",
|
||||
Type: "float",
|
||||
},
|
||||
{
|
||||
Name: "buckets",
|
||||
Type: "int64[]",
|
||||
@@ -203,6 +199,8 @@ func CreateTypesenseCollections() error {
|
||||
}
|
||||
|
||||
func ReindexAllTasks() (err error) {
|
||||
tasks := make(map[int64]*Task)
|
||||
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
@@ -220,7 +218,6 @@ func ReindexAllTasks() (err error) {
|
||||
return fmt.Errorf("could not update last sync: %s", err.Error())
|
||||
}
|
||||
|
||||
tasks := make(map[int64]*Task)
|
||||
err = s.Find(tasks)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get all tasks: %s", err.Error())
|
||||
@@ -254,9 +251,6 @@ func getTypesenseTaskForTask(s *xorm.Session, task *Task, projectsCache map[int6
|
||||
if projectsCache == nil {
|
||||
p, err = GetProjectSimpleByID(s, task.ProjectID)
|
||||
if err != nil {
|
||||
if IsErrProjectDoesNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("could not fetch project %d: %s", task.ProjectID, err.Error())
|
||||
}
|
||||
} else {
|
||||
@@ -265,9 +259,6 @@ func getTypesenseTaskForTask(s *xorm.Session, task *Task, projectsCache map[int6
|
||||
if !has {
|
||||
p, err = GetProjectSimpleByID(s, task.ProjectID)
|
||||
if err != nil {
|
||||
if IsErrProjectDoesNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("could not fetch project %d: %s", task.ProjectID, err.Error())
|
||||
}
|
||||
projectsCache[task.ProjectID] = p
|
||||
@@ -314,32 +305,22 @@ func reindexTasksInTypesense(s *xorm.Session, tasks map[int64]*Task) (err error)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ttask == nil {
|
||||
log.Debugf("Converted typesense task %d is nil, not indexing", task.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
typesenseTasks = append(typesenseTasks, ttask)
|
||||
}
|
||||
|
||||
response, err := typesenseClient.Collection("tasks").
|
||||
_, err = typesenseClient.Collection("tasks").
|
||||
Documents().
|
||||
Import(context.Background(), typesenseTasks, &api.ImportDocumentsParams{
|
||||
Action: pointer.String("emplace"),
|
||||
Action: pointer.String("upsert"),
|
||||
BatchSize: pointer.Int(100),
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Could not upsert tasks into Typesense: %s", err)
|
||||
log.Errorf("Could not upsert task into Typesense", err)
|
||||
return err
|
||||
}
|
||||
for _, r := range response {
|
||||
if r.Success {
|
||||
continue
|
||||
}
|
||||
log.Errorf("Errors during index: [error=%s, document=%s]", r.Error, r.Document)
|
||||
}
|
||||
|
||||
log.Debugf("Indexed %d tasks into Typesense", len(typesenseTasks))
|
||||
log.Debugf("Indexed %d tasks into Typesense", len(tasks))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,11 +17,14 @@
|
||||
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"
|
||||
)
|
||||
@@ -114,6 +117,7 @@ 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."
|
||||
@@ -152,6 +156,17 @@ 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 {
|
||||
@@ -164,6 +179,14 @@ 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, withChildren bool) ([]data.Resource, error) {
|
||||
func (vcls *VikunjaCaldavProjectStorage) GetResources(rpath string, _ 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,24 +92,6 @@ func (vcls *VikunjaCaldavProjectStorage) GetResources(rpath string, withChildren
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -181,7 +163,9 @@ func (vcls *VikunjaCaldavProjectStorage) GetResourcesByList(rpaths []string) (re
|
||||
rr := VikunjaProjectResourceAdapter{
|
||||
task: t,
|
||||
}
|
||||
addTaskResource(t, &rr, &resources)
|
||||
r := data.NewResource(getTaskURL(t), &rr)
|
||||
r.Name = t.Title
|
||||
resources = append(resources, r)
|
||||
}
|
||||
|
||||
return
|
||||
@@ -687,9 +671,3 @@ 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