Compare commits
264 Commits
main
...
release/0.
| Author | SHA1 | Date |
|---|---|---|
|
|
c934124b88 | |
|
|
b7bba814e3 | |
|
|
8c285968af | |
|
|
b91a5d9adf | |
|
|
17b281072f | |
|
|
d47555e3c2 | |
|
|
3f98f47256 | |
|
|
00a4d63344 | |
|
|
82f37df16d | |
|
|
dfd3e276ab | |
|
|
3a0a3b49af | |
|
|
6d7f81fa7e | |
|
|
154e3e4587 | |
|
|
372363d254 | |
|
|
61e6990ab3 | |
|
|
714298a94e | |
|
|
a0d05211ff | |
|
|
4d454de228 | |
|
|
fcb70b884c | |
|
|
33c9ea802a | |
|
|
ab29ac7a5c | |
|
|
7a1519da43 | |
|
|
7a839925ea | |
|
|
60fa211334 | |
|
|
e318e604b5 | |
|
|
6ea482a10b | |
|
|
a9b37db504 | |
|
|
7f3c074958 | |
|
|
2e4de2919f | |
|
|
addcb9c1f6 | |
|
|
6b3e7f0d25 | |
|
|
6dbfbc43da | |
|
|
3e3efa85ea | |
|
|
5dc94bfa64 | |
|
|
04c6b6ba36 | |
|
|
85d0c8b1b9 | |
|
|
f32472eb24 | |
|
|
99bed5c616 | |
|
|
166d9cf860 | |
|
|
16994c5d36 | |
|
|
8d849b3d30 | |
|
|
884eef5407 | |
|
|
96d8de06da | |
|
|
d6bab95376 | |
|
|
a983142ad6 | |
|
|
98c878b201 | |
|
|
b2e983fde6 | |
|
|
724ec5751f | |
|
|
17618301bc | |
|
|
83b27d813a | |
|
|
a0ad6e7d28 | |
|
|
940d149e2a | |
|
|
802f661f0d | |
|
|
f9829a6338 | |
|
|
a87419fa07 | |
|
|
b321ad5fc0 | |
|
|
edfe41ef87 | |
|
|
4cbed7c2ed | |
|
|
25c8476883 | |
|
|
15cde08db6 | |
|
|
96835f6a28 | |
|
|
b1c4f0c216 | |
|
|
d2fbd468e6 | |
|
|
a751178c8b | |
|
|
2b9b9216dc | |
|
|
f732082708 | |
|
|
f56df8aa34 | |
|
|
e290d79c15 | |
|
|
51f26d14b9 | |
|
|
320fc75c27 | |
|
|
4857bfbbdb | |
|
|
62c238e4bc | |
|
|
d27b62db6e | |
|
|
a21036340e | |
|
|
3398dee481 | |
|
|
063aa7afec | |
|
|
df3f5e73ed | |
|
|
c57636182a | |
|
|
4d2a567c46 | |
|
|
21cc3722a1 | |
|
|
6a68736ca5 | |
|
|
9e5d6bad8d | |
|
|
2b0fbe2575 | |
|
|
1e7d9c982d | |
|
|
20c52a8325 | |
|
|
a46009dfc9 | |
|
|
6ab12b9dd1 | |
|
|
aeb404f313 | |
|
|
9c5df7d389 | |
|
|
ffd36261cb | |
|
|
8812af2d77 | |
|
|
93f7dd611a | |
|
|
0e683ae7bf | |
|
|
deef8da370 | |
|
|
a0988daeaf | |
|
|
6225b24da8 | |
|
|
983c02964c | |
|
|
7bc93757c7 | |
|
|
b4c5df1ef8 | |
|
|
fd7d83bdaf | |
|
|
53d62d35f4 | |
|
|
a266fbf2b9 | |
|
|
971f328256 | |
|
|
89d643b9cf | |
|
|
a1c4fbf936 | |
|
|
1049b27d37 | |
|
|
6b850c56f7 | |
|
|
614f70db36 | |
|
|
b297cb5398 | |
|
|
8e32d099c4 | |
|
|
19a1dc9daf | |
|
|
7f28a514ea | |
|
|
44f5e42c0b | |
|
|
2fc9504285 | |
|
|
2b15bb5154 | |
|
|
f1451abebe | |
|
|
c9f47797cf | |
|
|
42b33d6553 | |
|
|
b4b2136869 | |
|
|
3d9a719424 | |
|
|
5c5f42d4ca | |
|
|
fc1208029e | |
|
|
4a40bdfe89 | |
|
|
b01e531fb7 | |
|
|
02ab944020 | |
|
|
1df4a4ea2e | |
|
|
61cb072ba3 | |
|
|
02b8ea40af | |
|
|
b5eaa51560 | |
|
|
eb6663e1f5 | |
|
|
b94802169c | |
|
|
dfda2e5500 | |
|
|
d23484c8f3 | |
|
|
78b8ea6211 | |
|
|
93cc2e1cb0 | |
|
|
52fb43a6f7 | |
|
|
8f5273600a | |
|
|
40105ee4ce | |
|
|
5bfd99dd77 | |
|
|
ac87035742 | |
|
|
10edeafa46 | |
|
|
01a7a62541 | |
|
|
c5f043c346 | |
|
|
1b55be6a15 | |
|
|
99e401cc67 | |
|
|
9deda69baf | |
|
|
34fe37bfb7 | |
|
|
a88939ff9a | |
|
|
89e4c3722a | |
|
|
4e27d7da70 | |
|
|
be5822712e | |
|
|
a5a54a40f6 | |
|
|
ac977f02df | |
|
|
3d43603b23 | |
|
|
9d3f8635d6 | |
|
|
dab0c39b70 | |
|
|
f71cc9e77b | |
|
|
5aa6b230cf | |
|
|
785ee36921 | |
|
|
476cadbbed | |
|
|
69c609478f | |
|
|
77ac70c47a | |
|
|
1371692bb6 | |
|
|
bd1b1ca23c | |
|
|
85445ea032 | |
|
|
0b9f3070fd | |
|
|
cca02a3f2e | |
|
|
5c82333977 | |
|
|
239cabd34a | |
|
|
4701c91c1a | |
|
|
5775c51be5 | |
|
|
1cdf8f4271 | |
|
|
442009a47b | |
|
|
e70f5bcce3 | |
|
|
a355d9798e | |
|
|
7fa171c3d3 | |
|
|
672fc5d9c7 | |
|
|
586132ce8c | |
|
|
bf08dc2585 | |
|
|
761d278b9a | |
|
|
6da9bc964e | |
|
|
d508fe3fb8 | |
|
|
e7075762ab | |
|
|
eb89f68f73 | |
|
|
08b4bcaff9 | |
|
|
0a29a88a26 | |
|
|
313b99e296 | |
|
|
95ef4e1045 | |
|
|
8b8ec19bb3 | |
|
|
7646c7f0c9 | |
|
|
c8f7a57566 | |
|
|
1c64b75f86 | |
|
|
fc8252e751 | |
|
|
1c9590075a | |
|
|
800f4545c1 | |
|
|
a462697b30 | |
|
|
5049cbf236 | |
|
|
a2ef74cade | |
|
|
fe44b7d473 | |
|
|
01c4f1fc0e | |
|
|
5fba4ed6ef | |
|
|
d885b43328 | |
|
|
459c8daed6 | |
|
|
5768648760 | |
|
|
198b2e3b70 | |
|
|
be8ecb6d36 | |
|
|
4c73c74587 | |
|
|
cfa58ae599 | |
|
|
39d0409f57 | |
|
|
7b804efbe2 | |
|
|
f812a67269 | |
|
|
1a131d79f9 | |
|
|
a253f76060 | |
|
|
c85da01294 | |
|
|
499f3f000c | |
|
|
1059b00298 | |
|
|
37324aeaf2 | |
|
|
7b275794dc | |
|
|
72db97203a | |
|
|
27c14b6903 | |
|
|
ec94cf6813 | |
|
|
6a9b5cb7d0 | |
|
|
8248049530 | |
|
|
d1678fe420 | |
|
|
08821ea8a8 | |
|
|
e9a466fa31 | |
|
|
de9f686480 | |
|
|
d08e9650ba | |
|
|
0434a96c3a | |
|
|
2b9b77bef2 | |
|
|
f5040ad2f4 | |
|
|
7bc77ae8c0 | |
|
|
715269a5d0 | |
|
|
53605c24f0 | |
|
|
da0b741b69 | |
|
|
73f923bc47 | |
|
|
25a8c7ea80 | |
|
|
950de7c954 | |
|
|
42ba9240b0 | |
|
|
3da6299021 | |
|
|
1ac0f412b9 | |
|
|
e7fbdc2727 | |
|
|
7117303d57 | |
|
|
0ca43dc147 | |
|
|
c47ea2716c | |
|
|
cbdcc61414 | |
|
|
194a7d4cf8 | |
|
|
9695785a0c | |
|
|
d9f555554e | |
|
|
6e38bcf349 | |
|
|
fc780a90ae | |
|
|
d586f691b7 | |
|
|
9e8e43bd12 | |
|
|
c554a96b4b | |
|
|
3ecc81094f | |
|
|
811ccc1baa | |
|
|
d7c5451729 | |
|
|
96884372b4 | |
|
|
dddba4d64a | |
|
|
a0e3efe2d1 | |
|
|
d707e1576a | |
|
|
3aaf363413 | |
|
|
28fff10fec | |
|
|
8729c24e1d |
186
.drone.yml
186
.drone.yml
|
|
@ -139,7 +139,7 @@ steps:
|
|||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: api-lint
|
||||
image: golangci/golangci-lint:v1.59.1
|
||||
image: golangci/golangci-lint:v1.61.0
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
|
|
@ -364,7 +364,7 @@ steps:
|
|||
- api-build
|
||||
|
||||
- name: frontend-dependencies
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.16.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
|
@ -378,7 +378,7 @@ steps:
|
|||
# - restore-cache
|
||||
|
||||
- name: frontend-lint
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.16.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
|
@ -390,7 +390,7 @@ steps:
|
|||
- frontend-dependencies
|
||||
|
||||
- name: frontend-build-prod
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.16.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
|
@ -402,7 +402,7 @@ steps:
|
|||
- frontend-dependencies
|
||||
|
||||
- name: frontend-test-unit
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.16.0-alpine
|
||||
pull: always
|
||||
commands:
|
||||
- cd frontend
|
||||
|
|
@ -413,7 +413,7 @@ steps:
|
|||
|
||||
- name: frontend-typecheck
|
||||
failure: ignore
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.16.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
|
@ -544,7 +544,7 @@ steps:
|
|||
- git fetch --tags
|
||||
|
||||
- name: frontend-dependencies
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.16.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
|
@ -556,7 +556,7 @@ steps:
|
|||
- pnpm install --fetch-timeout 100000
|
||||
|
||||
- name: frontend-build
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.16.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
|
@ -580,7 +580,7 @@ steps:
|
|||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: before-static-build
|
||||
image: techknowlogick/xgo:latest
|
||||
image: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
pull: always
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
|
|
@ -589,7 +589,7 @@ steps:
|
|||
depends_on: [ fetch-tags, mage ]
|
||||
|
||||
- name: static-build-windows
|
||||
image: techknowlogick/xgo:latest
|
||||
image: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
|
|
@ -605,7 +605,7 @@ steps:
|
|||
- frontend-build
|
||||
|
||||
- name: static-build-linux
|
||||
image: techknowlogick/xgo:latest
|
||||
image: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
|
|
@ -621,7 +621,7 @@ steps:
|
|||
- frontend-build
|
||||
|
||||
- name: static-build-darwin
|
||||
image: techknowlogick/xgo:latest
|
||||
image: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
|
|
@ -647,7 +647,7 @@ steps:
|
|||
- ./mage-static release:compress
|
||||
|
||||
- name: after-build-static
|
||||
image: techknowlogick/xgo:latest
|
||||
image: ghcr.io/techknowlogick/xgo:go-1.23.x
|
||||
pull: always
|
||||
depends_on:
|
||||
- after-build-compress
|
||||
|
|
@ -676,13 +676,13 @@ steps:
|
|||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
strip_prefix: dist/zip/
|
||||
source: dist/zip/*
|
||||
|
|
@ -698,13 +698,13 @@ steps:
|
|||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
strip_prefix: dist/zip/
|
||||
source: dist/zip/*
|
||||
|
|
@ -716,7 +716,7 @@ steps:
|
|||
|
||||
# Build os packages and push it to our bucket
|
||||
- name: build-os-packages-unstable
|
||||
image: goreleaser/nfpm:v2.38.0
|
||||
image: goreleaser/nfpm:v2.40.0
|
||||
pull: always
|
||||
commands:
|
||||
- apk add git go
|
||||
|
|
@ -732,7 +732,7 @@ steps:
|
|||
depends_on: [ after-build-compress ]
|
||||
|
||||
- name: build-os-packages-version
|
||||
image: goreleaser/nfpm:v2.38.0
|
||||
image: goreleaser/nfpm:v2.40.0
|
||||
pull: always
|
||||
commands:
|
||||
- apk add git go
|
||||
|
|
@ -750,13 +750,13 @@ steps:
|
|||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
strip_prefix: dist/os-packages/
|
||||
source: dist/os-packages/*
|
||||
|
|
@ -772,13 +772,13 @@ steps:
|
|||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
strip_prefix: dist/os-packages/
|
||||
source: dist/os-packages/*
|
||||
|
|
@ -837,7 +837,6 @@ steps:
|
|||
repo: vikunja/vikunja
|
||||
tags: unstable
|
||||
platforms:
|
||||
- linux/386
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
|
|
@ -869,7 +868,6 @@ steps:
|
|||
from_secret: docker_password
|
||||
repo: vikunja/vikunja
|
||||
platforms:
|
||||
- linux/386
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
|
|
@ -901,7 +899,7 @@ steps:
|
|||
- git fetch --tags
|
||||
|
||||
- name: build
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.16.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
|
@ -931,13 +929,13 @@ steps:
|
|||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
source: frontend/vikunja-frontend-unstable.zip
|
||||
target: /
|
||||
|
|
@ -962,7 +960,7 @@ steps:
|
|||
- git fetch --tags
|
||||
|
||||
- name: build
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.16.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
|
@ -990,13 +988,13 @@ steps:
|
|||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
source: frontend/vikunja-frontend-${DRONE_TAG##v}.zip
|
||||
target: /
|
||||
|
|
@ -1095,8 +1093,8 @@ steps:
|
|||
# settings:
|
||||
# restore: true
|
||||
# bucket: kolaente.dev-drone-dependency-cache
|
||||
# endpoint: https://s3.fr-par.scw.cloud
|
||||
# region: fr-par
|
||||
# endpoint: https://fsn1.your-objectstorage.com
|
||||
# region: fsn1
|
||||
# path_style: true
|
||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||
# mount:
|
||||
|
|
@ -1133,8 +1131,8 @@ steps:
|
|||
# settings:
|
||||
# rebuild: true
|
||||
# bucket: kolaente.dev-drone-dependency-cache
|
||||
# endpoint: https://s3.fr-par.scw.cloud
|
||||
# region: fr-par
|
||||
# endpoint: https://fsn1.your-objectstorage.com
|
||||
# region: fsn1
|
||||
# path_style: true
|
||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||
# mount:
|
||||
|
|
@ -1176,8 +1174,8 @@ steps:
|
|||
# settings:
|
||||
# restore: true
|
||||
# bucket: kolaente.dev-drone-dependency-cache
|
||||
# endpoint: https://s3.fr-par.scw.cloud
|
||||
# region: fr-par
|
||||
# endpoint: https://fsn1.your-objectstorage.com
|
||||
# region: fsn1
|
||||
# path_style: true
|
||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||
# mount:
|
||||
|
|
@ -1200,30 +1198,30 @@ steps:
|
|||
- unzip vikunja-frontend-$$VERSION.zip -d frontend
|
||||
- sed -i 's/\\/api\\/v1//g' frontend/index.html
|
||||
- ./bumpp.sh
|
||||
- yarn install
|
||||
- cat package.json
|
||||
- yarn dist --linux --windows
|
||||
|
||||
# - name: rebuild-cache
|
||||
# image: meltwater/drone-cache:dev
|
||||
# pull: true
|
||||
# environment:
|
||||
# AWS_ACCESS_KEY_ID:
|
||||
# from_secret: cache_aws_access_key_id
|
||||
# AWS_SECRET_ACCESS_KEY:
|
||||
# from_secret: cache_aws_secret_access_key
|
||||
# settings:
|
||||
# rebuild: true
|
||||
# bucket: kolaente.dev-drone-dependency-cache
|
||||
# endpoint: https://s3.fr-par.scw.cloud
|
||||
# region: fr-par
|
||||
# path_style: true
|
||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||
# mount:
|
||||
# - '.cache'
|
||||
# depends_on:
|
||||
# - build
|
||||
- corepack enable && pnpm config set store-dir .cache/pnpm
|
||||
- pnpm install --fetch-timeout 100000
|
||||
- pnpm dist --linux --windows
|
||||
|
||||
# - name: rebuild-cache
|
||||
# image: meltwater/drone-cache:dev
|
||||
# pull: true
|
||||
# environment:
|
||||
# AWS_ACCESS_KEY_ID:
|
||||
# from_secret: cache_aws_access_key_id
|
||||
# AWS_SECRET_ACCESS_KEY:
|
||||
# from_secret: cache_aws_secret_access_key
|
||||
# settings:
|
||||
# rebuild: true
|
||||
# bucket: kolaente.dev-drone-dependency-cache
|
||||
# endpoint: https://fsn1.your-objectstorage.com
|
||||
# region: fsn1
|
||||
# path_style: true
|
||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||
# mount:
|
||||
# - '.cache'
|
||||
# depends_on:
|
||||
# - build
|
||||
|
||||
- name: rename-unstable
|
||||
image: bash
|
||||
pull: true
|
||||
|
|
@ -1241,13 +1239,13 @@ steps:
|
|||
image: plugins/s3
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
strip_prefix: desktop/dist/
|
||||
source: desktop/dist/Vikunja-Desktop*
|
||||
|
|
@ -1263,13 +1261,13 @@ steps:
|
|||
image: plugins/s3
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
bucket: vikunja
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
from_secret: hetzner_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
from_secret: hetzner_secret_access_key
|
||||
endpoint: https://fsn1.your-objectstorage.com
|
||||
region: fsn1
|
||||
path_style: true
|
||||
strip_prefix: desktop/dist/
|
||||
source: desktop/dist/*
|
||||
|
|
@ -1298,9 +1296,9 @@ steps:
|
|||
# - name: build
|
||||
# environment:
|
||||
# ACCESS_KEY:
|
||||
# from_secret: aws_access_key_id
|
||||
# from_secret: hetzner_access_key_id
|
||||
# SECRET_KEY:
|
||||
# from_secret: aws_secret_access_key
|
||||
# from_secret: hetzner_secret_access_key
|
||||
# commands:
|
||||
# - git fetch --tags
|
||||
# - export VERSION=${DRONE_TAG##v}
|
||||
|
|
@ -1313,9 +1311,9 @@ steps:
|
|||
# - sed -i '' "s/\$${version}/$$VERSION/g" package.json
|
||||
# - yarn install
|
||||
# - yarn dist --mac
|
||||
# - mc config host add scw-fr-par https://s3.fr-par.scw.cloud $ACCESS_KEY $SECRET_KEY --api S3v4
|
||||
# - mc cp ./dist/*.dmg scw-fr-par/vikunja-releases/desktop/$VERSION/
|
||||
# - mc cp ./dist/*.dmg.blockmap scw-fr-par/vikunja-releases/desktop/$VERSION/
|
||||
# - mc config host add scw-fsn1 https://fsn1.your-objectstorage.com $ACCESS_KEY $SECRET_KEY --api S3v4
|
||||
# - mc cp ./dist/*.dmg scw-fsn1/vikunja/desktop/$VERSION/
|
||||
# - mc cp ./dist/*.dmg.blockmap scw-fsn1/vikunja/desktop/$VERSION/
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
|
|
@ -1352,6 +1350,6 @@ steps:
|
|||
- failure
|
||||
---
|
||||
kind: signature
|
||||
hmac: 196e215a44cc1b6b5cb89e6ef09196166af17969f69e3e7e8c571a0e44285714
|
||||
hmac: 02fb3097f3e50facb2579de5c3b94e513b97e535a049a28ddb67f85e494d65b1
|
||||
|
||||
...
|
||||
|
|
|
|||
4
.envrc
4
.envrc
|
|
@ -1 +1,3 @@
|
|||
use flake
|
||||
source_url "https://raw.githubusercontent.com/cachix/devenv/95f329d49a8a5289d31e0982652f7058a189bfca/direnvrc" "sha256-d+8cBpDfDBj41inrADaJt+bDWhOktwslgoP5YiGJ1v0="
|
||||
|
||||
use devenv
|
||||
|
|
@ -28,4 +28,14 @@ vendor/
|
|||
os-packages/
|
||||
mage_output_file.go
|
||||
mage-static
|
||||
.direnv/
|
||||
.DS_Store
|
||||
|
||||
# Devenv
|
||||
.devenv*
|
||||
devenv.local.nix
|
||||
|
||||
# direnv
|
||||
.direnv
|
||||
|
||||
# pre-commit
|
||||
.pre-commit-config.yaml
|
||||
|
|
|
|||
|
|
@ -109,3 +109,13 @@ issues:
|
|||
text: 'structtag: struct field Position repeats json tag "position" also at'
|
||||
linters:
|
||||
- govet
|
||||
- path: pkg/cmd/user.go
|
||||
text: 'G115: integer overflow conversion uintptr -> int'
|
||||
linters:
|
||||
- gosec
|
||||
- text: 'G115: integer overflow conversion int64 -> uint64'
|
||||
linters:
|
||||
- gosec
|
||||
- text: 'G115: integer overflow conversion int -> uint64'
|
||||
linters:
|
||||
- gosec
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"codezombiech.gitignore",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"editorconfig.editorconfig",
|
||||
"vue.volar",
|
||||
"lokalise.i18n-ally",
|
||||
"mgmcdermott.vscode-language-babel",
|
||||
"mikestead.dotenv",
|
||||
"Syler.sass-indented",
|
||||
"vitest.explorer"
|
||||
]
|
||||
"recommendations": [
|
||||
"codezombiech.gitignore",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"editorconfig.editorconfig",
|
||||
"vue.volar",
|
||||
"lokalise.i18n-ally",
|
||||
"mgmcdermott.vscode-language-babel",
|
||||
"mikestead.dotenv",
|
||||
"Syler.sass-indented",
|
||||
"vitest.explorer",
|
||||
"mkhl.direnv",
|
||||
"golang.Go"
|
||||
]
|
||||
}
|
||||
314
CHANGELOG.md
314
CHANGELOG.md
|
|
@ -5,7 +5,319 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
|
||||
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
All releases can be found on https://code.vikunja.io/api/releases.
|
||||
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
|
||||
|
||||
* *(i18n)* Change casing of Ukrainian language in selector
|
||||
* *(kanban)* Always make cover image full width
|
||||
* *(mail)* Do not fail testmail command when the connection could not be closed.
|
||||
* *(migration)* Make sure tasks are associated to the correct view and bucket for data imported from Vikunja dump
|
||||
* *(migration)* Ensure project background gets exported and imported
|
||||
* *(projects)* Trigger only single mutation
|
||||
* *(task)* Do not allow moving a task to the project the task already belongs to
|
||||
* *(task)* Set current project after moving a task
|
||||
* *(task)* Move task into new kanban bucket when moving between projects
|
||||
* *(views)* Do not create task bucket and task position entries when duplicating a project* Emit for DatepickerWithValues ([3aaf363](3aaf3634134a6989337bad02ac99a9329d33b17f))
|
||||
* Textarea autosize for LanguageTool ([d9f5555](d9f555554e5ecfa9d1243c565e2f42c77f7a7597))
|
||||
* Remove console log ([0ca43dc](0ca43dc147acd04d9f9b566325ccde0a5782680f))
|
||||
|
||||
### Dependencies
|
||||
|
||||
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.11.0
|
||||
* *(deps)* Update flake
|
||||
* *(deps)* Update go toolchain to 1.22.5
|
||||
* *(deps)* Update dependency node to v20.16.0
|
||||
|
||||
### Documentation
|
||||
|
||||
* Clarify Todoist redirect url ([7117303](7117303d5705199bb39fb6661b20deebea96959f))
|
||||
|
||||
### Features
|
||||
|
||||
* *(editor)* Support custom protocol for links* Use withDefaults for Reminders ([8729c24](8729c24e1d2bdc783e750dc1166a8ab31a716107))
|
||||
* Improve projects store ([d707e15](d707e1576a006baa73c529b432e694dee237db76))
|
||||
* Improve label store ([a0e3efe](a0e3efe2d12521e773334a19ac985a2011575c3c))
|
||||
* Improve priority visibility ([dddba4d](dddba4d64a9ddddd6f442c8180661d2d498de994))
|
||||
* Add tailwind with prefix (#2513) ([d7c5451](d7c54517297e750d92618376f52045b11da6e82c))
|
||||
* Improve ProjectSettingsViews ([811ccc1](811ccc1baa2fe1fb9fcc8b5515c8c3f3b591e96a))
|
||||
* Add missing peer dependency ([d586f69](d586f691b7a245bbffd5df4e7a4937f0527047e0))
|
||||
* Switch from nix flakes to devenv ([73f923b](73f923bc47d32a6a9a689e461901295b07646ebf))
|
||||
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
* *(i18n)* Update translations via Crowdin
|
||||
* Remove lodash.debounce ([fc780a9](fc780a90ae856038db73d3ad63fcdd5627211c36))
|
||||
* Improve error message ([6e38bcf](6e38bcf3498a15a1dc1f6fbef254c08513b408b3))
|
||||
* Use nixpkgs unstable for more recent packages ([f5040ad](f5040ad2f4996fe1ccf03172b1a1ee6007068ee7))
|
||||
|
||||
### Other
|
||||
|
||||
* *(other)* [skip ci] Updated swagger docs
|
||||
|
||||
## [0.24.1] - 2024-07-18
|
||||
|
||||
|
|
|
|||
12
Dockerfile
12
Dockerfile
|
|
@ -1,11 +1,11 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
FROM --platform=$BUILDPLATFORM node:20.14.0-alpine AS frontendbuilder
|
||||
FROM --platform=$BUILDPLATFORM node:20.16.0-alpine AS frontendbuilder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
ENV PNPM_CACHE_FOLDER .cache/pnpm/
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD true
|
||||
ENV CYPRESS_INSTALL_BINARY 0
|
||||
ENV PNPM_CACHE_FOLDER=.cache/pnpm/
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=true
|
||||
ENV CYPRESS_INSTALL_BINARY=0
|
||||
|
||||
COPY frontend/ ./
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ RUN corepack enable && \
|
|||
pnpm install && \
|
||||
pnpm run build
|
||||
|
||||
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.21.x AS apibuilder
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/techknowlogick/xgo:go-1.23.x AS apibuilder
|
||||
|
||||
RUN go install github.com/magefile/mage@latest && \
|
||||
mv /go/bin/mage /usr/local/go/bin
|
||||
|
|
@ -24,7 +24,7 @@ COPY --from=frontendbuilder /build/dist ./frontend/dist
|
|||
|
||||
ARG TARGETOS TARGETARCH TARGETVARIANT
|
||||
|
||||
ENV GOPROXY https://goproxy.kolaente.de
|
||||
ENV GOPROXY=https://goproxy.kolaente.de
|
||||
RUN export PATH=$PATH:$GOPATH/bin && \
|
||||
mage build:clean && \
|
||||
mage release:xgo "${TARGETOS}/${TARGETARCH}/${TARGETVARIANT}"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -227,12 +227,13 @@ migration:
|
|||
clientid:
|
||||
# The client secret, also required for making requests to the todoist api
|
||||
clientsecret:
|
||||
# The url where clients are redirected after they authorized Vikunja to access their todoist items.
|
||||
# This needs to match the url you entered when registering your Vikunja instance at todoist.
|
||||
# This is usually the frontend url where the frontend then makes a request to /migration/todoist/migrate
|
||||
# The url where clients are redirected after they authorized Vikunja to access their Todoist items.
|
||||
# In Todoist, this is called `OAuth redirect URL` and it needs to match the url you entered when registering
|
||||
# your Vikunja instance at the Todoist developer console.
|
||||
# When using the official Vikunja frontend, set this to <service.publicurl>/migrate/todoist.
|
||||
# Otherwise, set this to an url which then makes a request to /api/v1/migration/todoist/migrate
|
||||
# with the code obtained from the todoist api.
|
||||
# Note that the Vikunja frontend expects this to be /migrate/todoist
|
||||
redirecturl: <frontend url>/migrate/todoist
|
||||
redirecturl: <service.publicurl>/migrate/todoist
|
||||
trello:
|
||||
# Whether to enable the trello migrator or not
|
||||
enable: false
|
||||
|
|
|
|||
|
|
@ -51,11 +51,11 @@
|
|||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "29.4.5",
|
||||
"electron": "29.4.6",
|
||||
"electron-builder": "24.13.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"connect-history-api-fallback": "2.0.0",
|
||||
"express": "4.19.2"
|
||||
"express": "4.21.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,139 @@
|
|||
{
|
||||
"nodes": {
|
||||
"devenv": {
|
||||
"locked": {
|
||||
"dir": "src/modules",
|
||||
"lastModified": 1726063457,
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "39bf6ce569103c9390d37322daa59468c31b3ce7",
|
||||
"treeHash": "839747a1cb35ba6d5b36cce9a739ab2ba5e4a5d4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "src/modules",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"treeHash": "2addb7b71a20a25ea74feeaf5c2f6a6b30898ecb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"pre-commit-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709087332,
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"treeHash": "ca14199cabdfe1a06a7b1654c76ed49100a689f9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1716977621,
|
||||
"owner": "cachix",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"rev": "4267e705586473d3e5c8d50299e71503f16a6fb6",
|
||||
"treeHash": "6d9f1f7ca0faf1bc2eeb397c78a49623260d3412",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "rolling",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1725930920,
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "44a71ff39c182edaf25a7ace5c9454e7cba2c658",
|
||||
"treeHash": "56e93544112b7bb7aa0c3093d537295683ef9148",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-unstable": {
|
||||
"locked": {
|
||||
"lastModified": 1725983898,
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1355a0cbfeac61d785b7183c0caaec1f97361b43",
|
||||
"treeHash": "d8ce0c1af7690da6161f0991484661331f5bc999",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1725513492,
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "7570de7b9b504cfe92025dd1be797bf546f66528",
|
||||
"treeHash": "4b46d77870afecd8f642541cb4f4927326343b59",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{ pkgs, lib, config, inputs, ... }:
|
||||
|
||||
let
|
||||
pkgs-unstable = import inputs.nixpkgs-unstable { system = pkgs.stdenv.system; };
|
||||
in {
|
||||
packages = with pkgs-unstable; [
|
||||
# General tools
|
||||
git-cliff
|
||||
# API tools
|
||||
golangci-lint mage
|
||||
# Desktop
|
||||
electron
|
||||
] ++ lib.optionals (!pkgs.stdenv.isDarwin) [
|
||||
# Frontend tools (exclude on Darwin)
|
||||
cypress
|
||||
];
|
||||
|
||||
languages = {
|
||||
javascript = {
|
||||
enable = true;
|
||||
package = pkgs-unstable.nodejs-slim;
|
||||
pnpm = {
|
||||
enable = true;
|
||||
package = pkgs-unstable.pnpm;
|
||||
};
|
||||
};
|
||||
|
||||
go = {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
|
||||
inputs:
|
||||
nixpkgs:
|
||||
url: github:cachix/devenv-nixpkgs/rolling
|
||||
nixpkgs-unstable:
|
||||
url: github:NixOS/nixpkgs/nixos-unstable
|
||||
|
||||
# If you're using non-OSS software, you can set allowUnfree to true.
|
||||
allowUnfree: true
|
||||
|
||||
# If you're willing to use a package that's vulnerable
|
||||
# permittedInsecurePackages:
|
||||
# - "openssl-1.1.1w"
|
||||
|
||||
# If you have more than one devenv you can merge them
|
||||
#imports:
|
||||
# - ./backend
|
||||
25
flake.lock
25
flake.lock
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1721116560,
|
||||
"narHash": "sha256-++TYlGMAJM1Q+0nMVaWBSEvEUjRs7ZGiNQOpqbQApCU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9355fa86e6f27422963132c2c9aeedb0fb963d93",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
20
flake.nix
20
flake.nix
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
description = "Vikunja dev environment";
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
||||
in {
|
||||
defaultPackage.x86_64-linux =
|
||||
pkgs.mkShell { buildInputs = with pkgs; [
|
||||
# General tools
|
||||
git-cliff
|
||||
# Frontend tools
|
||||
pnpm cypress
|
||||
# API tools
|
||||
go golangci-lint mage
|
||||
# Desktop
|
||||
electron
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ node_modules
|
|||
dist
|
||||
coverage
|
||||
*.zip
|
||||
.vite/
|
||||
|
||||
# Test files
|
||||
cypress/screenshots
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
20.15.0
|
||||
20.16.0
|
||||
|
|
@ -148,7 +148,7 @@ describe('Project View Kanban', () => {
|
|||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
||||
.contains('Delete')
|
||||
.click()
|
||||
cy.get('.modal-mask .modal-container .modal-content .header')
|
||||
cy.get('.modal-mask .modal-container .modal-content .modal-header')
|
||||
.should('contain', 'Delete the bucket')
|
||||
cy.get('.modal-mask .modal-container .modal-content .actions .button')
|
||||
.contains('Do it!')
|
||||
|
|
@ -251,7 +251,7 @@ describe('Project View Kanban', () => {
|
|||
.should('be.visible')
|
||||
.contains('Delete')
|
||||
.click()
|
||||
cy.get('.modal-mask .modal-container .modal-content .header')
|
||||
cy.get('.modal-mask .modal-container .modal-content .modal-header')
|
||||
.should('contain', 'Delete this task')
|
||||
cy.get('.modal-mask .modal-container .modal-content .actions .button')
|
||||
.contains('Do it!')
|
||||
|
|
|
|||
|
|
@ -356,7 +356,7 @@ describe('Task', () => {
|
|||
.should('be.visible')
|
||||
.contains('Delete')
|
||||
.click()
|
||||
cy.get('.modal-mask .modal-container .modal-content .header')
|
||||
cy.get('.modal-mask .modal-container .modal-content .modal-header')
|
||||
.should('contain', 'Delete this task')
|
||||
cy.get('.modal-mask .modal-container .modal-content .actions .button')
|
||||
.contains('Do it!')
|
||||
|
|
@ -484,7 +484,7 @@ describe('Task', () => {
|
|||
|
||||
addLabelToTaskAndVerify(labels[0].title)
|
||||
|
||||
cy.get('.modal-content .close')
|
||||
cy.get('.modal-container > .close')
|
||||
.click()
|
||||
|
||||
cy.get('.bucket .task')
|
||||
|
|
@ -850,7 +850,7 @@ describe('Task', () => {
|
|||
|
||||
uploadAttachmentAndVerify(tasks[0].id)
|
||||
|
||||
cy.get('.modal-content .close')
|
||||
cy.get('.modal-container > .close')
|
||||
.click()
|
||||
|
||||
cy.get('.bucket .task .footer .icon svg.fa-paperclip')
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
"homepage": "https://vikunja.io/",
|
||||
"funding": "https://opencollective.com/vikunja",
|
||||
"packageManager": "pnpm@9.5.0",
|
||||
"packageManager": "pnpm@9.10.0",
|
||||
"keywords": [
|
||||
"todo",
|
||||
"productivity",
|
||||
|
|
@ -56,131 +56,131 @@
|
|||
"@fortawesome/vue-fontawesome": "3.0.8",
|
||||
"@github/hotkey": "3.1.1",
|
||||
"@infectoone/vue-ganttastic": "2.3.2",
|
||||
"@intlify/unplugin-vue-i18n": "4.0.0",
|
||||
"@kyvg/vue3-notification": "3.2.1",
|
||||
"@intlify/unplugin-vue-i18n": "5.2.0",
|
||||
"@kyvg/vue3-notification": "3.3.0",
|
||||
"@sentry/tracing": "7.114.0",
|
||||
"@sentry/vue": "8.18.0",
|
||||
"@tiptap/core": "2.5.4",
|
||||
"@tiptap/extension-blockquote": "2.5.4",
|
||||
"@tiptap/extension-bold": "2.5.4",
|
||||
"@tiptap/extension-bullet-list": "2.5.4",
|
||||
"@tiptap/extension-code": "2.5.4",
|
||||
"@tiptap/extension-code-block-lowlight": "2.5.4",
|
||||
"@tiptap/extension-document": "2.5.4",
|
||||
"@tiptap/extension-dropcursor": "2.5.4",
|
||||
"@tiptap/extension-gapcursor": "2.5.4",
|
||||
"@tiptap/extension-hard-break": "2.5.4",
|
||||
"@tiptap/extension-heading": "2.5.4",
|
||||
"@tiptap/extension-history": "2.5.4",
|
||||
"@tiptap/extension-horizontal-rule": "2.5.4",
|
||||
"@tiptap/extension-image": "2.5.4",
|
||||
"@tiptap/extension-italic": "2.5.4",
|
||||
"@tiptap/extension-link": "2.5.4",
|
||||
"@tiptap/extension-list-item": "2.5.4",
|
||||
"@tiptap/extension-ordered-list": "2.5.4",
|
||||
"@tiptap/extension-paragraph": "2.5.4",
|
||||
"@tiptap/extension-placeholder": "2.5.4",
|
||||
"@tiptap/extension-strike": "2.5.4",
|
||||
"@tiptap/extension-table": "2.5.4",
|
||||
"@tiptap/extension-table-cell": "2.5.4",
|
||||
"@tiptap/extension-table-header": "2.5.4",
|
||||
"@tiptap/extension-table-row": "2.5.4",
|
||||
"@tiptap/extension-task-item": "2.5.4",
|
||||
"@tiptap/extension-task-list": "2.5.4",
|
||||
"@tiptap/extension-text": "2.5.4",
|
||||
"@tiptap/extension-typography": "2.5.4",
|
||||
"@tiptap/extension-underline": "2.5.4",
|
||||
"@tiptap/pm": "2.5.4",
|
||||
"@tiptap/suggestion": "2.5.4",
|
||||
"@tiptap/vue-3": "2.5.4",
|
||||
"@vueuse/core": "10.11.0",
|
||||
"@vueuse/router": "10.11.0",
|
||||
"axios": "1.7.2",
|
||||
"@sentry/vue": "8.32.0",
|
||||
"@tiptap/core": "2.7.4",
|
||||
"@tiptap/extension-blockquote": "2.7.4",
|
||||
"@tiptap/extension-bold": "2.7.4",
|
||||
"@tiptap/extension-bullet-list": "2.7.4",
|
||||
"@tiptap/extension-code": "2.7.4",
|
||||
"@tiptap/extension-code-block": "2.7.4",
|
||||
"@tiptap/extension-code-block-lowlight": "2.7.4",
|
||||
"@tiptap/extension-document": "2.7.4",
|
||||
"@tiptap/extension-dropcursor": "2.7.4",
|
||||
"@tiptap/extension-gapcursor": "2.7.4",
|
||||
"@tiptap/extension-hard-break": "2.7.4",
|
||||
"@tiptap/extension-heading": "2.7.4",
|
||||
"@tiptap/extension-history": "2.7.4",
|
||||
"@tiptap/extension-horizontal-rule": "2.7.4",
|
||||
"@tiptap/extension-image": "2.7.4",
|
||||
"@tiptap/extension-italic": "2.7.4",
|
||||
"@tiptap/extension-link": "2.7.4",
|
||||
"@tiptap/extension-list-item": "2.7.4",
|
||||
"@tiptap/extension-ordered-list": "2.7.4",
|
||||
"@tiptap/extension-paragraph": "2.7.4",
|
||||
"@tiptap/extension-placeholder": "2.7.4",
|
||||
"@tiptap/extension-strike": "2.7.4",
|
||||
"@tiptap/extension-table": "2.7.4",
|
||||
"@tiptap/extension-table-cell": "2.7.4",
|
||||
"@tiptap/extension-table-header": "2.7.4",
|
||||
"@tiptap/extension-table-row": "2.7.4",
|
||||
"@tiptap/extension-task-item": "2.7.4",
|
||||
"@tiptap/extension-task-list": "2.7.4",
|
||||
"@tiptap/extension-text": "2.7.4",
|
||||
"@tiptap/extension-typography": "2.7.4",
|
||||
"@tiptap/extension-underline": "2.7.4",
|
||||
"@tiptap/pm": "2.7.4",
|
||||
"@tiptap/suggestion": "2.7.4",
|
||||
"@tiptap/vue-3": "2.7.4",
|
||||
"@vueuse/core": "11.1.0",
|
||||
"@vueuse/router": "11.1.0",
|
||||
"axios": "1.7.7",
|
||||
"blurhash": "2.0.5",
|
||||
"bulma-css-variables": "0.9.33",
|
||||
"change-case": "5.4.4",
|
||||
"date-fns": "3.6.0",
|
||||
"dayjs": "1.11.12",
|
||||
"dompurify": "3.1.6",
|
||||
"date-fns": "4.1.0",
|
||||
"dayjs": "1.11.13",
|
||||
"dompurify": "3.1.7",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"flatpickr": "4.6.13",
|
||||
"flexsearch": "0.7.31",
|
||||
"flexsearch": "0.7.43",
|
||||
"floating-vue": "5.2.2",
|
||||
"is-touch-device": "1.0.1",
|
||||
"klona": "2.0.6",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"lowlight": "2.9.0",
|
||||
"pinia": "2.1.7",
|
||||
"pinia": "2.2.2",
|
||||
"register-service-worker": "1.7.2",
|
||||
"sortablejs": "1.15.2",
|
||||
"sortablejs": "1.15.3",
|
||||
"tailwindcss": "3.4.13",
|
||||
"tippy.js": "6.3.7",
|
||||
"ufo": "1.5.4",
|
||||
"vue": "3.4.32",
|
||||
"vue": "3.5.10",
|
||||
"vue-advanced-cropper": "2.8.9",
|
||||
"vue-flatpickr-component": "11.0.5",
|
||||
"vue-i18n": "9.13.1",
|
||||
"vue-router": "4.4.0",
|
||||
"vuemoji-picker": "0.2.1",
|
||||
"vue-i18n": "10.0.3",
|
||||
"vue-router": "4.4.5",
|
||||
"vuemoji-picker": "0.3.1",
|
||||
"workbox-precaching": "7.1.0",
|
||||
"zhyswan-vuedraggable": "4.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@4tw/cypress-drag-drop": "2.2.5",
|
||||
"@cypress/vite-dev-server": "5.1.1",
|
||||
"@cypress/vite-dev-server": "5.2.0",
|
||||
"@cypress/vue": "6.0.1",
|
||||
"@faker-js/faker": "8.4.1",
|
||||
"@histoire/plugin-screenshot": "0.17.17",
|
||||
"@histoire/plugin-vue": "0.17.17",
|
||||
"@rushstack/eslint-patch": "1.10.3",
|
||||
"@rushstack/eslint-patch": "1.10.4",
|
||||
"@tsconfig/node20": "20.1.4",
|
||||
"@types/codemirror": "5.60.15",
|
||||
"@types/dompurify": "3.0.5",
|
||||
"@types/flexsearch": "0.7.6",
|
||||
"@types/is-touch-device": "1.0.2",
|
||||
"@types/is-touch-device": "1.0.3",
|
||||
"@types/lodash.clonedeep": "4.5.9",
|
||||
"@types/lodash.debounce": "4.0.9",
|
||||
"@types/node": "20.14.11",
|
||||
"@types/node": "20.16.10",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "7.16.1",
|
||||
"@typescript-eslint/parser": "7.16.1",
|
||||
"@vitejs/plugin-legacy": "5.4.1",
|
||||
"@vitejs/plugin-vue": "5.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "7.18.0",
|
||||
"@typescript-eslint/parser": "7.18.0",
|
||||
"@vitejs/plugin-legacy": "5.4.2",
|
||||
"@vitejs/plugin-vue": "5.1.4",
|
||||
"@vue/eslint-config-typescript": "13.0.0",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"@vue/tsconfig": "0.5.1",
|
||||
"autoprefixer": "10.4.19",
|
||||
"browserslist": "4.23.2",
|
||||
"caniuse-lite": "1.0.30001642",
|
||||
"autoprefixer": "10.4.20",
|
||||
"browserslist": "4.24.0",
|
||||
"caniuse-lite": "1.0.30001664",
|
||||
"csstype": "3.1.3",
|
||||
"cypress": "13.13.1",
|
||||
"esbuild": "0.23.0",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-vue": "9.27.0",
|
||||
"cypress": "13.15.0",
|
||||
"esbuild": "0.24.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-plugin-vue": "9.28.0",
|
||||
"happy-dom": "14.12.3",
|
||||
"histoire": "0.17.17",
|
||||
"postcss": "8.4.39",
|
||||
"postcss": "8.4.47",
|
||||
"postcss-easing-gradients": "3.0.1",
|
||||
"postcss-easings": "4.0.0",
|
||||
"postcss-preset-env": "9.6.0",
|
||||
"rollup": "4.18.1",
|
||||
"rollup": "4.22.5",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"sass": "1.77.8",
|
||||
"start-server-and-test": "2.0.4",
|
||||
"typescript": "5.5.3",
|
||||
"unplugin-inject-preload": "2.0.4",
|
||||
"vite": "5.3.4",
|
||||
"vite-plugin-pwa": "0.20.0",
|
||||
"sass": "1.79.4",
|
||||
"start-server-and-test": "2.0.8",
|
||||
"typescript": "5.6.2",
|
||||
"unplugin-inject-preload": "2.0.5",
|
||||
"vite": "5.4.8",
|
||||
"vite-plugin-pwa": "0.20.5",
|
||||
"vite-plugin-sentry": "1.4.0",
|
||||
"vite-svg-loader": "5.1.0",
|
||||
"vitest": "1.6.0",
|
||||
"vue-tsc": "2.0.26",
|
||||
"vue-tsc": "2.1.6",
|
||||
"wait-on": "7.2.0",
|
||||
"workbox-cli": "7.1.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"flexsearch@0.7.31": "patches/flexsearch@0.7.31.patch",
|
||||
"@github/hotkey@3.1.1": "patches/@github__hotkey@3.1.1.patch"
|
||||
"@github/hotkey@3.1.1": "patches/@github__hotkey@3.1.1.patch",
|
||||
"flexsearch@0.7.43": "patches/flexsearch@0.7.43.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
diff --git a/index.d.ts b/index.d.ts
|
||||
deleted file mode 100644
|
||||
index 9f39f41073864b83968bdaa242ac4e3c3149685a..0000000000000000000000000000000000000000
|
||||
diff --git a/package.json b/package.json
|
||||
index 8968f5bf8010ff194240591c8b83299f7328e79d..6d84b6f590a841b129ed8b3860cb786df5a185c0 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -22,8 +22,6 @@
|
||||
},
|
||||
"main": "dist/flexsearch.bundle.js",
|
||||
"browser": "dist/flexsearch.bundle.js",
|
||||
- "module": "dist/module/index.js",
|
||||
- "types": "./index.d.ts",
|
||||
"preferGlobal": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
diff --git a/package.json b/package.json
|
||||
index c154e54029c94be444916fb2249941e7182d80ed..54a65c42a42c4627506e016132becc43b47a517c 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -28,13 +28,11 @@
|
||||
"email": "info@nextapps.de"
|
||||
},
|
||||
"main": "dist/flexsearch.bundle.min.js",
|
||||
- "module": "dist/flexsearch.bundle.module.min.js",
|
||||
"browser": {
|
||||
"dist/flexsearch.bundle.min.js": "./dist/flexsearch.bundle.min.js",
|
||||
"dist/flexsearch.bundle.module.min.js": "./dist/flexsearch.bundle.module.min.js",
|
||||
"worker_threads": false
|
||||
},
|
||||
- "types": "./index.d.ts",
|
||||
"scripts": {
|
||||
"build": "npm run copy && npm run build:bundle",
|
||||
"build:bundle": "node task/build RELEASE=bundle DEBUG=false SUPPORT_WORKER=true SUPPORT_ENCODER=true SUPPORT_CACHE=true SUPPORT_ASYNC=true SUPPORT_STORE=true SUPPORT_TAGS=true SUPPORT_SUGGESTION=true SUPPORT_SERIALIZE=true SUPPORT_DOCUMENT=true POLYFILL=false",
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,33 +3,22 @@
|
|||
v-cy="'checkbox'"
|
||||
class="base-checkbox"
|
||||
>
|
||||
<input
|
||||
:id="checkboxId"
|
||||
type="checkbox"
|
||||
class="is-sr-only"
|
||||
:checked="modelValue"
|
||||
:disabled="disabled || undefined"
|
||||
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
|
||||
<label
|
||||
class="base-checkbox__label"
|
||||
>
|
||||
|
||||
<slot
|
||||
name="label"
|
||||
:checkbox-id="checkboxId"
|
||||
>
|
||||
<label
|
||||
:for="checkboxId"
|
||||
class="base-checkbox__label"
|
||||
<input
|
||||
type="checkbox"
|
||||
class="is-sr-only"
|
||||
:checked="modelValue"
|
||||
:disabled="disabled || undefined"
|
||||
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
|
||||
>
|
||||
<slot />
|
||||
</label>
|
||||
</slot>
|
||||
<slot />
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue'
|
||||
import {createRandomID} from '@/helpers/randomId'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
modelValue?: boolean,
|
||||
disabled: boolean,
|
||||
|
|
@ -38,10 +27,8 @@ withDefaults(defineProps<{
|
|||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: boolean): void
|
||||
(event: 'update:modelValue', value: boolean): void
|
||||
}>()
|
||||
|
||||
const checkboxId = ref(`checkbox_${createRandomID()}`)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
@ -53,7 +40,7 @@ const checkboxId = ref(`checkbox_${createRandomID()}`)
|
|||
}
|
||||
|
||||
.base-checkbox:has(input:disabled) .base-checkbox__label {
|
||||
cursor:not-allowed;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<nav
|
||||
v-if="totalPages > 1"
|
||||
aria-label="pagination"
|
||||
class="pagination is-centered p-4"
|
||||
role="navigation"
|
||||
>
|
||||
<slot
|
||||
name="previous"
|
||||
:disabled="currentPage === 1"
|
||||
>
|
||||
{{ $t('misc.previous') }}
|
||||
</slot>
|
||||
<slot
|
||||
name="next"
|
||||
:disabled="currentPage === totalPages"
|
||||
>
|
||||
{{ $t('misc.next') }}
|
||||
</slot>
|
||||
<ul class="pagination-list">
|
||||
<li
|
||||
v-for="(p, i) in pages"
|
||||
:key="`page-${i}`"
|
||||
>
|
||||
<span
|
||||
v-if="p.isEllipsis"
|
||||
class="pagination-ellipsis"
|
||||
>…</span>
|
||||
<slot
|
||||
v-else
|
||||
name="page-link"
|
||||
:page="p"
|
||||
:is-current="p.number === currentPage"
|
||||
>
|
||||
{{ p.number }}
|
||||
</slot>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed} from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
totalPages: number,
|
||||
currentPage: number
|
||||
}>()
|
||||
|
||||
function createPagination(totalPages: number, currentPage: number) {
|
||||
const pages = []
|
||||
for (let i = 0; i < totalPages; i++) {
|
||||
if (
|
||||
i > 0 &&
|
||||
(i + 1) < totalPages &&
|
||||
((i + 1) > currentPage + 1 || (i + 1) < currentPage - 1)
|
||||
) {
|
||||
if (pages[i - 1] && !pages[i - 1].isEllipsis) {
|
||||
pages.push({
|
||||
number: 0,
|
||||
isEllipsis: true,
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
pages.push({
|
||||
number: i + 1,
|
||||
isEllipsis: false,
|
||||
})
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
const pages = computed(() => createPagination(props.totalPages, props.currentPage))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pagination {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.pagination-previous,
|
||||
.pagination-next {
|
||||
&:not(:disabled):hover {
|
||||
background: $scheme-main;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-list {
|
||||
&, & li {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="datepicker-with-range-container">
|
||||
<Popup
|
||||
:open="open"
|
||||
@update:open="(open) => !open && emit('close')"
|
||||
@update:open="(open) => !open && $emit('update:open', false)"
|
||||
>
|
||||
<template #content="{isOpen}">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -69,6 +69,10 @@ const shouldShowMessage = computed(() => {
|
|||
@media screen and (min-width: $tablet) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media print {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.has-update-available {
|
||||
bottom: 5rem;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,10 @@
|
|||
class="task-detail-view-modal"
|
||||
@close="closeModal()"
|
||||
>
|
||||
<component :is="currentModal" />
|
||||
<component
|
||||
:is="currentModal"
|
||||
@close="closeModal()"
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<BaseButton
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
class="list-menu loader-container is-loading-small"
|
||||
:class="{'is-loading': isLoading}"
|
||||
>
|
||||
<div>
|
||||
<div class="navigation-item">
|
||||
<BaseButton
|
||||
v-if="canCollapse && childProjects?.length > 0"
|
||||
class="collapse-project-button"
|
||||
|
|
@ -193,4 +193,18 @@ const childProjects = computed(() => {
|
|||
.is-touch .handle.has-color-bubble {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.navigation-item:has(*:focus-visible) {
|
||||
box-shadow: 0 0 0 2px hsla(var(--primary-hsl), 0.5);
|
||||
background-color: var(--white);
|
||||
|
||||
.favorite, .menu-list-dropdown {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-item a:focus-visible {
|
||||
// The focus ring is already added to the navigation-item, so we don't need to add it again.
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ const emit = defineEmits<{
|
|||
}>()
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fancy-checkbox {
|
||||
display: inline-block;
|
||||
|
|
@ -70,8 +69,8 @@ const emit = defineEmits<{
|
|||
}
|
||||
}
|
||||
|
||||
.fancy-checkbox:not(:has(input:disabled)):hover .fancy-checkbox__icon,
|
||||
.fancy-checkbox:has(input:checked) .fancy-checkbox__icon {
|
||||
.fancy-checkbox:hover input:not(:disabled) + .fancy-checkbox__icon,
|
||||
.fancy-checkbox input:checked + .fancy-checkbox__icon {
|
||||
--stroke-color: var(--primary);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -80,13 +79,13 @@ const emit = defineEmits<{
|
|||
// Since css-has-pseudo doesn't work with deep classes,
|
||||
// the following rules can't be scoped
|
||||
|
||||
.fancy-checkbox:has(:not(input:checked)) .fancy-checkbox__icon {
|
||||
.fancy-checkbox :not(input:checked) + .fancy-checkbox__icon {
|
||||
path {
|
||||
transition-delay: 0.05s;
|
||||
}
|
||||
}
|
||||
|
||||
.fancy-checkbox:has(input:checked) .fancy-checkbox__icon {
|
||||
.fancy-checkbox input:checked + .fancy-checkbox__icon {
|
||||
path {
|
||||
stroke-dashoffset: 60;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
<div
|
||||
ref="multiselectRoot"
|
||||
class="multiselect"
|
||||
:class="{'has-search-results': searchResultsVisible}"
|
||||
:class="{'has-search-results': searchResultsVisible, 'is-disabled': disabled}"
|
||||
tabindex="-1"
|
||||
:aria-disabled="disabled"
|
||||
@focus="focus"
|
||||
>
|
||||
<div
|
||||
|
|
@ -12,7 +13,7 @@
|
|||
>
|
||||
<div
|
||||
class="input-wrapper input"
|
||||
:class="{'has-multiple': hasMultiple, 'has-removal-button': removalAvailable}"
|
||||
:class="{'has-multiple': hasMultiple, 'has-removal-button': removalAvailable && !disabled}"
|
||||
>
|
||||
<slot
|
||||
v-if="Array.isArray(internalValue)"
|
||||
|
|
@ -31,6 +32,7 @@
|
|||
>
|
||||
{{ label !== '' ? item[label] : item }}
|
||||
<BaseButton
|
||||
v-if="!disabled"
|
||||
class="delete is-small"
|
||||
@click="() => remove(item)"
|
||||
/>
|
||||
|
|
@ -40,6 +42,7 @@
|
|||
</slot>
|
||||
|
||||
<input
|
||||
v-if="!disabled"
|
||||
:id="id"
|
||||
ref="searchInput"
|
||||
v-model="query"
|
||||
|
|
@ -55,7 +58,7 @@
|
|||
@focus="handleFocus"
|
||||
>
|
||||
<BaseButton
|
||||
v-if="removalAvailable"
|
||||
v-if="removalAvailable && !disabled"
|
||||
class="removal-button"
|
||||
@click="resetSelectedValue"
|
||||
>
|
||||
|
|
@ -151,7 +154,7 @@ const props = withDefaults(defineProps<{
|
|||
/** The text shown next to the new value option. */
|
||||
createPlaceholder?: string
|
||||
/** The text shown next to an option. */
|
||||
selectPlaceholder: string
|
||||
selectPlaceholder?: string
|
||||
/** If true, allows for selecting multiple items. v-model will be an array with all selected values in that case. */
|
||||
multiple?: boolean
|
||||
/** If true, displays the search results inline instead of using a dropdown. */
|
||||
|
|
@ -164,6 +167,8 @@ const props = withDefaults(defineProps<{
|
|||
closeAfterSelect?: boolean
|
||||
/** If false, the search input will get the autocomplete="off" attributes attached to it. */
|
||||
autocompleteEnabled?: boolean
|
||||
/** If true, disables the multiselect input */
|
||||
disabled?: boolean
|
||||
}>(), {
|
||||
modelValue: null,
|
||||
loading: false,
|
||||
|
|
@ -179,6 +184,7 @@ const props = withDefaults(defineProps<{
|
|||
searchDelay: 200,
|
||||
closeAfterSelect: true,
|
||||
autocompleteEnabled: true,
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
@ -448,6 +454,18 @@ function focus() {
|
|||
.control.is-loading::after {
|
||||
top: .75rem;
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
.input-wrapper, .input, & {
|
||||
cursor: default !important;
|
||||
|
||||
&:focus-within,&:focus-visible, &:hover, &:focus {
|
||||
border-color: transparent !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import {ref, watchEffect} from 'vue'
|
|||
import {useDebounceFn} from '@vueuse/core'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import {validatePassword} from '@/helpers/validatePasswort'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: string,
|
||||
|
|
@ -60,22 +61,8 @@ const validateAfterFirst = ref(false)
|
|||
watchEffect(() => props.validateInitially && validate())
|
||||
|
||||
const validate = useDebounceFn(() => {
|
||||
if (password.value === '') {
|
||||
isValid.value = t('user.auth.passwordRequired')
|
||||
return
|
||||
}
|
||||
|
||||
if (props.validateMinLength && password.value.length < 8) {
|
||||
isValid.value = t('user.auth.passwordNotMin')
|
||||
return
|
||||
}
|
||||
|
||||
if (props.validateMinLength && password.value.length > 250) {
|
||||
isValid.value = t('user.auth.passwordNotMax')
|
||||
return
|
||||
}
|
||||
|
||||
isValid.value = true
|
||||
const valid = validatePassword(password.value, props.validateMinLength)
|
||||
isValid.value = valid === true ? true : t(valid)
|
||||
}, 100)
|
||||
|
||||
function togglePasswordFieldType() {
|
||||
|
|
|
|||
|
|
@ -314,7 +314,17 @@ const internalMode = ref<Mode>('preview')
|
|||
const isEditing = computed(() => internalMode.value === 'edit' && isEditEnabled)
|
||||
const contentHasChanged = ref<boolean>(false)
|
||||
|
||||
let lastSavedState = modelValue
|
||||
let lastSavedState = ''
|
||||
|
||||
watch(
|
||||
() => modelValue,
|
||||
(newValue) => {
|
||||
if (!contentHasChanged.value) {
|
||||
lastSavedState = newValue
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => internalMode.value,
|
||||
|
|
@ -325,6 +335,13 @@ watch(
|
|||
},
|
||||
)
|
||||
|
||||
const additionalLinkProtocols = [
|
||||
'ftp',
|
||||
'git',
|
||||
'obsidian',
|
||||
'notion',
|
||||
]
|
||||
|
||||
const extensions : Extensions = [
|
||||
// Starterkit:
|
||||
Blockquote,
|
||||
|
|
@ -379,7 +396,11 @@ const extensions : Extensions = [
|
|||
Underline,
|
||||
Link.configure({
|
||||
openOnClick: false,
|
||||
validate: (href: string) => /^https?:\/\//.test(href),
|
||||
validate: (href: string) => (new RegExp(
|
||||
`^(https?|${additionalLinkProtocols.join('|')}):\\/\\/`,
|
||||
'i',
|
||||
)).test(href),
|
||||
protocols: additionalLinkProtocols,
|
||||
}),
|
||||
Table.configure({
|
||||
resizable: true,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import {
|
|||
faDownload,
|
||||
faEllipsisH,
|
||||
faEllipsisV,
|
||||
faExclamation,
|
||||
faExclamationCircle,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faFile,
|
||||
|
|
@ -138,7 +138,7 @@ library.add(faCopy)
|
|||
library.add(faDownload)
|
||||
library.add(faEllipsisH)
|
||||
library.add(faEllipsisV)
|
||||
library.add(faExclamation)
|
||||
library.add(faExclamationCircle)
|
||||
library.add(faEye)
|
||||
library.add(faEyeSlash)
|
||||
library.add(faFillDrip)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@
|
|||
class="modal-container"
|
||||
@mousedown.self.prevent.stop="$emit('close')"
|
||||
>
|
||||
<BaseButton
|
||||
class="close"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<Icon icon="times" />
|
||||
</BaseButton>
|
||||
<div
|
||||
class="modal-content"
|
||||
:class="{
|
||||
|
|
@ -27,15 +33,8 @@
|
|||
'is-wide': wide
|
||||
}"
|
||||
>
|
||||
<BaseButton
|
||||
class="close"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<Icon icon="times" />
|
||||
</BaseButton>
|
||||
|
||||
<slot>
|
||||
<div class="header">
|
||||
<div class="modal-header">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div class="content">
|
||||
|
|
@ -137,11 +136,11 @@ $modal-width: 1024px;
|
|||
|
||||
@media screen and (max-width: $tablet) {
|
||||
margin: 0;
|
||||
top: 25%;
|
||||
transform: translate(-50%, -25%);
|
||||
position: static;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
.modal-header {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
|
@ -154,12 +153,15 @@ $modal-width: 1024px;
|
|||
// scrolling-content
|
||||
// used e.g. for <TaskDetailViewModal>
|
||||
.scrolling .modal-content {
|
||||
max-width: $modal-width;
|
||||
width: 100%;
|
||||
margin: $modal-margin auto;
|
||||
|
||||
max-height: none; // reset bulma
|
||||
overflow: visible; // reset bulma
|
||||
|
||||
@media not print {
|
||||
max-width: $modal-width;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $tablet) {
|
||||
max-height: none; // reset bulma
|
||||
|
|
@ -167,7 +169,7 @@ $modal-width: 1024px;
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $desktop) {
|
||||
@media screen and (max-width: $desktop), print {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -194,7 +196,7 @@ $modal-width: 1024px;
|
|||
position: fixed;
|
||||
top: .5rem;
|
||||
right: $close-button-padding;
|
||||
color: var(--grey-900);
|
||||
color: var(--white);
|
||||
font-size: 2rem;
|
||||
|
||||
@media screen and (min-width: $desktop) and (max-width: calc(#{$desktop } + #{$close-button-min-space})) {
|
||||
|
|
@ -203,15 +205,50 @@ $modal-width: 1024px;
|
|||
// we align the close button to the modal until there is enough space outside for it
|
||||
transform: translateX(calc((#{$modal-width} / 2) - #{$close-button-padding}));
|
||||
}
|
||||
// we can only use light color when there is enough space for the close button next to the modal
|
||||
@media screen and (min-width: calc(#{$desktop } + #{$close-button-min-space})) {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
@media screen and (min-width: $tablet) and (max-width: #{$desktop + $close-button-min-space}) {
|
||||
top: .75rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media print, screen and (max-width: $tablet) {
|
||||
.modal-mask {
|
||||
position: static;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
height: auto;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: static;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.card) {
|
||||
border: none !important;
|
||||
border-radius: 0 !important;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
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">
|
||||
|
|
@ -219,4 +256,10 @@ $modal-width: 1024px;
|
|||
.dark .close {
|
||||
color: var(--grey-900);
|
||||
}
|
||||
</style>
|
||||
|
||||
@media print, screen and (max-width: $tablet) {
|
||||
body:has(.modal-mask) #app {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,89 +1,49 @@
|
|||
<template>
|
||||
<nav
|
||||
v-if="totalPages > 1"
|
||||
aria-label="pagination"
|
||||
class="pagination is-centered p-4"
|
||||
role="navigation"
|
||||
<BasePagination
|
||||
:total-pages="totalPages"
|
||||
:current-page="currentPage"
|
||||
>
|
||||
<RouterLink
|
||||
:disabled="currentPage === 1 || undefined"
|
||||
:to="getRouteForPagination(currentPage - 1)"
|
||||
class="pagination-previous"
|
||||
>
|
||||
{{ $t('misc.previous') }}
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
:disabled="currentPage === totalPages || undefined"
|
||||
:to="getRouteForPagination(currentPage + 1)"
|
||||
class="pagination-next"
|
||||
>
|
||||
{{ $t('misc.next') }}
|
||||
</RouterLink>
|
||||
<ul class="pagination-list">
|
||||
<li
|
||||
v-for="(p, i) in pages"
|
||||
:key="`page-${i}`"
|
||||
<template #previous="{ disabled }">
|
||||
<RouterLink
|
||||
:disabled="disabled || undefined"
|
||||
:to="getRouteForPagination(currentPage - 1)"
|
||||
class="pagination-previous"
|
||||
>
|
||||
<span
|
||||
v-if="p.isEllipsis"
|
||||
class="pagination-ellipsis"
|
||||
>…</span>
|
||||
<RouterLink
|
||||
v-else
|
||||
class="pagination-link"
|
||||
:aria-label="'Goto page ' + p.number"
|
||||
:class="{ 'is-current': p.number === currentPage }"
|
||||
:to="getRouteForPagination(p.number)"
|
||||
>
|
||||
{{ p.number }}
|
||||
</RouterLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{{ $t('misc.previous') }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
<template #next="{ disabled }">
|
||||
<RouterLink
|
||||
:disabled="disabled || undefined"
|
||||
:to="getRouteForPagination(currentPage + 1)"
|
||||
class="pagination-next"
|
||||
>
|
||||
{{ $t('misc.next') }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
<template #page-link="{ page, isCurrent }">
|
||||
<RouterLink
|
||||
class="pagination-link"
|
||||
:aria-label="'Goto page ' + page.number"
|
||||
:class="{ 'is-current': isCurrent }"
|
||||
:to="getRouteForPagination(page.number)"
|
||||
>
|
||||
{{ page.number }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
</BasePagination>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed} from 'vue'
|
||||
import BasePagination from '@/components/base/BasePagination.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
withDefaults(defineProps<{
|
||||
totalPages: number,
|
||||
currentPage?: number
|
||||
}>(), {
|
||||
currentPage: 0,
|
||||
})
|
||||
|
||||
function createPagination(totalPages: number, currentPage: number) {
|
||||
const pages = []
|
||||
for (let i = 0; i < totalPages; i++) {
|
||||
|
||||
// Show ellipsis instead of all pages
|
||||
if (
|
||||
i > 0 && // Always at least the first page
|
||||
(i + 1) < totalPages && // And the last page
|
||||
(
|
||||
// And the current with current + 1 and current - 1
|
||||
(i + 1) > currentPage + 1 ||
|
||||
(i + 1) < currentPage - 1
|
||||
)
|
||||
) {
|
||||
// Only add an ellipsis if the last page isn't already one
|
||||
if (pages[i - 1] && !pages[i - 1].isEllipsis) {
|
||||
pages.push({
|
||||
number: 0,
|
||||
isEllipsis: true,
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
pages.push({
|
||||
number: i + 1,
|
||||
isEllipsis: false,
|
||||
})
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
function getRouteForPagination(page = 1, type = null) {
|
||||
return {
|
||||
name: type,
|
||||
|
|
@ -95,20 +55,4 @@ function getRouteForPagination(page = 1, type = null) {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
const pages = computed(() => createPagination(props.totalPages, props.currentPage))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pagination {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.pagination-previous,
|
||||
.pagination-next {
|
||||
&:not(:disabled):hover {
|
||||
background: $scheme-main;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</script>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<BasePagination
|
||||
:total-pages="totalPages"
|
||||
:current-page="currentPage"
|
||||
>
|
||||
<template #previous="{ disabled }">
|
||||
<BaseButton
|
||||
:disabled="disabled"
|
||||
class="pagination-previous"
|
||||
@click="changePage(currentPage - 1)"
|
||||
>
|
||||
{{ $t('misc.previous') }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
<template #next="{ disabled }">
|
||||
<BaseButton
|
||||
:disabled="disabled"
|
||||
class="pagination-next"
|
||||
@click="changePage(currentPage + 1)"
|
||||
>
|
||||
{{ $t('misc.next') }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
<template #page-link="{ page, isCurrent }">
|
||||
<BaseButton
|
||||
class="pagination-link"
|
||||
:aria-label="'Goto page ' + page.number"
|
||||
:class="{ 'is-current': isCurrent }"
|
||||
@click="changePage(page.number)"
|
||||
>
|
||||
{{ page.number }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
</BasePagination>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BasePagination from '@/components/base/BasePagination.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
totalPages: number,
|
||||
currentPage: number
|
||||
}>(), {
|
||||
currentPage: 1,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['pageChanged'])
|
||||
|
||||
function changePage(page: number) {
|
||||
if (page >= 1 && page <= props.totalPages) {
|
||||
emit('pageChanged', page)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -218,33 +218,35 @@ function handleFieldInput() {
|
|||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [matched, prefix, operator, space, keyword] = match
|
||||
if (keyword) {
|
||||
let search = keyword
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
const keywords = keyword.split(',')
|
||||
search = keywords[keywords.length - 1].trim()
|
||||
}
|
||||
if (matched.startsWith('label')) {
|
||||
autocompleteResultType.value = 'labels'
|
||||
autocompleteResults.value = labelStore.filterLabelsByQuery([], search)
|
||||
}
|
||||
if (matched.startsWith('assignee')) {
|
||||
autocompleteResultType.value = 'assignees'
|
||||
if (props.projectId) {
|
||||
projectUserService.getAll({projectId: props.projectId}, {s: search})
|
||||
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
|
||||
} else {
|
||||
userService.getAll({}, {s: search})
|
||||
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
|
||||
}
|
||||
}
|
||||
if (!props.projectId && matched.startsWith('project')) {
|
||||
autocompleteResultType.value = 'projects'
|
||||
autocompleteResults.value = projectStore.searchProject(search)
|
||||
}
|
||||
autocompleteMatchText.value = keyword
|
||||
autocompleteMatchPosition.value = match.index + prefix.length - 1 + keyword.replace(search, '').length
|
||||
if(!keyword) {
|
||||
return
|
||||
}
|
||||
|
||||
let search = keyword
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
const keywords = keyword.split(',')
|
||||
search = keywords[keywords.length - 1].trim()
|
||||
}
|
||||
if (matched.startsWith('label')) {
|
||||
autocompleteResultType.value = 'labels'
|
||||
autocompleteResults.value = labelStore.filterLabelsByQuery([], search)
|
||||
}
|
||||
if (matched.startsWith('assignee')) {
|
||||
autocompleteResultType.value = 'assignees'
|
||||
if (props.projectId) {
|
||||
projectUserService.getAll({projectId: props.projectId}, {s: search})
|
||||
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
|
||||
} else {
|
||||
userService.getAll({}, {s: search})
|
||||
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
|
||||
}
|
||||
}
|
||||
if (!props.projectId && matched.startsWith('project')) {
|
||||
autocompleteResultType.value = 'projects'
|
||||
autocompleteResults.value = projectStore.searchProject(search)
|
||||
}
|
||||
autocompleteMatchText.value = keyword
|
||||
autocompleteMatchPosition.value = match.index + prefix.length - 1 + keyword.replace(search, '').length
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ watch(
|
|||
const val = {...value}
|
||||
val.filter = transformFilterStringFromApi(
|
||||
val?.filter || '',
|
||||
labelId => labelStore.getLabelById(labelId)?.title,
|
||||
labelId => labelStore.getLabelById(labelId)?.title || null,
|
||||
projectId => projectStore.projects[projectId]?.title || null,
|
||||
)
|
||||
params.value = val
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
<BaseButton
|
||||
class="project-button"
|
||||
:aria-label="project.title"
|
||||
:title="project.description"
|
||||
:title="textOnlyDescription"
|
||||
:to="{
|
||||
name: 'project.index',
|
||||
params: { projectId: project.id}
|
||||
|
|
@ -53,6 +53,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed} from 'vue'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
|
@ -68,6 +69,10 @@ const props = defineProps<{
|
|||
const {background, blurHashUrl} = useProjectBackground(() => props.project)
|
||||
|
||||
const projectStore = useProjectStore()
|
||||
|
||||
const textOnlyDescription = computed(() => {
|
||||
return props.project.description ? props.project.description.replace(/<[^>]*>/g, '') : ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<ul class="project-grid">
|
||||
<ul
|
||||
class="project-grid"
|
||||
:class="{ 'show-even-number-of-projects': showEvenNumberOfProjects }"
|
||||
>
|
||||
<li
|
||||
v-for="(item, index) in filteredProjects"
|
||||
:key="`project_${item.id}_${index}`"
|
||||
|
|
@ -19,11 +22,13 @@ import ProjectCard from './ProjectCard.vue'
|
|||
const props = withDefaults(defineProps<{
|
||||
projects: IProject[],
|
||||
showArchived?: boolean,
|
||||
itemLimit?: boolean
|
||||
itemLimit?: boolean,
|
||||
showEvenNumberOfProjects?: boolean,
|
||||
}>(), {
|
||||
projects: () => [],
|
||||
showArchived: false,
|
||||
itemLimit: false,
|
||||
showEvenNumberOfProjects: false,
|
||||
})
|
||||
|
||||
const filteredProjects = computed(() => {
|
||||
|
|
@ -58,9 +63,13 @@ const filteredProjects = computed(() => {
|
|||
|
||||
@media screen and (min-width: $widescreen) {
|
||||
--project-grid-columns: 5;
|
||||
}
|
||||
|
||||
.project-grid-item:nth-child(6) {
|
||||
display: none;
|
||||
&.show-even-number-of-projects {
|
||||
@media screen and (min-width: $widescreen) {
|
||||
.project-grid-item:nth-child(5) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
@click="() => unCollapseBucket(bucket)"
|
||||
>
|
||||
<span
|
||||
v-if="view?.doneBucketId === bucket.id"
|
||||
v-if="bucket.id !== 0 && view?.doneBucketId === bucket.id"
|
||||
v-tooltip="$t('project.kanban.doneBucketHint')"
|
||||
class="icon is-small has-text-success mr-2"
|
||||
>
|
||||
|
|
@ -512,6 +512,7 @@ async function updateTaskPosition(e) {
|
|||
taskId: newTask.id,
|
||||
})
|
||||
await taskPositionService.value.update(newPosition)
|
||||
newTask.position = position
|
||||
|
||||
if(bucketHasChanged) {
|
||||
const updatedTaskBucket = await taskBucketService.value.update(new TaskBucketModel({
|
||||
|
|
@ -524,8 +525,8 @@ async function updateTaskPosition(e) {
|
|||
if (updatedTaskBucket.bucketId !== newTask.bucketId) {
|
||||
kanbanStore.moveTaskToBucket(newTask, updatedTaskBucket.bucketId)
|
||||
}
|
||||
kanbanStore.setTaskInBucket(newTask)
|
||||
}
|
||||
kanbanStore.setTaskInBucket(newTask)
|
||||
|
||||
// Make sure the first and second task don't both get position 0 assigned
|
||||
if (newTaskIndex === 0 && taskAfter !== null && taskAfter.position === 0) {
|
||||
|
|
@ -546,6 +547,9 @@ async function updateTaskPosition(e) {
|
|||
}
|
||||
|
||||
function toggleShowNewTaskInput(bucketId: IBucket['id']) {
|
||||
if (loading.value || taskLoading.value) {
|
||||
return
|
||||
}
|
||||
showNewTaskInput.value[bucketId] = !showNewTaskInput.value[bucketId]
|
||||
newTaskInputFocused.value = false
|
||||
}
|
||||
|
|
@ -562,6 +566,7 @@ async function addTaskToBucket(bucketId: IBucket['id']) {
|
|||
bucketId,
|
||||
projectId: project.value.id,
|
||||
})
|
||||
toggleShowNewTaskInput(bucketId)
|
||||
newTaskText.value = ''
|
||||
kanbanStore.addTaskToBucket(task)
|
||||
scrollTaskContainerToBottom(bucketId)
|
||||
|
|
@ -766,6 +771,18 @@ function unCollapseBucket(bucket: IBucket) {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.control.is-loading {
|
||||
&::after {
|
||||
top: 30%;
|
||||
right: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
--loader-border-color: var(--grey-500);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
$ease-out: all .3s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
$bucket-width: 300px;
|
||||
|
|
@ -788,8 +805,9 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
|||
}
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
height: calc(#{$crazy-height-calculation} - #{$filter-container-height});
|
||||
height: calc(#{$crazy-height-calculation} - #{$filter-container-height} + 9px);
|
||||
scroll-snap-type: x mandatory;
|
||||
margin: 0 -0.5rem;
|
||||
}
|
||||
|
||||
&-bucket-container {
|
||||
|
|
@ -867,7 +885,6 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
|||
// to hide the fact we just made the button smaller.
|
||||
min-width: calc(#{$bucket-width} + 1rem);
|
||||
background: transparent;
|
||||
padding-right: 1rem;
|
||||
|
||||
.button {
|
||||
background: var(--grey-100);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@
|
|||
|
||||
<draggable
|
||||
v-if="tasks && tasks.length > 0"
|
||||
v-bind="DRAG_OPTIONS"
|
||||
v-model="tasks"
|
||||
group="tasks"
|
||||
handle=".handle"
|
||||
|
|
@ -59,6 +58,8 @@
|
|||
},
|
||||
type: 'transition-group'
|
||||
}"
|
||||
:animation="100"
|
||||
ghost-class="task-ghost"
|
||||
@start="() => drag = true"
|
||||
@end="saveTaskPosition"
|
||||
>
|
||||
|
|
@ -73,7 +74,7 @@
|
|||
>
|
||||
<template v-if="canWrite">
|
||||
<span class="icon handle">
|
||||
<Icon icon="grip-lines"/>
|
||||
<Icon icon="grip-lines" />
|
||||
</span>
|
||||
</template>
|
||||
</SingleTaskInProject>
|
||||
|
|
@ -128,10 +129,6 @@ const props = defineProps<{
|
|||
const ctaVisible = ref(false)
|
||||
|
||||
const drag = ref(false)
|
||||
const DRAG_OPTIONS = {
|
||||
animation: 100,
|
||||
ghostClass: 'task-ghost',
|
||||
} as const
|
||||
|
||||
const {
|
||||
tasks: allTasks,
|
||||
|
|
|
|||
|
|
@ -289,6 +289,7 @@ import type {ITask} from '@/modelTypes/ITask'
|
|||
import type {IProject} from '@/modelTypes/IProject'
|
||||
import AssigneeList from '@/components/tasks/partials/AssigneeList.vue'
|
||||
import type {IProjectView} from '@/modelTypes/IProjectView'
|
||||
import { camelCase } from 'change-case'
|
||||
|
||||
const props = defineProps<{
|
||||
projectId: IProject['id'],
|
||||
|
|
@ -355,7 +356,7 @@ function sort(property: keyof SortBy) {
|
|||
|
||||
function setActiveColumnsSortParam() {
|
||||
sortByParam.value = Object.keys(sortBy.value)
|
||||
.filter(prop => activeColumns.value[prop])
|
||||
.filter(prop => activeColumns.value[camelCase(prop)])
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = sortBy.value[key]
|
||||
return obj
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const projectStore = useProjectStore()
|
|||
onBeforeMount(() => {
|
||||
const transform = (filterString: string) => transformFilterStringFromApi(
|
||||
filterString,
|
||||
labelId => labelStore.getLabelById(labelId)?.title,
|
||||
labelId => labelStore.getLabelById(labelId)?.title || null,
|
||||
projectId => projectStore.projects[projectId]?.title || null,
|
||||
)
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ onBeforeMount(() => {
|
|||
function save() {
|
||||
const transformFilter = (filterQuery: string) => transformFilterStringForApi(
|
||||
filterQuery,
|
||||
labelTitle => labelStore.filterLabelsByQuery([], labelTitle)[0]?.id || null,
|
||||
labelTitle => labelStore.getLabelByExactTitle(labelTitle)?.id || null,
|
||||
projectTitle => {
|
||||
const found = projectStore.findProjectByExactname(projectTitle)
|
||||
return found?.id || null
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
import {computed, ref} from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {useElementHover} from '@vueuse/core'
|
||||
import {useRouter} from 'vue-router'
|
||||
|
||||
import {RELATION_KIND} from '@/types/IRelationKind'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
|
|
@ -66,6 +67,8 @@ import {useAuthStore} from '@/stores/auth'
|
|||
import {useTaskStore} from '@/stores/tasks'
|
||||
|
||||
import {useAutoHeightTextarea} from '@/composables/useAutoHeightTextarea'
|
||||
import TaskService from '@/services/task'
|
||||
import TaskModel from '@/models/task'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
defaultPosition?: number,
|
||||
|
|
@ -81,6 +84,7 @@ const {textarea: newTaskInput} = useAutoHeightTextarea(newTaskTitle)
|
|||
const {t} = useI18n({useScope: 'global'})
|
||||
const authStore = useAuthStore()
|
||||
const taskStore = useTaskStore()
|
||||
const router = useRouter()
|
||||
|
||||
// enable only if we don't have a modal
|
||||
// onStartTyping(() => {
|
||||
|
|
@ -129,21 +133,55 @@ async function addTask() {
|
|||
const allLabels = tasksToCreate.map(({title}) => getLabelsFromPrefix(title, authStore.settings.frontendSettings.quickAddMagicMode) ?? [])
|
||||
await taskStore.ensureLabelsExist(allLabels.flat())
|
||||
|
||||
const newTasks = tasksToCreate.map(async ({title, project}) => {
|
||||
const taskCollectionService = new TaskService()
|
||||
const projectIndices = new Map<number, number>()
|
||||
|
||||
let currentProjectId = authStore.settings.defaultProjectId
|
||||
if (typeof router.currentRoute.value.params.projectId !== 'undefined') {
|
||||
currentProjectId = Number(router.currentRoute.value.params.projectId)
|
||||
}
|
||||
|
||||
// Create a map of project indices before creating tasks
|
||||
if (tasksToCreate.length > 1) {
|
||||
for (const {project} of tasksToCreate) {
|
||||
const projectId = project !== null
|
||||
? await taskStore.findProjectId({project, projectId: 0})
|
||||
: currentProjectId
|
||||
|
||||
if (!projectIndices.has(projectId)) {
|
||||
const newestTask = await taskCollectionService.getAll(new TaskModel({}), {
|
||||
sort_by: ['id'],
|
||||
order_by: ['desc'],
|
||||
per_page: 1,
|
||||
filter: `project_id = ${projectId}`,
|
||||
})
|
||||
projectIndices.set(projectId, newestTask[0]?.index || 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newTasks = tasksToCreate.map(async ({title, project}, index) => {
|
||||
if (title === '') {
|
||||
return
|
||||
}
|
||||
|
||||
// If the task has a project specified, make sure to use it
|
||||
let projectId = null
|
||||
if (project !== null) {
|
||||
projectId = await taskStore.findProjectId({project, projectId: 0})
|
||||
const projectId = project !== null
|
||||
? await taskStore.findProjectId({project, projectId: 0})
|
||||
: currentProjectId
|
||||
|
||||
// Calculate new index for this task per project
|
||||
let taskIndex: number | undefined
|
||||
if (tasksToCreate.length > 1) {
|
||||
const lastIndex = projectIndices.get(projectId)
|
||||
taskIndex = lastIndex + index + 1
|
||||
}
|
||||
|
||||
const task = await taskStore.createNewTask({
|
||||
title,
|
||||
projectId: projectId || authStore.settings.defaultProjectId,
|
||||
position: props.defaultPosition,
|
||||
index: taskIndex,
|
||||
})
|
||||
createdTasks[title] = task
|
||||
return task
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="enabled"
|
||||
class="content details"
|
||||
ref="commentsRef"
|
||||
class="content details comments-container"
|
||||
>
|
||||
<h3
|
||||
v-if="canWrite || comments.length > 0"
|
||||
|
|
@ -15,7 +16,7 @@
|
|||
<div class="comments">
|
||||
<span
|
||||
v-if="taskCommentService.loading && saving === null && !creating"
|
||||
class="is-inline-flex is-align-items-center"
|
||||
class="is-flex is-align-items-center my-4 ml-2"
|
||||
>
|
||||
<span class="loader is-inline-block mr-2" />
|
||||
{{ $t('task.comment.loading') }}
|
||||
|
|
@ -107,6 +108,14 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PaginationEmit
|
||||
v-if="taskCommentService.totalPages > 1"
|
||||
:total-pages="taskCommentService.totalPages"
|
||||
:current-page="currentPage"
|
||||
@pageChanged="changePage"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="canWrite"
|
||||
class="media comment d-print-none"
|
||||
|
|
@ -160,6 +169,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<Modal
|
||||
:enabled="showDeleteModal"
|
||||
@close="showDeleteModal = false"
|
||||
|
|
@ -185,6 +195,7 @@ import {useI18n} from 'vue-i18n'
|
|||
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import Editor from '@/components/input/AsyncEditor'
|
||||
import PaginationEmit from '@/components/misc/PaginationEmit.vue'
|
||||
|
||||
import TaskCommentService from '@/services/taskComment'
|
||||
import TaskCommentModel from '@/models/taskComment'
|
||||
|
|
@ -242,6 +253,12 @@ const actions = computed(() => {
|
|||
])))
|
||||
})
|
||||
|
||||
const frontendUrl = computed(() => configStore.frontendUrl)
|
||||
|
||||
const currentPage = ref(1)
|
||||
|
||||
const commentsRef = ref<HTMLElement | null>(null)
|
||||
|
||||
async function attachmentUpload(files: File[] | FileList): (Promise<string[]>) {
|
||||
|
||||
const uploadPromises: Promise<string>[] = []
|
||||
|
|
@ -267,12 +284,21 @@ async function loadComments(taskId: ITask['id']) {
|
|||
newComment.taskId = taskId
|
||||
commentEdit.taskId = taskId
|
||||
commentToDelete.taskId = taskId
|
||||
comments.value = await taskCommentService.getAll({taskId})
|
||||
comments.value = await taskCommentService.getAll({taskId}, {}, currentPage.value)
|
||||
}
|
||||
|
||||
async function changePage(page: number) {
|
||||
commentsRef.value?.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' })
|
||||
currentPage.value = page
|
||||
await loadComments(props.taskId)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.taskId,
|
||||
loadComments,
|
||||
() => {
|
||||
currentPage.value = 1 // Reset to first page when task changes
|
||||
loadComments(props.taskId)
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
|
|
@ -404,4 +430,8 @@ async function deleteComment(commentToDelete: ITaskComment) {
|
|||
.media-content {
|
||||
width: calc(100% - 48px - 2rem);
|
||||
}
|
||||
|
||||
.comments-container {
|
||||
scroll-margin-top: 4rem;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -135,18 +135,8 @@ async function updateDueDate() {
|
|||
$defer-task-max-width: 350px + 100px;
|
||||
|
||||
.defer-task {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
max-width: $defer-task-max-width;
|
||||
border-radius: $radius;
|
||||
border: 1px solid var(--grey-200);
|
||||
padding: 1rem;
|
||||
margin: 1rem;
|
||||
background: var(--white);
|
||||
color: var(--text);
|
||||
cursor: default;
|
||||
z-index: 10;
|
||||
box-shadow: var(--shadow-lg);
|
||||
|
||||
@media screen and (max-width: ($defer-task-max-width)) {
|
||||
left: .5rem;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
:create-placeholder="$t('task.label.createPlaceholder')"
|
||||
:search-delay="10"
|
||||
:close-after-select="false"
|
||||
:disabled="disabled"
|
||||
@search="findLabel"
|
||||
@select="addLabel"
|
||||
@create="createAndAddLabel"
|
||||
|
|
@ -21,6 +22,7 @@
|
|||
>
|
||||
<span>{{ label.title }}</span>
|
||||
<BaseButton
|
||||
v-if="!disabled"
|
||||
v-cy="'taskDetail.removeLabel'"
|
||||
class="delete is-small"
|
||||
@click="removeLabel(label)"
|
||||
|
|
|
|||
|
|
@ -1,20 +1,27 @@
|
|||
<template>
|
||||
<div class="heading">
|
||||
<div class="flex is-align-items-center">
|
||||
<BaseButton @click="copyUrl">
|
||||
<h1 class="title task-id">
|
||||
{{ textIdentifier }}
|
||||
</h1>
|
||||
</BaseButton>
|
||||
<div class="tw-flex tw-items-center md:tw-items-stretch tw-flex-col tw-gap-1 task-properties">
|
||||
<div class="tw-flex tw-items-center tw-gap-2">
|
||||
<ColorBubble
|
||||
v-if="task.hexColor !== ''"
|
||||
:color="getHexColor(task.hexColor)"
|
||||
/>
|
||||
<BaseButton @click="copyUrl">
|
||||
<h1 class="title task-id">
|
||||
{{ textIdentifier }}
|
||||
</h1>
|
||||
</BaseButton>
|
||||
</div>
|
||||
<Done
|
||||
class="heading__done"
|
||||
:is-done="task.done"
|
||||
/>
|
||||
<ColorBubble
|
||||
v-if="task.hexColor !== ''"
|
||||
:color="getHexColor(task.hexColor)"
|
||||
class="ml-2"
|
||||
/>
|
||||
<BaseButton
|
||||
v-if="hasClose"
|
||||
class="close"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<Icon icon="times" />
|
||||
</BaseButton>
|
||||
</div>
|
||||
<h1
|
||||
class="title input"
|
||||
|
|
@ -26,6 +33,13 @@
|
|||
>
|
||||
{{ task.title.trim() }}
|
||||
</h1>
|
||||
<BaseButton
|
||||
v-if="hasClose"
|
||||
class="close"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<Icon icon="times" />
|
||||
</BaseButton>
|
||||
<CustomTransition name="fade">
|
||||
<span
|
||||
v-if="loading && saving"
|
||||
|
|
@ -66,12 +80,15 @@ 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<{
|
||||
'update:task': [task: ITask]
|
||||
'update:task': [task: ITask],
|
||||
'close': [],
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
|
|
@ -138,14 +155,11 @@ async function save(title: string) {
|
|||
.title.input {
|
||||
// 1.8rem is the font-size, 1.125 is the line-height, .3rem padding everywhere, 1px border around the whole thing.
|
||||
min-height: calc(1.8rem * 1.125 + .6rem + 2px);
|
||||
margin-right: 0;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
margin: 0 -.3rem .5rem -.3rem; // the title has 0.3rem padding - this make the text inside of it align with the rest
|
||||
}
|
||||
|
||||
@media screen and (min-width: $tablet) and (max-width: #{$desktop + $close-button-min-space}) {
|
||||
width: calc(100% - 6.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
.title.task-id {
|
||||
|
|
@ -153,12 +167,39 @@ async function save(title: string) {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.heading__done {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.color-bubble {
|
||||
height: .75rem;
|
||||
width: .75rem;
|
||||
}
|
||||
|
||||
.close {
|
||||
font-size: 2rem;
|
||||
margin-left: 0.5rem;
|
||||
line-height: 1;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: #{$desktop + 1px}) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.task-properties .close {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 1.25rem;
|
||||
top: 1.1rem;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.task-properties {
|
||||
@media screen and (max-width: $tablet) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
v-if="coverImageBlobUrl"
|
||||
:src="coverImageBlobUrl"
|
||||
alt=""
|
||||
class="cover-image"
|
||||
class="tw-w-full"
|
||||
>
|
||||
<div class="p-2">
|
||||
<span class="task-id">
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
<template>
|
||||
<span
|
||||
v-if="!done && (showAll || priority >= priorities.HIGH)"
|
||||
:class="{'not-so-high': priority === priorities.HIGH, 'high-priority': priority >= priorities.HIGH}"
|
||||
:class="{
|
||||
'not-so-high': priority === priorities.HIGH,
|
||||
'high-priority': priority >= priorities.HIGH
|
||||
}"
|
||||
class="priority-label"
|
||||
>
|
||||
<span
|
||||
v-if="priority >= priorities.HIGH"
|
||||
class="icon"
|
||||
>
|
||||
<Icon icon="exclamation" />
|
||||
<Icon icon="exclamation-circle" />
|
||||
</span>
|
||||
<span>
|
||||
<template v-if="priority === priorities.UNSET">{{ $t('task.priority.unset') }}</template>
|
||||
|
|
@ -18,12 +21,6 @@
|
|||
<template v-if="priority === priorities.URGENT">{{ $t('task.priority.urgent') }}</template>
|
||||
<template v-if="priority === priorities.DO_NOW">{{ $t('task.priority.doNow') }}</template>
|
||||
</span>
|
||||
<span
|
||||
v-if="priority === priorities.DO_NOW"
|
||||
class="icon pr-0"
|
||||
>
|
||||
<Icon icon="exclamation" />
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
|
@ -42,18 +39,18 @@ withDefaults(defineProps<{
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
span.high-priority {
|
||||
.high-priority {
|
||||
color: var(--danger);
|
||||
width: auto !important; // To override the width set in tasks
|
||||
}
|
||||
|
||||
.icon {
|
||||
vertical-align: top;
|
||||
width: auto !important;
|
||||
padding: 0 .5rem;
|
||||
}
|
||||
.not-so-high {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
&.not-so-high {
|
||||
color: var(--warning);
|
||||
}
|
||||
.icon {
|
||||
vertical-align: top;
|
||||
width: auto !important;
|
||||
padding-right: .5rem;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -36,9 +36,11 @@ import Multiselect from '@/components/input/Multiselect.vue'
|
|||
const props = withDefaults(defineProps<{
|
||||
modelValue?: IProject
|
||||
savedFiltersOnly?: boolean
|
||||
filter?: (project: IProject) => boolean,
|
||||
}>(), {
|
||||
modelValue: () => new ProjectModel(),
|
||||
savedFiltersOnly: false,
|
||||
filter: () => true,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
@ -65,11 +67,13 @@ function findProjects(query: string) {
|
|||
}
|
||||
|
||||
if (props.savedFiltersOnly) {
|
||||
foundProjects.value = projectStore.searchSavedFilter(query)
|
||||
const found = projectStore.searchSavedFilter(query)
|
||||
foundProjects.value = found.filter(props.filter)
|
||||
return
|
||||
}
|
||||
|
||||
foundProjects.value = projectStore.searchProject(query)
|
||||
const found = projectStore.searchProject(query)
|
||||
foundProjects.value = found.filter(props.filter)
|
||||
}
|
||||
|
||||
function select(p: IProject | null) {
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@
|
|||
<FancyCheckbox
|
||||
v-model="task.done"
|
||||
class="task-done-checkbox"
|
||||
@update:modelValue="toggleTaskDone(t)"
|
||||
@update:modelValue="toggleTaskDone(task)"
|
||||
/>
|
||||
<RouterLink
|
||||
:to="{ name: route.name as string, params: { id: task.id } }"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<div
|
||||
v-for="(r, index) in reminders"
|
||||
:key="index"
|
||||
:class="{ 'overdue': r.reminder < new Date() }"
|
||||
:class="{ 'overdue': r.reminder < now }"
|
||||
class="reminder-input"
|
||||
>
|
||||
<ReminderDetail
|
||||
|
|
@ -40,21 +40,25 @@ import BaseButton from '@/components/base/BaseButton.vue'
|
|||
import ReminderDetail from '@/components/tasks/partials/ReminderDetail.vue'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import {REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo'
|
||||
import { useNow } from '@vueuse/core'
|
||||
|
||||
const {
|
||||
modelValue,
|
||||
disabled = false,
|
||||
} = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: ITask,
|
||||
disabled?: boolean,
|
||||
}>()
|
||||
}>(), {
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [ITask]
|
||||
}>()
|
||||
|
||||
const reminders = ref<ITaskReminder[]>([])
|
||||
|
||||
const now = useNow({interval: 1000})
|
||||
|
||||
watch(
|
||||
() => modelValue.reminders,
|
||||
() => props.modelValue.reminders,
|
||||
(newVal) => {
|
||||
reminders.value = newVal
|
||||
},
|
||||
|
|
@ -62,19 +66,19 @@ watch(
|
|||
)
|
||||
|
||||
const defaultRelativeTo = computed(() => {
|
||||
if (typeof modelValue === 'undefined') {
|
||||
if (typeof props.modelValue === 'undefined') {
|
||||
return null
|
||||
}
|
||||
|
||||
if (modelValue?.dueDate) {
|
||||
if (props.modelValue?.dueDate) {
|
||||
return REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE
|
||||
}
|
||||
|
||||
if (modelValue.dueDate === null && modelValue.startDate !== null) {
|
||||
if (props.modelValue.dueDate === null && props.modelValue.startDate !== null) {
|
||||
return REMINDER_PERIOD_RELATIVE_TO_TYPES.STARTDATE
|
||||
}
|
||||
|
||||
if (modelValue.dueDate === null && modelValue.startDate === null && modelValue.endDate !== null) {
|
||||
if (props.modelValue.dueDate === null && props.modelValue.startDate === null && props.modelValue.endDate !== null) {
|
||||
return REMINDER_PERIOD_RELATIVE_TO_TYPES.ENDDATE
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +87,7 @@ const defaultRelativeTo = computed(() => {
|
|||
|
||||
function updateData() {
|
||||
emit('update:modelValue', {
|
||||
...modelValue,
|
||||
...props.modelValue,
|
||||
reminders: reminders.value,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<template>
|
||||
<div>
|
||||
<div
|
||||
ref="taskContainerRef"
|
||||
:class="{'is-loading': taskService.loading}"
|
||||
class="task loader-container single-task"
|
||||
tabindex="-1"
|
||||
@mouseup.stop.self="openTaskDetail"
|
||||
@mousedown.stop.self="focusTaskLink"
|
||||
@click="openTaskDetail"
|
||||
@keyup.enter="openTaskDetail"
|
||||
>
|
||||
<FancyCheckbox
|
||||
v-model="task.done"
|
||||
:disabled="(isArchived || disabled) && !canMarkAsDone"
|
||||
@update:modelValue="markAsDone"
|
||||
@click.stop
|
||||
/>
|
||||
|
||||
<ColorBubble
|
||||
|
|
@ -23,8 +23,6 @@
|
|||
<div
|
||||
:class="{ 'done': task.done, 'show-project': showProject && project}"
|
||||
class="tasktext"
|
||||
@mouseup.stop.self="openTaskDetail"
|
||||
@mousedown.stop.self="focusTaskLink"
|
||||
>
|
||||
<span>
|
||||
<RouterLink
|
||||
|
|
@ -33,6 +31,7 @@
|
|||
:to="{ name: 'project.index', params: { projectId: task.projectId } }"
|
||||
class="task-project mr-1"
|
||||
:class="{'mr-2': task.hexColor !== ''}"
|
||||
@click.stop
|
||||
>
|
||||
{{ project.title }}
|
||||
</RouterLink>
|
||||
|
|
@ -42,15 +41,15 @@
|
|||
:color="getHexColor(task.hexColor)"
|
||||
class="mr-1"
|
||||
/>
|
||||
|
||||
|
||||
<PriorityLabel
|
||||
:priority="task.priority"
|
||||
:done="task.done"
|
||||
class="pr-2"
|
||||
/>
|
||||
|
||||
|
||||
<RouterLink
|
||||
ref="taskLink"
|
||||
ref="taskLinkRef"
|
||||
:to="taskDetailRoute"
|
||||
class="task-link"
|
||||
tabindex="-1"
|
||||
|
|
@ -73,29 +72,33 @@
|
|||
:inline="true"
|
||||
/>
|
||||
|
||||
<!-- FIXME: use popup -->
|
||||
<BaseButton
|
||||
<Popup
|
||||
v-if="+new Date(task.dueDate) > 0"
|
||||
v-tooltip="formatDateLong(task.dueDate)"
|
||||
class="dueDate"
|
||||
@click.prevent.stop="showDefer = !showDefer"
|
||||
>
|
||||
<time
|
||||
:datetime="formatISO(task.dueDate)"
|
||||
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||
class="is-italic"
|
||||
:aria-expanded="showDefer ? 'true' : 'false'"
|
||||
>
|
||||
– {{ $t('task.detail.due', {at: dueDateFormatted}) }}
|
||||
</time>
|
||||
</BaseButton>
|
||||
<CustomTransition name="fade">
|
||||
<DeferTask
|
||||
v-if="+new Date(task.dueDate) > 0 && showDefer"
|
||||
ref="deferDueDate"
|
||||
v-model="task"
|
||||
/>
|
||||
</CustomTransition>
|
||||
<template #trigger="{toggle, isOpen}">
|
||||
<BaseButton
|
||||
v-tooltip="formatDateLong(task.dueDate)"
|
||||
class="dueDate"
|
||||
@click.prevent.stop="toggle()"
|
||||
>
|
||||
<time
|
||||
:datetime="formatISO(task.dueDate)"
|
||||
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||
class="is-italic"
|
||||
:aria-expanded="isOpen ? 'true' : 'false'"
|
||||
>
|
||||
– {{ $t('task.detail.due', {at: dueDateFormatted}) }}
|
||||
</time>
|
||||
</BaseButton>
|
||||
</template>
|
||||
<template #content="{isOpen}">
|
||||
<DeferTask
|
||||
v-if="isOpen"
|
||||
v-model="task"
|
||||
/>
|
||||
</template>
|
||||
</Popup>
|
||||
|
||||
|
||||
<span>
|
||||
<span
|
||||
|
|
@ -138,6 +141,7 @@
|
|||
v-tooltip="$t('task.detail.belongsToProject', {project: project.title})"
|
||||
:to="{ name: 'project.index', params: { projectId: task.projectId } }"
|
||||
class="task-project"
|
||||
@click.stop
|
||||
>
|
||||
{{ project.title }}
|
||||
</RouterLink>
|
||||
|
|
@ -145,7 +149,7 @@
|
|||
<BaseButton
|
||||
:class="{'is-favorite': task.isFavorite}"
|
||||
class="favorite"
|
||||
@click="toggleFavorite"
|
||||
@click.stop="toggleFavorite"
|
||||
>
|
||||
<Icon
|
||||
v-if="task.isFavorite"
|
||||
|
|
@ -176,7 +180,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, watch, shallowReactive, onMounted, onBeforeUnmount, computed} from 'vue'
|
||||
import {ref, watch, shallowReactive, onMounted, computed} from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import TaskModel, {getHexColor} from '@/models/task'
|
||||
|
|
@ -191,11 +195,10 @@ import ProgressBar from '@/components/misc/ProgressBar.vue'
|
|||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import FancyCheckbox from '@/components/input/FancyCheckbox.vue'
|
||||
import ColorBubble from '@/components/misc/ColorBubble.vue'
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import Popup from '@/components/misc/Popup.vue'
|
||||
|
||||
import TaskService from '@/services/task'
|
||||
|
||||
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
||||
import {formatDateSince, formatISO, formatDateLong} from '@/helpers/time/formatDate'
|
||||
import {success} from '@/message'
|
||||
|
||||
|
|
@ -239,7 +242,6 @@ const {t} = useI18n({useScope: 'global'})
|
|||
|
||||
const taskService = shallowReactive(new TaskService())
|
||||
const task = ref<ITask>(new TaskModel())
|
||||
const showDefer = ref(false)
|
||||
|
||||
const isRepeating = computed(() => task.value.repeatAfter.amount > 0 || (task.value.repeatAfter.amount === 0 && task.value.repeatMode === TASK_REPEAT_MODES.REPEAT_MODE_MONTH))
|
||||
|
||||
|
|
@ -254,14 +256,6 @@ watch(
|
|||
},
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', hideDeferDueDatePopup)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', hideDeferDueDatePopup)
|
||||
})
|
||||
|
||||
const baseStore = useBaseStore()
|
||||
const projectStore = useProjectStore()
|
||||
const taskStore = useTaskStore()
|
||||
|
|
@ -350,35 +344,22 @@ async function toggleFavorite() {
|
|||
emit('taskUpdated', task.value)
|
||||
}
|
||||
|
||||
const deferDueDate = ref<typeof DeferTask | null>(null)
|
||||
|
||||
function hideDeferDueDatePopup(e) {
|
||||
if (!showDefer.value) {
|
||||
return
|
||||
}
|
||||
closeWhenClickedOutside(e, deferDueDate.value.$el, () => {
|
||||
showDefer.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const taskLink = ref<HTMLElement | null>(null)
|
||||
const taskContainerRef = ref<HTMLElement | null>(null)
|
||||
const taskLinkRef = ref<HTMLElement | null>(null)
|
||||
|
||||
function hasTextSelected() {
|
||||
const isTextSelected = window.getSelection().toString()
|
||||
return !(typeof isTextSelected === 'undefined' || isTextSelected === '' || isTextSelected === '\n')
|
||||
}
|
||||
|
||||
function openTaskDetail() {
|
||||
if (!hasTextSelected()) {
|
||||
taskLink.value.$el.click()
|
||||
function openTaskDetail(event: MouseEvent | KeyboardEvent) {
|
||||
if (event.target instanceof HTMLElement) {
|
||||
const isInteractiveElement = event.target.closest('a, button, .favorite, [role="button"]')
|
||||
if (isInteractiveElement || hasTextSelected()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function focusTaskLink() {
|
||||
if (!hasTextSelected()) {
|
||||
taskContainerRef.value.focus()
|
||||
}
|
||||
taskLinkRef.value?.$el.click()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -397,7 +378,7 @@ function focusTaskLink() {
|
|||
background-color: var(--grey-100);
|
||||
}
|
||||
|
||||
&:focus-within, &:focus {
|
||||
&:has(*:focus-visible) {
|
||||
box-shadow: 0 0 0 2px hsla(var(--primary-hsl), 0.5);
|
||||
|
||||
a.task-link {
|
||||
|
|
@ -405,6 +386,16 @@ function focusTaskLink() {
|
|||
}
|
||||
}
|
||||
|
||||
@supports not selector(:focus-within) {
|
||||
:focus {
|
||||
box-shadow: 0 0 0 2px hsla(var(--primary-hsl), 0.5);
|
||||
|
||||
a.task-link {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tasktext,
|
||||
&.tasktext {
|
||||
text-overflow: ellipsis;
|
||||
|
|
@ -421,6 +412,15 @@ function focusTaskLink() {
|
|||
.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,6 +466,7 @@ function focusTaskLink() {
|
|||
text-align: center;
|
||||
width: 27px;
|
||||
transition: opacity $transition, color $transition;
|
||||
border-radius: $radius;
|
||||
|
||||
&:hover {
|
||||
color: var(--warning);
|
||||
|
|
@ -542,4 +543,17 @@ function focusTaskLink() {
|
|||
.subtask-nested {
|
||||
margin-left: 1.75rem;
|
||||
}
|
||||
|
||||
:deep(.popup) {
|
||||
border-radius: $radius;
|
||||
background-color: var(--white);
|
||||
box-shadow: var(--shadow-lg);
|
||||
color: var(--text);
|
||||
top: unset;
|
||||
|
||||
&.is-open {
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--grey-200);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
export const calculateItemPosition = (positionBefore: number | null, positionAfter: number | null): number => {
|
||||
export const calculateItemPosition = (
|
||||
positionBefore: number | null = null,
|
||||
positionAfter: number | null = null,
|
||||
): number => {
|
||||
if (positionBefore === null) {
|
||||
if (positionAfter === null) {
|
||||
return 0
|
||||
|
|
@ -13,6 +16,6 @@ export const calculateItemPosition = (positionBefore: number | null, positionAft
|
|||
return positionBefore + Math.pow(2, 16)
|
||||
}
|
||||
|
||||
// If we have both a task before and after it, we acually calculate the position
|
||||
// If we have both a task before and after it, we actually calculate the position
|
||||
return positionBefore + (positionAfter - positionBefore) / 2
|
||||
}
|
||||
|
|
@ -147,6 +147,26 @@ describe('Filter Transformation', () => {
|
|||
|
||||
expect(transformed).toBe('start_date > now')
|
||||
})
|
||||
|
||||
it('should correctly resolve label when the label is called label', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'labels = label',
|
||||
(title: string) => 1,
|
||||
nullTitleToIdResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('labels = 1')
|
||||
})
|
||||
|
||||
it('should correctly resolve project when the project is called project', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'project = project',
|
||||
nullTitleToIdResolver,
|
||||
(title: string) => 1,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project = 1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('To API', () => {
|
||||
|
|
@ -199,6 +219,26 @@ describe('Filter Transformation', () => {
|
|||
expect(transformed).toBe('labels in lorem, ipsum')
|
||||
})
|
||||
|
||||
it('should not touch the label value when it is undefined', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'labels = one',
|
||||
(id: number) => undefined,
|
||||
nullIdToTitleResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('labels = one')
|
||||
})
|
||||
|
||||
it('should not touch the label value when it is null', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'labels = one',
|
||||
(id: number) => null,
|
||||
nullIdToTitleResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('labels = one')
|
||||
})
|
||||
|
||||
it('should correctly resolve projects', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'project = 1',
|
||||
|
|
@ -228,6 +268,26 @@ describe('Filter Transformation', () => {
|
|||
|
||||
expect(transformed).toBe('project in lorem, ipsum')
|
||||
})
|
||||
|
||||
it('should not touch the project value when it is undefined', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'project = one',
|
||||
nullIdToTitleResolver,
|
||||
(id: number) => undefined,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project = one')
|
||||
})
|
||||
|
||||
it('should not touch the project value when it is null', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'project = one',
|
||||
nullIdToTitleResolver,
|
||||
(id: number) => null,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project = one')
|
||||
})
|
||||
|
||||
it('should transform the same attribute multiple times', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
|
|
|
|||
|
|
@ -77,49 +77,34 @@ export function transformFilterStringForApi(
|
|||
filter = filter.replace(new RegExp(f, 'ig'), f)
|
||||
})
|
||||
|
||||
// Transform labels to ids
|
||||
LABEL_FIELDS.forEach(field => {
|
||||
const pattern = getFilterFieldRegexPattern(field)
|
||||
// Transform labels and projects to ids
|
||||
function transformFieldToIds(
|
||||
fields: string[],
|
||||
resolver: (title: string) => number | null,
|
||||
filter: string,
|
||||
): string {
|
||||
fields.forEach(field => {
|
||||
const pattern = getFilterFieldRegexPattern(field)
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = pattern.exec(filter)) !== null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [matched, prefix, operator, space, keyword] = match
|
||||
if (!keyword) {
|
||||
continue
|
||||
}
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = pattern.exec(filter)) !== null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [matched, prefix, operator, space, keyword] = match
|
||||
if (keyword) {
|
||||
let keywords = [keyword.trim()]
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
keywords = keyword.trim().split(',').map(k => k.trim())
|
||||
}
|
||||
|
||||
keywords.forEach(k => {
|
||||
const labelId = labelResolver(k)
|
||||
if (labelId !== null) {
|
||||
filter = filter.replace(k, String(labelId))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
// Transform projects to ids
|
||||
PROJECT_FIELDS.forEach(field => {
|
||||
const pattern = getFilterFieldRegexPattern(field)
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = pattern.exec(filter)) !== null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [matched, prefix, operator, space, keyword] = match
|
||||
if (keyword) {
|
||||
let keywords = [keyword.trim()]
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
keywords = keyword.trim().split(',').map(k => k.trim())
|
||||
}
|
||||
|
||||
|
||||
let replaced = keyword
|
||||
|
||||
keywords.forEach(k => {
|
||||
const projectId = projectResolver(k)
|
||||
if (projectId !== null) {
|
||||
replaced = replaced.replace(k, String(projectId))
|
||||
const id = resolver(k)
|
||||
if (id !== null) {
|
||||
replaced = replaced.replace(k, String(id))
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -128,8 +113,15 @@ export function transformFilterStringForApi(
|
|||
replaced +
|
||||
filter.substring(actualKeywordStart + keyword.length)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
return filter
|
||||
}
|
||||
|
||||
// Transform labels to ids
|
||||
filter = transformFieldToIds(LABEL_FIELDS, labelResolver, filter)
|
||||
|
||||
// Transform projects to ids
|
||||
filter = transformFieldToIds(PROJECT_FIELDS, projectResolver, filter)
|
||||
|
||||
// Transform all attributes to snake case
|
||||
AVAILABLE_FILTER_FIELDS.forEach(f => {
|
||||
|
|
@ -141,8 +133,8 @@ export function transformFilterStringForApi(
|
|||
|
||||
export function transformFilterStringFromApi(
|
||||
filter: string,
|
||||
labelResolver: (id: number) => string | null,
|
||||
projectResolver: (id: number) => string | null,
|
||||
labelResolver: (id: number) => string | null | undefined,
|
||||
projectResolver: (id: number) => string | null | undefined,
|
||||
): string {
|
||||
|
||||
if (filter.trim() === '') {
|
||||
|
|
@ -154,53 +146,40 @@ export function transformFilterStringFromApi(
|
|||
filter = filter.replaceAll(snakeCase(f), f)
|
||||
})
|
||||
|
||||
// Function to transform fields to their titles
|
||||
function transformFieldsToTitles(
|
||||
fields: string[],
|
||||
resolver: (id: number) => string | null | undefined,
|
||||
) {
|
||||
fields.forEach(field => {
|
||||
const pattern = getFilterFieldRegexPattern(field)
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = pattern.exec(filter)) !== null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [matched, prefix, operator, space, keyword] = match
|
||||
if (keyword) {
|
||||
let keywords = [keyword.trim()]
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
keywords = keyword.trim().split(',').map(k => k.trim())
|
||||
}
|
||||
|
||||
keywords.forEach(k => {
|
||||
const title = resolver(parseInt(k))
|
||||
if (title) {
|
||||
filter = filter.replace(k, title)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Transform labels to their titles
|
||||
LABEL_FIELDS.forEach(field => {
|
||||
const pattern = getFilterFieldRegexPattern(field)
|
||||
transformFieldsToTitles(LABEL_FIELDS, labelResolver)
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = pattern.exec(filter)) !== null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [matched, prefix, operator, space, keyword] = match
|
||||
if (keyword) {
|
||||
let keywords = [keyword.trim()]
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
keywords = keyword.trim().split(',').map(k => k.trim())
|
||||
}
|
||||
|
||||
keywords.forEach(k => {
|
||||
const labelTitle = labelResolver(parseInt(k))
|
||||
if (labelTitle !== null) {
|
||||
filter = filter.replace(k, labelTitle)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Transform projects to ids
|
||||
PROJECT_FIELDS.forEach(field => {
|
||||
const pattern = getFilterFieldRegexPattern(field)
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = pattern.exec(filter)) !== null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [matched, prefix, operator, space, keyword] = match
|
||||
if (keyword) {
|
||||
let keywords = [keyword.trim()]
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
keywords = keyword.trim().split(',').map(k => k.trim())
|
||||
}
|
||||
|
||||
keywords.forEach(k => {
|
||||
const project = projectResolver(parseInt(k))
|
||||
if (project !== null) {
|
||||
filter = filter.replace(k, project)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
// Transform projects to their titles
|
||||
transformFieldsToTitles(PROJECT_FIELDS, projectResolver)
|
||||
|
||||
return filter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
export function validatePassword(password: string, validateMinLength: boolean = true): string | true {
|
||||
if (password === '') {
|
||||
return 'user.auth.passwordRequired'
|
||||
}
|
||||
|
||||
if (validateMinLength && password.length < 8) {
|
||||
return 'user.auth.passwordNotMin'
|
||||
}
|
||||
|
||||
if (validateMinLength && password.length > 72) {
|
||||
return 'user.auth.passwordNotMax'
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ export const SUPPORTED_LOCALES = {
|
|||
'sl-SI': 'Slovenščina',
|
||||
'pt-BR': 'Português Brasileiro',
|
||||
'hr-HR': 'Hrvatski',
|
||||
'uk-UA': 'українська',
|
||||
'uk-UA': 'Українська',
|
||||
// IMPORTANT: Also add new languages to useDayjsLanguageSync
|
||||
} as const
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "الرجاء إدخال كلمة المرور.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "إظهار كلمة المرور",
|
||||
"hidePassword": "إخفاء كلمة المرور",
|
||||
"noAccountYet": "ليس لديك حساب بعد؟",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 يوماً",
|
||||
"permissionExplanation": "نطاق الصلاحيات المسموح به هو ما يسمح لك القيام به رمز Api.",
|
||||
"titleRequired": "العنوان إجباري",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "انتهت صلاحية هذا الرمز {ago}.",
|
||||
"tokenCreatedSuccess": "إليك رمز Api الجديد: {token}",
|
||||
"tokenCreatedNotSeeAgain": "قم بتخزينه في مكان آمن، لن تتمكن من عرضه مرة أخرى!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "حذف هذا التعليق",
|
||||
"deleteText1": "هل أنت متأكد من رغبتك بحذف هذا التعليق؟",
|
||||
"deleteSuccess": "تم حذف التعليق بنجاح.",
|
||||
"addedSuccess": "تمت إضافة التعليق بنجاح."
|
||||
"addedSuccess": "تمت إضافة التعليق بنجاح.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "تأجيل تاريخ الاستحقاق",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "المشاركة في المشروع غير موجودة.",
|
||||
"3007": "يوجد مشروع بهذا المعرف مسبقاً.",
|
||||
"3008": "المشروع مؤرشف وبالتالي يمكن الوصول إليه للقراءة فقط، وهذا ينطبق أيضا على جميع المهام المرتبطة بهذا المشروع.",
|
||||
"4001": "لا يمكن ترك نص مهمة المشروع فارغاً.",
|
||||
"4002": "مهمة المشروع غير موجودة.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "يجب أن تنتمي جميع إجراءات التعديل المجمعة لنفس المشروع.",
|
||||
"4004": "تحتاج إلى مهمة واحدة على الأقل عند القيام بإجراءات التعديل بالجملة.",
|
||||
"4005": "ليس لديك الصلاحية لعرض المهمة.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Please provide a password.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Show the password",
|
||||
"hidePassword": "Hide the password",
|
||||
"noAccountYet": "Don't have an account yet?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,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",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Need at least one task when bulk editing tasks.",
|
||||
"4005": "You do not have the right to see the task.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "Uživatelské jméno nesmí vypadat jako adresa URL.",
|
||||
"passwordRequired": "Zadejte prosím heslo.",
|
||||
"passwordNotMin": "Heslo musí mít nejméně 8 znaků.",
|
||||
"passwordNotMax": "Heslo může mít maximálně 250 znaků.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Ukázat heslo",
|
||||
"hidePassword": "Skrýt heslo",
|
||||
"noAccountYet": "Ještě nemáte účet?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 dní",
|
||||
"permissionExplanation": "Oprávnění vám umožní nastavit, k čemu lze api token použít.",
|
||||
"titleRequired": "Je vyžadován název",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "Platnost tohoto tokenu vypršela {ago}.",
|
||||
"tokenCreatedSuccess": "Zde je tvůj nový api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Ulož jej na zabezpečeném místě, už ho znovu neuvidíš!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Smazat tento komentář",
|
||||
"deleteText1": "Opravdu chcete smazat tento komentář?",
|
||||
"deleteSuccess": "Komentář byl úspěšně odstraněn.",
|
||||
"addedSuccess": "Komentář byl úspěšně přidán."
|
||||
"addedSuccess": "Komentář byl úspěšně přidán.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Odložit datum dokončení",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "Sdílení projektu neexistuje.",
|
||||
"3007": "Projekt s tímto identifikátorem již existuje.",
|
||||
"3008": "Projekt je archivován, a proto je přístupný pouze pro čtení. To platí i pro všechny úkoly spojené s tímto projektem.",
|
||||
"4001": "Text úkolu projektu nemůže být prázdný.",
|
||||
"4002": "Úkol projektu neexistuje.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "Všechny úkoly pro hromadnou úpravu musí patřit do stejného projektu.",
|
||||
"4004": "Při hromadných úpravách úkolů je potřeba alespoň jeden úkol.",
|
||||
"4005": "Nemáte právo vidět tento úkol.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Angiv venligst en adgangskode.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Vis adgangskoden",
|
||||
"hidePassword": "Skjul adgangskoden",
|
||||
"noAccountYet": "Har du ikke en konto endnu?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Slet denne kommentar",
|
||||
"deleteText1": "Er du sikker på du vil slette denne kommentar?",
|
||||
"deleteSuccess": "Kommentaren blev slettet.",
|
||||
"addedSuccess": "Din kommentar blev tilføjet."
|
||||
"addedSuccess": "Din kommentar blev tilføjet.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Udsæt forfaldsdato",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Der skal være mindst én opgave for at masseredigere opgaver.",
|
||||
"4005": "Du har ikke rettigheder til at se opgaven.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "Der Anmeldename darf nicht wie eine URL aussehen.",
|
||||
"passwordRequired": "Bitte gib ein Passwort ein.",
|
||||
"passwordNotMin": "Das Passwort muss aus mindestens 8 Zeichen bestehen.",
|
||||
"passwordNotMax": "Das Passwort darf höchstens 250 Zeichen lang sein.",
|
||||
"passwordNotMax": "Das Passwort darf höchstens 72 Zeichen lang sein.",
|
||||
"showPassword": "Passwort anzeigen",
|
||||
"hidePassword": "Passwort verbergen",
|
||||
"noAccountYet": "Noch kein Account?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Tage",
|
||||
"permissionExplanation": "Mit Berechtigungen kannst du einschränken, was ein API-Token tun darf.",
|
||||
"titleRequired": "Titel ist erforderlich",
|
||||
"permissionRequired": "Bitte wähle mindestens eine Berechtigung aus der Liste.",
|
||||
"expired": "Dieses Token ist {ago} abgelaufen.",
|
||||
"tokenCreatedSuccess": "Hier ist dein neues API Token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Speichere es an einem sicheren Ort, du wirst es nicht mehr sehen!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Diesen Kommentar löschen",
|
||||
"deleteText1": "Bist du sicher, dass du diesen Kommentar löschen willst?",
|
||||
"deleteSuccess": "Der Kommentar wurde erfolgreich gelöscht.",
|
||||
"addedSuccess": "Der Kommentar wurde erfolgreich hinzugefügt."
|
||||
"addedSuccess": "Der Kommentar wurde erfolgreich hinzugefügt.",
|
||||
"permalink": "Permalink zu diesem Kommentar kopieren"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Fälligkeitsdatum verschieben",
|
||||
|
|
@ -1153,7 +1155,7 @@
|
|||
"3006": "Diese Linkfreigabe existiert nicht.",
|
||||
"3007": "Ein Projekt mit diesem Bezeichner existiert bereits.",
|
||||
"3008": "Dieses Projekt ist archiviert und kann deshalb nur gelesen werden. Dies gilt auch für alle Aufgaben, die mit diesem Projekt verbunden sind.",
|
||||
"4001": "Der Aufgabentitel kann nicht leer sein.",
|
||||
"4001": "Der Titel darf nicht leer sein.",
|
||||
"4002": "Diese Aufgabe existiert nicht.",
|
||||
"4003": "Alle Massenbearbeitungen an Aufgaben müssen zum selben Projekt gehören.",
|
||||
"4004": "Es benötigt mindestens einen Task, um eine Massenänderung durchzuführen.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "Der Anmeldename darf nicht wie eine URL aussehen.",
|
||||
"passwordRequired": "Bitte gib ein Passwort ein.",
|
||||
"passwordNotMin": "Das Passwort muss aus mindestens 8 Zeichen bestehen.",
|
||||
"passwordNotMax": "Das Passwort darf höchstens 250 Zeichen lang sein.",
|
||||
"passwordNotMax": "Das Passwort darf höchstens 72 Zeichen lang sein.",
|
||||
"showPassword": "Passwort anzeigen",
|
||||
"hidePassword": "Passwort verbergen",
|
||||
"noAccountYet": "Noch kein Account?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Tage",
|
||||
"permissionExplanation": "Mit Berechtigungen kannst du einschränken, was ein API-Token tun darf.",
|
||||
"titleRequired": "Titel ist erforderlich",
|
||||
"permissionRequired": "Bitte wähle mindestens eine Berechtigung aus der Liste.",
|
||||
"expired": "Dieses Token ist {ago} abgelaufen.",
|
||||
"tokenCreatedSuccess": "Hier ist dein neues API Token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Speichere es an einem sicheren Ort, du wirst es nicht mehr sehen!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "De Kommentar chüble",
|
||||
"deleteText1": "Bisch du dir sicher, dass du de Kommentar chüble wetsch?",
|
||||
"deleteSuccess": "Der Kommentar wurde erfolgreich gelöscht.",
|
||||
"addedSuccess": "Din Kommentar isch erfolgriich hinzuegfüegt worde."
|
||||
"addedSuccess": "Din Kommentar isch erfolgriich hinzuegfüegt worde.",
|
||||
"permalink": "Permalink zu diesem Kommentar kopieren"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Fälligkeitsdatum verschiebe",
|
||||
|
|
@ -1153,7 +1155,7 @@
|
|||
"3006": "Diese Linkfreigabe existiert nicht.",
|
||||
"3007": "Ein Projekt mit diesem Bezeichner existiert bereits.",
|
||||
"3008": "Dieses Projekt ist archiviert und kann deshalb nur gelesen werden. Dies gilt auch für alle Aufgaben, die mit diesem Projekt verbunden sind.",
|
||||
"4001": "Der Aufgabentitel kann nicht leer sein.",
|
||||
"4001": "Der Titel darf nicht leer sein.",
|
||||
"4002": "Diese Aufgabe existiert nicht.",
|
||||
"4003": "Alle Massenbearbeitungen an Aufgaben müssen zum selben Projekt gehören.",
|
||||
"4004": "Es bruucht mindestens ei Uufgab, um e Masseänderig durezfüehre.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Please provide a password.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Show the password",
|
||||
"hidePassword": "Hide the password",
|
||||
"noAccountYet": "Don't have an account yet?",
|
||||
|
|
@ -1154,8 +1154,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Need at least one task when bulk editing tasks.",
|
||||
"4005": "You do not have the right to see the task.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Please provide a password.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Show the password",
|
||||
"hidePassword": "Hide the password",
|
||||
"noAccountYet": "Don't have an account yet?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,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",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Need at least one task when bulk editing tasks.",
|
||||
"4005": "You do not have the right to see the task.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Por favor, proporciona una contraseña.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Mostrar la contraseña",
|
||||
"hidePassword": "Ocultar la contraseña",
|
||||
"noAccountYet": "¿Aún no tienes una cuenta?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Eliminar este comentario",
|
||||
"deleteText1": "¿Está seguro que desea eliminar este comentario?",
|
||||
"deleteSuccess": "El comentario fue eliminado con éxito.",
|
||||
"addedSuccess": "El comentario fue añadido con éxito."
|
||||
"addedSuccess": "El comentario fue añadido con éxito.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Aplazar fecha de vencimiento",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "La compartición del proyecto no existe.",
|
||||
"3007": "Ya existe un proyecto con este identificador.",
|
||||
"3008": "El proyecto está archivado y por lo tanto sólo se puede acceder a él en modo lectura. Esto también se aplica a las tareas asociadas con este proyecto.",
|
||||
"4001": "El texto de la tarea del proyecto no puede estar vacío.",
|
||||
"4002": "La tarea del proyecto no existe.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "Todas las tareas de edición en masa deben pertenecer al mismo proyecto.",
|
||||
"4004": "Se necesita al menos una tarea cuando se editan tareas masivamente.",
|
||||
"4005": "No tiene permiso para ver la tarea.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Veuillez fournir un mot de passe.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Afficher le mot de passe",
|
||||
"hidePassword": "Masquer le mot de passe",
|
||||
"noAccountYet": "Vous n'avez pas encore de compte ?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Supprimer ce commentaire",
|
||||
"deleteText1": "Supprimer ce commentaire ?",
|
||||
"deleteSuccess": "Le commentaire a bien été supprimé.",
|
||||
"addedSuccess": "Commentaire ajouté."
|
||||
"addedSuccess": "Commentaire ajouté.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Reporter la date d’échéance",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "Le partage de ce projet n’existe pas.",
|
||||
"3007": "Un projet avec cet identifiant existe déjà.",
|
||||
"3008": "Le projet est archivé et ne peut donc être consulté qu’en lecture seule. Ceci est également vrai pour toutes les tâches associées à ce projet.",
|
||||
"4001": "Le texte de la tâche du projet ne peut pas être vide.",
|
||||
"4002": "La tâche de projet n’existe pas.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "Toutes les tâches de modification en bloc doivent appartenir au même projet.",
|
||||
"4004": "Besoin d’au moins une tâche lors de la modification en bloc de tâches.",
|
||||
"4005": "Vous n’avez pas le droit de voir la tâche.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "Korisničko ime ne smije izgledati kao URL.",
|
||||
"passwordRequired": "Molimo upišite lozinku.",
|
||||
"passwordNotMin": "Lozinka treba imati barem 8 znakova.",
|
||||
"passwordNotMax": "Lozinka treba imati barem 250 znakova.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Prikaži lozinku",
|
||||
"hidePassword": "Sakrij lozinku",
|
||||
"noAccountYet": "Još nemate račun?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 dana",
|
||||
"permissionExplanation": "Dopuštenja vam omogućuju da odredite što api token smije raditi.",
|
||||
"titleRequired": "Naslov je obavezan",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "Ovaj je token istekao {ago}.",
|
||||
"tokenCreatedSuccess": "Evo vašeg novog api tokena: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Čuvajte ga na sigurnom mjestu, nećete ga više vidjeti!",
|
||||
|
|
@ -847,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",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Need at least one task when bulk editing tasks.",
|
||||
"4005": "You do not have the right to see the task.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "A felhasználónév nem nézhet ki URL-nek.",
|
||||
"passwordRequired": "Kérjük, adjon meg új jelszót.",
|
||||
"passwordNotMin": "A jelszónak legalább 8 karakterből kell állnia.",
|
||||
"passwordNotMax": "A jelszó legfeljebb 250 karakterből állhat.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Jelszó megjelenítése",
|
||||
"hidePassword": "A jelszó elrejtése",
|
||||
"noAccountYet": "Még nincs fiókja?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 nap",
|
||||
"permissionExplanation": "Az engedélyek lehetővé teszik annak hatókörét, hogy egy API Token mire jogosult.",
|
||||
"titleRequired": "A cím kötelező",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "Ez a token lejárt {ago}.",
|
||||
"tokenCreatedSuccess": "Íme az új API tokenje: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Tárolja el biztonságos helyen, többé nem fogja látni!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Hozzászólás törlése",
|
||||
"deleteText1": "Biztos benne, hogy törölni akarja ezt a hozzászólást?",
|
||||
"deleteSuccess": "A hozzászólás sikeresen törlődött.",
|
||||
"addedSuccess": "A hozzászólás sikeresen hozzáadva."
|
||||
"addedSuccess": "A hozzászólás sikeresen hozzáadva.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "A határidő elhalasztása",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "A projektmegosztás nem létezik.",
|
||||
"3007": "Ezzel az azonosítóval már létezik projekt.",
|
||||
"3008": "A projekt archiválva van, ezért csak olvasható. Ez a projekthez kapcsolódó összes feladatra is igaz.",
|
||||
"4001": "A projektfeladat szövege nem lehet üres.",
|
||||
"4002": "A projektfeladat nem létezik.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "Minden tömeges szerkesztési feladatnak ugyanahhoz a projekthez kell tartoznia.",
|
||||
"4004": "A feladatok tömeges szerkesztéséhez legalább egy feladatra van szükség.",
|
||||
"4005": "Nincs joga a feladat megtekintéséhez.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Inserisci una password.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Mostra la password",
|
||||
"hidePassword": "Nascondi la password",
|
||||
"noAccountYet": "Non hai un account?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Elimina questo commento",
|
||||
"deleteText1": "Sei sicuro di voler eliminare questo commento?",
|
||||
"deleteSuccess": "Commento cancellato con successo.",
|
||||
"addedSuccess": "Il commento è stato aggiunto correttamente."
|
||||
"addedSuccess": "Il commento è stato aggiunto correttamente.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Rinvia data di scadenza",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Hai bisogno di almeno un'attività quando si modificano in blocco le attività.",
|
||||
"4005": "Non hai il permesso di vedere l'attività.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "パスワードを入力してください。",
|
||||
"passwordNotMin": "パスワードは8文字以上でなければなりません。",
|
||||
"passwordNotMax": "パスワードは250文字以内でなければなりません。",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "パスワードの表示",
|
||||
"hidePassword": "パスワードの非表示",
|
||||
"noAccountYet": "まだアカウントをお持ちでないですか?",
|
||||
|
|
@ -99,7 +99,7 @@
|
|||
"defaultView": "デフォルトのビュー",
|
||||
"timezone": "タイムゾーン",
|
||||
"overdueTasksRemindersTime": "期限切れタスクのリマインダー送信時間",
|
||||
"filterUsedOnOverview": "概要ページに使用される絞り込み条件"
|
||||
"filterUsedOnOverview": "概要ページの絞り込み条件"
|
||||
},
|
||||
"totp": {
|
||||
"title": "2要素認証",
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
},
|
||||
"apiTokens": {
|
||||
"title": "APIトークン",
|
||||
"general": "APIトークンは、認証されたVikunja APIのリクエストを行うことができます。",
|
||||
"general": "APIトークンを使うとログインせずにVikunjaのAPIを利用できます。",
|
||||
"apiDocs": "詳しくはAPIドキュメントをご確認ください",
|
||||
"createAToken": "トークンの生成",
|
||||
"createToken": "トークンの生成",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90日",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "トークン名を入力してください。",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "このトークンは二度と表示されません。安全な場所に保管してください。",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "コメントの削除",
|
||||
"deleteText1": "このコメントを削除して本当によろしいですか?",
|
||||
"deleteSuccess": "コメントは正常に削除されました。",
|
||||
"addedSuccess": "コメントは正常に追加されました。"
|
||||
"addedSuccess": "コメントは正常に追加されました。",
|
||||
"permalink": "コメントへのリンクをコピー"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "延期",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "その共有プロジェクトは存在しません。",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "このプロジェクトはアーカイブ済みのため読み取り専用です。またプロジェクトに関連するタスクも同様です。",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "そのプロジェクトのタスクは存在しません。",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Need at least one task when bulk editing tasks.",
|
||||
"4005": "You do not have the right to see the task.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "비밀번호를 입력하세요.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "비밀번호 표시",
|
||||
"hidePassword": "비밀번호 숨김",
|
||||
"noAccountYet": "아직 계정이 없으신가요?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "의견 삭제",
|
||||
"deleteText1": "의견을 삭제하시겠습니까?",
|
||||
"deleteSuccess": "의견이 성공적으로 삭제되었습니다!",
|
||||
"addedSuccess": "The comment was added successfully."
|
||||
"addedSuccess": "The comment was added successfully.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Defer due date",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Need at least one task when bulk editing tasks.",
|
||||
"4005": "You do not have the right to see the task.",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Please provide a password.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Show the password",
|
||||
"hidePassword": "Hide the password",
|
||||
"noAccountYet": "Don't have an account yet?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Verwijder deze reactie",
|
||||
"deleteText1": "Weet je zeker dat je deze reactie wilt verwijderen?",
|
||||
"deleteSuccess": "The comment was deleted successfully.",
|
||||
"addedSuccess": "De reactie is succesvol toegevoegd."
|
||||
"addedSuccess": "De reactie is succesvol toegevoegd.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Vervaldatum uitstellen",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Need at least one task when bulk editing tasks.",
|
||||
"4005": "You do not have the right to see the task.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Angi et passord.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Vis passord",
|
||||
"hidePassword": "Skjul passord",
|
||||
"noAccountYet": "Har du ikke konto ennå?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Slett denne kommentaren",
|
||||
"deleteText1": "Er du sikker på at du vil slette denne kommentaren?",
|
||||
"deleteSuccess": "Sletting av kommentaren vellykket.",
|
||||
"addedSuccess": "Denne kommentaren ble lagt til."
|
||||
"addedSuccess": "Denne kommentaren ble lagt til.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Utsatt forfallsdato",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "Prosjektdeling finnes ikke.",
|
||||
"3007": "Et prosjekt med denne identifikatoren eksisterer allerede.",
|
||||
"3008": "Prosjektet er arkivert og kan derfor bare leses inn. Dette gjelder også for alle oppgaver som er tilknyttet dette prosjektet.",
|
||||
"4001": "Prosjektets oppgavetekst kan ikke være tom.",
|
||||
"4002": "Prosjektoppgaven finnes ikke.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "Alle bulkredigering oppgaver må tilhøre samme prosjekt.",
|
||||
"4004": "Trenger minst én oppgave når masseredigeringsoppgaver skal utføres.",
|
||||
"4005": "Du har ikke rettigheter til å redigere denne siden.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "Nazwa użytkownika nie może wyglądać jak adres URL.",
|
||||
"passwordRequired": "Proszę podać hasło.",
|
||||
"passwordNotMin": "Hasło musi zawierać co najmniej 8 znaków.",
|
||||
"passwordNotMax": "Hasło musi zawierać co najwyżej 250 znaków.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Pokaż hasło",
|
||||
"hidePassword": "Ukryj hasło",
|
||||
"noAccountYet": "Nie masz jeszcze konta?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 dni",
|
||||
"permissionExplanation": "Uprawnienia pozwalają określić, co token API ma prawo robić.",
|
||||
"titleRequired": "Tytuł jest wymagany",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "Ten token wygasł {ago}.",
|
||||
"tokenCreatedSuccess": "Oto twój nowy token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Przechowuj go w bezpiecznym miejscu, nie zobaczysz go ponownie!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Usuń ten komentarz",
|
||||
"deleteText1": "Czy na pewno chcesz usunąć ten komentarz?",
|
||||
"deleteSuccess": "Komentarz został pomyślnie usunięty.",
|
||||
"addedSuccess": "Komentarz został pomyślnie dodany."
|
||||
"addedSuccess": "Komentarz został pomyślnie dodany.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Odroczenie terminu",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "Udostępnienie projektu nie istnieje.",
|
||||
"3007": "Projekt z tym identyfikatorem już istnieje.",
|
||||
"3008": "Projekt jest zarchiwizowany i dlatego można go tylko przeglądać. To samo dotyczy wszystkich zadań związanych z tym projektem.",
|
||||
"4001": "Tekst zadania projektu nie może być pusty.",
|
||||
"4002": "Projekt nie istnieje.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "Wszystkie zadania do edycji zbiorczej muszą należeć do tego samego projektu.",
|
||||
"4004": "Potrzebujesz co najmniej jednego zadania do edycji zbiorczej zadań.",
|
||||
"4005": "Nie masz uprawnień, aby zobaczyć to zadanie.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Por favor, insira uma senha.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Exibir senha",
|
||||
"hidePassword": "Ocultar senha",
|
||||
"noAccountYet": "Não possui uma conta ainda?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Dias",
|
||||
"permissionExplanation": "As permissões controlam as ações que um token de API pode realizar.",
|
||||
"titleRequired": "O título é obrigatório",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "Este token venceu {ago}.",
|
||||
"tokenCreatedSuccess": "Aqui está seu novo token de API: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Guarde em um local seguro, você não poderá visualizá-lo novamente!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Apagar este comentário",
|
||||
"deleteText1": "Tem certeza que deseja apagar este comentário?",
|
||||
"deleteSuccess": "O comentário foi apagado com sucesso.",
|
||||
"addedSuccess": "O comentário foi adicionado com sucesso."
|
||||
"addedSuccess": "O comentário foi adicionado com sucesso.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Data de vencimento antecipada",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "O compartilhamento de projeto não existe.",
|
||||
"3007": "Já existe um projeto com este identificador.",
|
||||
"3008": "O projeto está arquivado e, portanto, só pode ser acessado como somente leitura. Isso também se aplica a todas as tarefas associadas a este projeto.",
|
||||
"4001": "O texto da tarefa do projeto não pode estar em branco.",
|
||||
"4002": "A tarefa do projeto não existe.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "Todas as tarefas da edição em massa devem pertencer ao mesmo projeto.",
|
||||
"4004": "Precisa de pelo menos uma tarefa quando há edição em massa de tarefas.",
|
||||
"4005": "Você não tem o direito de ver a tarefa.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "O nome de utilizador não se deve assemelhar a um URL.",
|
||||
"passwordRequired": "Por favor, fornece uma palavra-passe.",
|
||||
"passwordNotMin": "A palavra-passe deve ter no mínimo 8 caracteres.",
|
||||
"passwordNotMax": "A palavra-passe deve ter no máximo 250 caracteres.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Mostrar a palavra-passe",
|
||||
"hidePassword": "Esconder a palavra-passe",
|
||||
"noAccountYet": "Ainda não tens uma conta?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Dias",
|
||||
"permissionExplanation": "As permissões permitem-te definir o âmbito para o qual o token de API pode ser utilizado.",
|
||||
"titleRequired": "O título é requerido",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "Este token expirou {ago}.",
|
||||
"tokenCreatedSuccess": "Aqui está o teu novo token de API: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Guarda-o num local seguro, não o vais poder visualizar novamente!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Eliminar este comentário",
|
||||
"deleteText1": "Tens a certeza que pretendes eliminar este comentário?",
|
||||
"deleteSuccess": "O comentário foi eliminado com sucesso.",
|
||||
"addedSuccess": "O comentário foi adicionada com sucesso."
|
||||
"addedSuccess": "O comentário foi adicionada com sucesso.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Adiar data de vencimento",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "O projeto partiilhado não existe.",
|
||||
"3007": "Já existe um projeto com este identificador.",
|
||||
"3008": "O projeto está arquivado, portanto, só pode ser acedido para leitura. Isto é também verdade para todas as tarefas associadas a este projeto.",
|
||||
"4001": "O texto da tarefa não pode estar vazio.",
|
||||
"4002": "A tarefa não existe.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "Todas as tarefas para edição em massa devem pertencer ao mesmo projeto.",
|
||||
"4004": "Precisas selecionar pelo menos uma tarefa para realizar uma edição em massa.",
|
||||
"4005": "Não possuis permissão para ver esta tarefa.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Please provide a password.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Show the password",
|
||||
"hidePassword": "Hide the password",
|
||||
"noAccountYet": "Don't have an account yet?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,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",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Need at least one task when bulk editing tasks.",
|
||||
"4005": "You do not have the right to see the task.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "Имя пользователя не должно быть похожим на URL.",
|
||||
"passwordRequired": "Введите пароль.",
|
||||
"passwordNotMin": "Пароль должен содержать не меньше 8 символов.",
|
||||
"passwordNotMax": "Пароль должен содержать не больше 250 символов.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Показать пароль",
|
||||
"hidePassword": "Скрыть пароль",
|
||||
"noAccountYet": "Ещё нет аккаунта?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 дней",
|
||||
"permissionExplanation": "Разрешения позволяют выбрать, какие действия можно выполнять с использованием этого токена.",
|
||||
"titleRequired": "Название обязательно",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "Срок действия этого токена истёк {ago}.",
|
||||
"tokenCreatedSuccess": "Ваш новый токен: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Сохраните его в безопасном месте, вы не увидите его снова!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Удалить комментарий",
|
||||
"deleteText1": "Удалить этот комментарий?",
|
||||
"deleteSuccess": "Комментарий удалён.",
|
||||
"addedSuccess": "Комментарий добавлен."
|
||||
"addedSuccess": "Комментарий добавлен.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Отложить срок",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "Проект с таким идентификатором уже существует.",
|
||||
"3008": "Этот проект архивирован и поэтому доступен только для чтения. Это также касается всех задач в этом проекте.",
|
||||
"4001": "Текст задачи не может быть пустым.",
|
||||
"4002": "Задача не существует.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "Все задачи для массового редактирования должны принадлежать одному проекту.",
|
||||
"4004": "Необходима хотя бы одна задача для массового редактирования.",
|
||||
"4005": "У вас нет прав для просмотра задачи.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Please provide a password.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Show the password",
|
||||
"hidePassword": "Hide the password",
|
||||
"noAccountYet": "Don't have an account yet?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,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",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Need at least one task when bulk editing tasks.",
|
||||
"4005": "You do not have the right to see the task.",
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
"forgotPassword": "Ste pozabili svoje geslo?",
|
||||
"resetPassword": "Ponastavite geslo",
|
||||
"resetPasswordAction": "Pošljite mi povezavo za ponastavitev gesla",
|
||||
"resetPasswordSuccess": "Check your inbox! You should have an email with instructions on how to reset your password.",
|
||||
"resetPasswordSuccess": "Preverite vaš poštni predal! Prejeti bi morali e-pošto z navodili za ponastavitev gesla.",
|
||||
"passwordsDontMatch": "Vneseni gesli se ne ujemata",
|
||||
"confirmEmailSuccess": "Uspešno ste potrdili svoj e-poštni naslov! Sedaj se lahko prijavite.",
|
||||
"totpTitle": "Koda za dvofaktorsko avtentikacijo",
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "Uporabniško ime ne sme izgledati kot URL.",
|
||||
"passwordRequired": "Prosim vnesite geslo.",
|
||||
"passwordNotMin": "Geslo mora imeti vsaj 8 znakov.",
|
||||
"passwordNotMax": "Geslo mora imeti največ 250 znakov.",
|
||||
"passwordNotMax": "Geslo mora imeti največ 72 znakov.",
|
||||
"showPassword": "Prikažite geslo",
|
||||
"hidePassword": "Skrijte geslo",
|
||||
"noAccountYet": "Še nimate računa?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 dni",
|
||||
"permissionExplanation": "Dovoljenja vam omogočajo, da določite, kaj vse vam API žeton dovoli.",
|
||||
"titleRequired": "Naslov je obvezen",
|
||||
"permissionRequired": "Prosim izberite vsaj eno dovoljenje iz seznama.",
|
||||
"expired": "Žeton je potekel pred {ago}.",
|
||||
"tokenCreatedSuccess": "Tu je vaš novi API žeton: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Shranite ga na varno mesto, ker ga ne boste več videli!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Izbriši ta komentar",
|
||||
"deleteText1": "Ali ste prepričani, da želite izbrisati ta komentar?",
|
||||
"deleteSuccess": "Komentar je bil uspešno izbrisan.",
|
||||
"addedSuccess": "Komentar je bil uspešno dodan."
|
||||
"addedSuccess": "Komentar je bil uspešno dodan.",
|
||||
"permalink": "Kopiraj trajno povezavo do tega komentarja"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Odloži datum zapadlosti",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "Skupna raba projekta ne obstaja.",
|
||||
"3007": "Projekt s tem identifikatorjem že obstaja.",
|
||||
"3008": "Projekt je arhiviran in je zato možen samo za branje. To velja tudi za vse naloge, povezane s tem projektom.",
|
||||
"4001": "Besedilo projektne naloge ne sme biti prazno.",
|
||||
"4002": "Projektna naloga ne obstaja.",
|
||||
"4001": "Naslov naloge ne sme biti prazen.",
|
||||
"4002": "Naloga ne obstaja.",
|
||||
"4003": "Vse naloge množičnega urejanja morajo pripadati istemu projektu.",
|
||||
"4004": "Pri množičnem urejanju nalog potrebujete vsaj eno nalogo.",
|
||||
"4005": "Nimate pravic do vpogleda v nalogo.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Please provide a password.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Show the password",
|
||||
"hidePassword": "Hide the password",
|
||||
"noAccountYet": "Don't have an account yet?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,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",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Need at least one task when bulk editing tasks.",
|
||||
"4005": "You do not have the right to see the task.",
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@
|
|||
"usernameMustNotContainSpace": "The username must not contain spaces.",
|
||||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Vänligen ange ett lösenord.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMin": "Lösenord måste innehålla minst 8 tecken.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Show the password",
|
||||
"hidePassword": "Hide the password",
|
||||
"noAccountYet": "Har du inget konto än?",
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
"newPasswordTitle": "Uppdatera ditt lösenord",
|
||||
"newPassword": "Nytt lösenord",
|
||||
"newPasswordConfirm": "New password confirmation",
|
||||
"currentPassword": "Current password",
|
||||
"currentPassword": "Nuvarande lösenord",
|
||||
"currentPasswordPlaceholder": "Ditt nuvarande lösenord",
|
||||
"passwordsDontMatch": "The new password and its confirmation don't match.",
|
||||
"passwordUpdateSuccess": "The password was successfully updated.",
|
||||
|
|
@ -83,7 +83,7 @@
|
|||
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
|
||||
"general": {
|
||||
"title": "General Settings",
|
||||
"name": "My Name",
|
||||
"name": "Mitt namn",
|
||||
"newName": "The new name",
|
||||
"savedSuccess": "The settings were successfully updated.",
|
||||
"emailReminders": "Send me reminders for tasks via email",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 dagar",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Förvara den på en säker plats, du kommer aldrig att se den igen!",
|
||||
|
|
@ -324,7 +325,7 @@
|
|||
"add": "Lägg till",
|
||||
"addPlaceholder": "Add a task…",
|
||||
"empty": "This project is currently empty.",
|
||||
"newTaskCta": "Create a task.",
|
||||
"newTaskCta": "Skapa en uppgift.",
|
||||
"editTask": "Redigera uppgift"
|
||||
},
|
||||
"gantt": {
|
||||
|
|
@ -395,7 +396,7 @@
|
|||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"create": "Skapa vy",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
|
|
@ -574,7 +575,7 @@
|
|||
"id": "ID",
|
||||
"created": "Created at",
|
||||
"createdBy": "Created by {0}",
|
||||
"actions": "Actions",
|
||||
"actions": "Åtgärder",
|
||||
"cannotBeUndone": "This cannot be undone!"
|
||||
},
|
||||
"input": {
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Delete this comment",
|
||||
"deleteText1": "Är du säker på att du vill radera denna kommentar?",
|
||||
"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",
|
||||
|
|
@ -1109,7 +1111,7 @@
|
|||
"createTask": "Create a task in the current project ({title})",
|
||||
"createProject": "Skapa ett projekt",
|
||||
"cmds": {
|
||||
"newTask": "New task",
|
||||
"newTask": "Ny uppgift",
|
||||
"newProject": "Nytt projekt",
|
||||
"newTeam": "Nytt team"
|
||||
}
|
||||
|
|
@ -1120,10 +1122,10 @@
|
|||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWith": "{user} reagerade med {value}",
|
||||
"reactedWithAnd": "{users} och {lastUser} reagerade med {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
"add": "Lägg till din reaktion"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Need at least one task when bulk editing tasks.",
|
||||
"4005": "You do not have the right to see the task.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Please provide a password.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Show the password",
|
||||
"hidePassword": "Hide the password",
|
||||
"noAccountYet": "Don't have an account yet?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,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",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Need at least one task when bulk editing tasks.",
|
||||
"4005": "You do not have the right to see the task.",
|
||||
|
|
|
|||
|
|
@ -56,12 +56,12 @@
|
|||
"openIdGeneralError": "Сталася помилка під час автентифікації проти третьої сторони.",
|
||||
"logout": "Вийти",
|
||||
"emailInvalid": "Будь ласка, введіть дійсну е-скриньку.",
|
||||
"usernameRequired": "Будь ласка, введіть ім'я вживача.",
|
||||
"usernameRequired": "Будь ласка, введіть ім'я або е-скриньку.",
|
||||
"usernameMustNotContainSpace": "Ім'я вживача не може містити пропусків.",
|
||||
"usernameMustNotLookLikeUrl": "Ім'я вживача не має виглядати як посилання.",
|
||||
"passwordRequired": "Будь ласка, введіть пароль.",
|
||||
"passwordNotMin": "Пароль має містити принаймні 8 знаків.",
|
||||
"passwordNotMax": "Пароль має містити не більше 250 знаків.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Показати пароль",
|
||||
"hidePassword": "Сховати пароль",
|
||||
"noAccountYet": "Досі немає обліковки?",
|
||||
|
|
@ -99,7 +99,7 @@
|
|||
"defaultView": "Основне подання",
|
||||
"timezone": "Часовий пояс",
|
||||
"overdueTasksRemindersTime": "Нагадувати про завдання на е-скриньку",
|
||||
"filterUsedOnOverview": "Постійна вибірка, яка вживатиметься на сторінці огляду"
|
||||
"filterUsedOnOverview": "Постійна вибірка для сторінки огляду"
|
||||
},
|
||||
"totp": {
|
||||
"title": "Дворівнева перевірка",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 днів",
|
||||
"permissionExplanation": "Дозволи визначають, що може робити api ключ.",
|
||||
"titleRequired": "Слід вказати заголовок",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "Цей ключ закінчився {ago}.",
|
||||
"tokenCreatedSuccess": "Ось Ваш новий api ключ: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Збережіть його в надійному місці, бо він зникне і не з'явиться знову!",
|
||||
|
|
@ -247,11 +248,11 @@
|
|||
"title": "Вилучити \"{project}\"",
|
||||
"header": "Вилучається справа",
|
||||
"text1": "Справді впровадити та втратити увесь вміст?",
|
||||
"text2": "Це містить всі завдання і ГОДІ СПИНИТИ!",
|
||||
"text2": "Це містить всі завдання і ПОДАЛЬША ДІЯ ОСТАТОЧНА!",
|
||||
"success": "Справу вилучено.",
|
||||
"tasksToDelete": "Близько {count} завдань пропадуть остаточно.",
|
||||
"tasksAndChildProjectsToDelete": "Близько {tasks} завдань та {projects} справ пропадуть остаточно.",
|
||||
"noTasksToDelete": "Справа не містить завдань та її безпечно вилучити."
|
||||
"noTasksToDelete": "Справа не містить завдань та її можна безпечно вилучити."
|
||||
},
|
||||
"duplicate": {
|
||||
"title": "Подвоїти цю справу",
|
||||
|
|
@ -287,7 +288,7 @@
|
|||
"passwordExplanation": "При вході, вживачеві слід ввести цей пароль.",
|
||||
"noName": "Не вказано ім'я",
|
||||
"remove": "Вилучається посилання для поширення",
|
||||
"removeText": "Дійсно вилучити посилання для поширення? За ним надалі годі одержати доступ. Годі спинити!",
|
||||
"removeText": "Дійсно вилучити посилання для поширення? За ним надалі годі одержати доступ. Подальша дія остаточна!",
|
||||
"createSuccess": "Поширення посилання створено.",
|
||||
"deleteSuccess": "Запрошення через посилання видалено",
|
||||
"view": "Подання",
|
||||
|
|
@ -399,7 +400,7 @@
|
|||
"createSuccess": "Подання створено.",
|
||||
"titleRequired": "Будь ласка, введіть заголовок.",
|
||||
"delete": "Вилучається подання",
|
||||
"deleteText": "Справді впровадити? Продовження ніяк не вплине на завдання, але унеможливить вживання у цій справі. Годі спинити!",
|
||||
"deleteText": "Справді впровадити? Продовження ніяк не вплине на завдання, але унеможливить вживання у цій справі. Подальша дія остаточна!",
|
||||
"deleteSuccess": "Подання вилучено.",
|
||||
"onlyAdminsCanEdit": "Лише очільники справи можуть змінювати подання.",
|
||||
"updateSuccess": "Подання оновлено."
|
||||
|
|
@ -429,7 +430,7 @@
|
|||
"title": "Постійна вибірка",
|
||||
"description": "Постійна вибірка - це набір встановлених вказівок, який обчислюється щоразу як його запускають.",
|
||||
"action": "Створити",
|
||||
"titleRequired": "Будь ласка, введіть заголовок вибірки."
|
||||
"titleRequired": "Будь ласка, введіть заголовок."
|
||||
},
|
||||
"delete": {
|
||||
"header": "Вилучається постійна вибірка",
|
||||
|
|
@ -575,7 +576,7 @@
|
|||
"created": "Створено",
|
||||
"createdBy": "Автор: {0}",
|
||||
"actions": "Дії",
|
||||
"cannotBeUndone": "Годі спинити!"
|
||||
"cannotBeUndone": "Подальша дія остаточна!"
|
||||
},
|
||||
"input": {
|
||||
"resetColor": "Скинути",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Вилучається приписка",
|
||||
"deleteText1": "Справді впровадити?",
|
||||
"deleteSuccess": "Приписку вилучено.",
|
||||
"addedSuccess": "Приписку додано."
|
||||
"addedSuccess": "Приписку додано.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Перенести строк",
|
||||
|
|
@ -986,7 +988,7 @@
|
|||
"delete": {
|
||||
"header": "Вилучається спільнота",
|
||||
"text1": "Справді впровадити та втратити всі згадки?",
|
||||
"text2": "Учасники втратять доступи до спільних справ. ГОДІ СПИНИТИ!",
|
||||
"text2": "Учасники втратять доступи до спільних справ. ПОДАЛЬША ДІЯ ОСТАТОЧНА!",
|
||||
"success": "Спільноту вилучено."
|
||||
},
|
||||
"deleteUser": {
|
||||
|
|
@ -1098,7 +1100,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Вказівки",
|
||||
"placeholder": "Введіть щось або вказівку щоб знайти…",
|
||||
"hint": "Вжива́йте {project} щоб знайти тільки у справі. Поєднуйте {project} і {label} (позначки) з запитом знаходження, щоб знайти завдання з цими позначками або в цій справі. Вживайте {assignee}, щоб знаходити тільки вказівки.",
|
||||
"hint": "Вживайте {project} щоб знайти тільки у справі. Поєднуйте {project} і {label} (позначки) з запитом знаходження, щоб знайти завдання з цими позначками або в цій справі. Вживайте {assignee}, щоб знаходити тільки вказівки.",
|
||||
"tasks": "Завдання",
|
||||
"projects": "Справи",
|
||||
"teams": "Спільноти",
|
||||
|
|
@ -1141,9 +1143,9 @@
|
|||
"1012": "Е-скринька вживача не підтверджена.",
|
||||
"1013": "Новий пароль не заповнений.",
|
||||
"1014": "Старий пароль не заповнений.",
|
||||
"1015": "TOTP is already enabled for this user.",
|
||||
"1016": "TOTP is not enabled for this user.",
|
||||
"1017": "The TOTP passcode is invalid.",
|
||||
"1015": "Разовий пароль вже увімкнено.",
|
||||
"1016": "Разовий пароль не увімкнено.",
|
||||
"1017": "Разовий пароль хибний.",
|
||||
"1018": "Вид облікової світлини вживача - хибний.",
|
||||
"2001": "ID не може бути порожнім або 0.",
|
||||
"2002": "Деякі відомості запиту були хибні.",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "Такого поширення справи немає.",
|
||||
"3007": "Справа з таким ідентифікатором вже є.",
|
||||
"3008": "Справа у сховищі, тому доступна тільки для читання. Це діє для усіх завдань пов'язаних з цією справою.",
|
||||
"4001": "Опис завдання не може бути порожнім.",
|
||||
"4002": "Такого завдання у справі немає.",
|
||||
"4001": "Назва не може бути порожньою.",
|
||||
"4002": "Такого завдання немає.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Має бути хоча б одне завдання при зміні завдань.",
|
||||
"4005": "У Вас немає прав дивитися завдання.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Vui lòng cung cấp một mật khẩu.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Hiển thị mật khẩu",
|
||||
"hidePassword": "Ẩn mật khẩu",
|
||||
"noAccountYet": "Bạn chưa có tài khoản?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "Xóa bình luận này",
|
||||
"deleteText1": "Bạn có chắc muốn xóa bình luận này không?",
|
||||
"deleteSuccess": "The comment was deleted successfully.",
|
||||
"addedSuccess": "Bình luận đã được thêm vào."
|
||||
"addedSuccess": "Bình luận đã được thêm vào.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Trì hoãn ngày đến hạn",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Cần ít nhất một công việc khi chỉnh sửa hàng loạt.",
|
||||
"4005": "Bạn không có quyền xem công việc.",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "用户名不能像一个 URL。",
|
||||
"passwordRequired": "请提供密码",
|
||||
"passwordNotMin": "密码至少有8个字符",
|
||||
"passwordNotMax": "密码不能超过250个字符",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "显示密码",
|
||||
"hidePassword": "隐藏密码",
|
||||
"noAccountYet": "还没有账号?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 天",
|
||||
"permissionExplanation": "权限允许您限制 api 令牌被允许做什么。",
|
||||
"titleRequired": "需要指定标题",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "Token {ago} 前到期",
|
||||
"tokenCreatedSuccess": "这是您的令牌: {token}",
|
||||
"tokenCreatedNotSeeAgain": "将其存储在一个安全的位置,你不会再看到它了!",
|
||||
|
|
@ -847,7 +848,8 @@
|
|||
"delete": "删除此评论",
|
||||
"deleteText1": "确实要删除此评论吗?",
|
||||
"deleteSuccess": "评论已删除。",
|
||||
"addedSuccess": "评论已添加。"
|
||||
"addedSuccess": "评论已添加。",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "推迟截止时间",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "项目共享不存在。",
|
||||
"3007": "具有此标识符的项目已存在。",
|
||||
"3008": "该项目已存档,因此只能读取。与该项目相关的所有任务也是如此。",
|
||||
"4001": "项目任务文本不能为空。",
|
||||
"4002": "项目任务不存在。",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "所有批量编辑任务必须属于同一项目。",
|
||||
"4004": "批量编辑任务时至少需要选择一项任务。",
|
||||
"4005": "你没有权限查看此任务。",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"passwordRequired": "Please provide a password.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMax": "Password must have at most 72 characters.",
|
||||
"showPassword": "Show the password",
|
||||
"hidePassword": "Hide the password",
|
||||
"noAccountYet": "Don't have an account yet?",
|
||||
|
|
@ -163,6 +163,7 @@
|
|||
"90d": "90 Days",
|
||||
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||
"titleRequired": "The title is required",
|
||||
"permissionRequired": "Please select at least one permission from the list.",
|
||||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
|
|
@ -847,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",
|
||||
|
|
@ -1153,8 +1155,8 @@
|
|||
"3006": "The project share does not exist.",
|
||||
"3007": "A project with this identifier already exists.",
|
||||
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
|
||||
"4001": "The project task text cannot be empty.",
|
||||
"4002": "The project task does not exist.",
|
||||
"4001": "The task title cannot be empty.",
|
||||
"4002": "The task does not exist.",
|
||||
"4003": "All bulk editing tasks must belong to the same project.",
|
||||
"4004": "Need at least one task when bulk editing tasks.",
|
||||
"4005": "You do not have the right to see the task.",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import type {IAbstract} from './IAbstract'
|
||||
import type {IUser} from './IUser'
|
||||
|
||||
// FIXME: what makes this different from TaskFilterParams?
|
||||
interface Filters {
|
||||
sortBy: ('start_date' | 'done' | 'id' | 'position')[],
|
||||
orderBy: ('asc' | 'desc')[],
|
||||
sort_by: ('start_date' | 'done' | 'id' | 'position')[],
|
||||
order_by: ('asc' | 'desc')[],
|
||||
filter: string,
|
||||
filterIncludeNulls: boolean,
|
||||
filter_include_nulls: boolean,
|
||||
s: string,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { objectToSnakeCase } from '@/helpers/case'
|
||||
import AbstractModel from './abstractModel'
|
||||
import UserModel from '@/models/user'
|
||||
|
||||
|
|
@ -9,10 +10,10 @@ export default class SavedFilterModel extends AbstractModel<ISavedFilter> implem
|
|||
title = ''
|
||||
description = ''
|
||||
filters: ISavedFilter['filters'] = {
|
||||
sortBy: ['done', 'id'],
|
||||
orderBy: ['asc', 'desc'],
|
||||
sort_by: ['done', 'id'],
|
||||
order_by: ['asc', 'desc'],
|
||||
filter: 'done = false',
|
||||
filterIncludeNulls: true,
|
||||
filter_include_nulls: true,
|
||||
s: '',
|
||||
}
|
||||
|
||||
|
|
@ -26,6 +27,10 @@ export default class SavedFilterModel extends AbstractModel<ISavedFilter> implem
|
|||
|
||||
this.owner = new UserModel(this.owner)
|
||||
|
||||
// Filters are in snake_case for the API - this makes it consistent with the way filter params are used with one-off filters.
|
||||
// Should probably be camelCase everywhere, but that's a task for another day.
|
||||
this.filters = objectToSnakeCase(this.filters)
|
||||
|
||||
this.created = new Date(this.created)
|
||||
this.updated = new Date(this.updated)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import SavedFilterModel from '@/models/savedFilter'
|
|||
import {useBaseStore} from '@/stores/base'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
||||
import {objectToSnakeCase, objectToCamelCase} from '@/helpers/case'
|
||||
import {success} from '@/message'
|
||||
import ProjectModel from '@/models/project'
|
||||
|
||||
|
|
@ -55,23 +54,6 @@ export default class SavedFilterService extends AbstractService<ISavedFilter> {
|
|||
modelFactory(data) {
|
||||
return new SavedFilterModel(data)
|
||||
}
|
||||
|
||||
processModel(model) {
|
||||
// Make filters from this.filters camelCase and set them to the model property:
|
||||
// That's easier than making the whole filter component configurable since that still needs to provide
|
||||
// the filter values in snake_sćase for url parameters.
|
||||
model.filters = objectToCamelCase(model.filters)
|
||||
|
||||
return model
|
||||
}
|
||||
|
||||
beforeUpdate(model) {
|
||||
return this.processModel(model)
|
||||
}
|
||||
|
||||
beforeCreate(model) {
|
||||
return this.processModel(model)
|
||||
}
|
||||
}
|
||||
|
||||
export function useSavedFilter(projectId?: MaybeRefOrGetter<IProject['id']>) {
|
||||
|
|
@ -98,10 +80,7 @@ export function useSavedFilter(projectId?: MaybeRefOrGetter<IProject['id']>) {
|
|||
// We assume the projectId in the route is the pseudoproject
|
||||
const savedFilterId = getSavedFilterIdFromProjectId(watchedProjectId)
|
||||
|
||||
filter.value = new SavedFilterModel({id: savedFilterId})
|
||||
const response = await filterService.get(filter.value)
|
||||
response.filters = objectToSnakeCase(response.filters)
|
||||
filter.value = response
|
||||
filter.value = await filterService.get(new SavedFilterModel({id: savedFilterId}))
|
||||
await validateTitleField()
|
||||
}, {immediate: true})
|
||||
|
||||
|
|
@ -115,7 +94,6 @@ export function useSavedFilter(projectId?: MaybeRefOrGetter<IProject['id']>) {
|
|||
const response = await filterService.update(filter.value)
|
||||
await projectStore.loadAllProjects()
|
||||
success({message: t('filters.edit.success')})
|
||||
response.filters = objectToSnakeCase(response.filters)
|
||||
filter.value = response
|
||||
await useBaseStore().setCurrentProject(new ProjectModel({
|
||||
id: getProjectId(filter.value),
|
||||
|
|
|
|||
|
|
@ -31,9 +31,17 @@ export default class TaskCollectionService extends AbstractService<ITask> {
|
|||
constructor() {
|
||||
super({
|
||||
getAll: '/projects/{projectId}/views/{viewId}/tasks',
|
||||
// /projects/{projectId}/tasks when viewId is not provided
|
||||
})
|
||||
}
|
||||
|
||||
getReplacedRoute(path: string, pathparams: Record<string, unknown>): string {
|
||||
if (!pathparams.viewId) {
|
||||
return super.getReplacedRoute('/projects/{projectId}/tasks', pathparams)
|
||||
}
|
||||
return super.getReplacedRoute(path, pathparams)
|
||||
}
|
||||
|
||||
modelFactory(data) {
|
||||
// FIXME: There must be a better way for this…
|
||||
if (typeof data.project_view_id !== 'undefined') {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {computed, ref} from 'vue'
|
||||
import {computed, readonly, ref} from 'vue'
|
||||
import {acceptHMRUpdate, defineStore} from 'pinia'
|
||||
|
||||
import LabelService from '@/services/label'
|
||||
|
|
@ -21,30 +21,35 @@ async function getAllLabels(page = 1): Promise<ILabel[]> {
|
|||
}
|
||||
}
|
||||
|
||||
export interface LabelState {
|
||||
[id: ILabel['id']]: ILabel
|
||||
}
|
||||
|
||||
export const useLabelStore = defineStore('label', () => {
|
||||
// The labels are stored as an object which has the label ids as keys.
|
||||
const labels = ref<LabelState>({})
|
||||
const isLoading = ref(false)
|
||||
const labels = ref<{ [id: ILabel['id']]: ILabel }>({})
|
||||
|
||||
const getLabelsByIds = computed(() => {
|
||||
return (ids: ILabel['id'][]) => Object.values(labels.value).filter(({id}) => ids.includes(id))
|
||||
})
|
||||
// Alphabetically sort the labels
|
||||
const labelsArray = computed(() => Object.values(labels.value)
|
||||
.sort((a, b) => a.title.localeCompare(
|
||||
b.title, i18n.global.locale.value,
|
||||
{ ignorePunctuation: true },
|
||||
)),
|
||||
)
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
||||
const getLabelById = computed(() => {
|
||||
return (labelId: ILabel['id']) => Object.values(labels.value).find(({id}) => id === labelId)
|
||||
return (labelId: ILabel['id']) => labels.value[labelId]
|
||||
})
|
||||
|
||||
const getLabelsByIds = computed(() => (ids: ILabel['id'][]) =>
|
||||
ids.map(id => labels.value[id]).filter(Boolean),
|
||||
)
|
||||
|
||||
|
||||
// **
|
||||
// * Checks if a project of labels is available in the store and filters them then query
|
||||
// * Checks if a list of labels is available in the store and filters them then query
|
||||
// **
|
||||
const filterLabelsByQuery = computed(() => {
|
||||
return (labelsToHide: ILabel[], query: string) => {
|
||||
const labelIdsToHide: number[] = labelsToHide.map(({id}) => id)
|
||||
|
||||
|
||||
return search(query)
|
||||
?.filter(value => !labelIdsToHide.includes(value))
|
||||
.map(id => labels.value[id])
|
||||
|
|
@ -53,14 +58,12 @@ export const useLabelStore = defineStore('label', () => {
|
|||
})
|
||||
|
||||
const getLabelsByExactTitles = computed(() => {
|
||||
return (labelTitles: string[]) => Object
|
||||
.values(labels.value)
|
||||
return (labelTitles: string[]) => labelsArray.value
|
||||
.filter(({title}) => labelTitles.some(l => l.toLowerCase() === title.toLowerCase()))
|
||||
})
|
||||
|
||||
const getLabelByExactTitle = computed(() => {
|
||||
return (labelTitle: string) => Object
|
||||
.values(labels.value)
|
||||
return (labelTitle: string) => labelsArray.value
|
||||
.find(l => l.title.toLowerCase() === labelTitle.toLowerCase())
|
||||
})
|
||||
|
||||
|
|
@ -144,11 +147,12 @@ export const useLabelStore = defineStore('label', () => {
|
|||
}
|
||||
|
||||
return {
|
||||
labels,
|
||||
labels: readonly(labels),
|
||||
labelsArray: readonly(labelsArray),
|
||||
isLoading,
|
||||
|
||||
getLabelsByIds,
|
||||
getLabelById,
|
||||
getLabelsByIds,
|
||||
filterLabelsByQuery,
|
||||
getLabelsByExactTitles,
|
||||
getLabelByExactTitle,
|
||||
|
|
|
|||
|
|
@ -20,10 +20,6 @@ import type {IProjectView} from '@/modelTypes/IProjectView'
|
|||
|
||||
const {add, remove, search, update} = createNewIndexer('projects', ['title', 'description'])
|
||||
|
||||
export interface ProjectState {
|
||||
[id: IProject['id']]: IProject
|
||||
}
|
||||
|
||||
export const useProjectStore = defineStore('project', () => {
|
||||
const baseStore = useBaseStore()
|
||||
const router = useRouter()
|
||||
|
|
@ -31,9 +27,10 @@ export const useProjectStore = defineStore('project', () => {
|
|||
const isLoading = ref(false)
|
||||
|
||||
// The projects are stored as an object which has the project ids as keys.
|
||||
const projects = ref<ProjectState>({})
|
||||
const projects = ref<{ [id: IProject['id']]: IProject }>({})
|
||||
const projectsArray = computed(() => Object.values(projects.value)
|
||||
.sort((a, b) => a.position - b.position))
|
||||
|
||||
const notArchivedRootProjects = computed(() => projectsArray.value
|
||||
.filter(p => p.parentProjectId === 0 && !p.isArchived && p.id > 0))
|
||||
const favoriteProjects = computed(() => projectsArray.value
|
||||
|
|
@ -48,7 +45,7 @@ export const useProjectStore = defineStore('project', () => {
|
|||
|
||||
const findProjectByExactname = computed(() => {
|
||||
return (name: string) => {
|
||||
const project = Object.values(projects.value).find(l => {
|
||||
const project = projectsArray.value.find(l => {
|
||||
return l.title.toLowerCase() === name.toLowerCase()
|
||||
})
|
||||
return typeof project === 'undefined' ? null : project
|
||||
|
|
@ -57,7 +54,7 @@ export const useProjectStore = defineStore('project', () => {
|
|||
|
||||
const findProjectByIdentifier = computed(() => {
|
||||
return (identifier: string) => {
|
||||
const project = Object.values(projects.value).find(p => {
|
||||
const project = projectsArray.value.find(p => {
|
||||
return p.identifier.toLowerCase() === identifier.toLowerCase()
|
||||
})
|
||||
return typeof project === 'undefined' ? null : project
|
||||
|
|
@ -69,7 +66,7 @@ export const useProjectStore = defineStore('project', () => {
|
|||
return search(query)
|
||||
?.filter(value => value > 0)
|
||||
.map(id => projects.value[id])
|
||||
.filter(project => project.isArchived === includeArchived)
|
||||
.filter(project => project?.isArchived === includeArchived)
|
||||
|| []
|
||||
}
|
||||
})
|
||||
|
|
@ -79,7 +76,7 @@ export const useProjectStore = defineStore('project', () => {
|
|||
return search(query)
|
||||
?.filter(value => getSavedFilterIdFromProjectId(value) > 0)
|
||||
.map(id => projects.value[id])
|
||||
.filter(project => project.isArchived === includeArchived)
|
||||
.filter(project => project?.isArchived === includeArchived)
|
||||
|| []
|
||||
}
|
||||
})
|
||||
|
|
@ -221,25 +218,30 @@ export const useProjectStore = defineStore('project', () => {
|
|||
}
|
||||
|
||||
function setProjectView(view: IProjectView) {
|
||||
const viewPos = projects.value[view.projectId].views.findIndex(v => v.id === view.id)
|
||||
const views = [...projects.value[view.projectId].views]
|
||||
const viewPos = views.findIndex(v => v.id === view.id)
|
||||
|
||||
if (viewPos !== -1) {
|
||||
projects.value[view.projectId].views[viewPos] = view
|
||||
projects.value[view.projectId].views.sort((a, b) => a.position < b.position ? -1 : 1)
|
||||
setProject(projects.value[view.projectId])
|
||||
return
|
||||
views[viewPos] = view
|
||||
} else {
|
||||
views.push(view)
|
||||
}
|
||||
views.sort((a, b) => a.position < b.position ? -1 : 1)
|
||||
|
||||
projects.value[view.projectId].views.push(view)
|
||||
projects.value[view.projectId].views.sort((a, b) => a.position < b.position ? -1 : 1)
|
||||
|
||||
setProject(projects.value[view.projectId])
|
||||
setProject({
|
||||
...projects.value[view.projectId],
|
||||
views,
|
||||
})
|
||||
}
|
||||
|
||||
function removeProjectView(projectId: IProject['id'], viewId: IProjectView['id']) {
|
||||
const viewPos = projects.value[projectId].views.findIndex(v => v.id === viewId)
|
||||
if (viewPos !== -1) {
|
||||
projects.value[projectId].views.splice(viewPos, 1)
|
||||
}
|
||||
const project = projects.value[projectId]
|
||||
const updatedViews = project.views.filter(v => v.id !== viewId)
|
||||
|
||||
setProject({
|
||||
...project,
|
||||
views: updatedViews,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue