Compare commits
213
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a974bf09b5 | ||
|
|
9e39ef9397 | ||
|
|
c54181eeda | ||
|
|
b3bf92b7ce | ||
|
|
05dd05216d | ||
|
|
cd0ab54d57 | ||
|
|
a7bd9dad24 | ||
|
|
10e245f6d0 | ||
|
|
010dd1ea22 | ||
|
|
65a2814b2a | ||
|
|
4dd9d5de67 | ||
|
|
ec902b051b | ||
|
|
4b2b8e3b83 | ||
|
|
bc52da4029 | ||
|
|
2123da49a3 | ||
|
|
c53a761396 | ||
|
|
c84b50b3ee | ||
|
|
c5b82fc591 | ||
|
|
1f00180af6 | ||
|
|
33e851f543 | ||
|
|
16d7386975 | ||
|
|
6d9bda8cc1 | ||
|
|
abf92e29fa | ||
|
|
f4d628550f | ||
|
|
1d352fcbf3 | ||
|
|
a1105434bf | ||
|
|
acc7c9f8f5 | ||
|
|
cc64ca6406 | ||
|
|
89f78cd369 | ||
|
|
14c3b13823 | ||
|
|
c2a7438814 | ||
|
|
8fe37fd900 | ||
|
|
88b4ab1768 | ||
|
|
c252c8f0cd | ||
|
|
410ff28b71 | ||
|
|
d98dd7ecbb | ||
|
|
fc73085876 | ||
|
|
c71f7334b4 | ||
|
|
6a5342bd49 | ||
|
|
b4ea1bb86a | ||
|
|
378d0ae9bb | ||
|
|
6f27e1401a | ||
|
|
bf5cafc03f | ||
|
|
f4a7326b68 | ||
|
|
1451f6e46f | ||
|
|
427eb2a618 | ||
|
|
e4b541e653 | ||
|
|
45ff5907e6 | ||
|
|
5a7c3927f3 | ||
|
|
f425d98b4d | ||
|
|
4c55016c1a | ||
|
|
1a8f12ac13 | ||
|
|
7b873ec31c | ||
|
|
8b77832af2 | ||
|
|
14710d0bba | ||
|
|
945f25b818 | ||
|
|
f53b93d98a | ||
|
|
97e030a1fc | ||
|
|
b708db7025 | ||
|
|
6bfe71b30e | ||
|
|
db196bdffe | ||
|
|
9428acd5e1 | ||
|
|
58a60fb4ae | ||
|
|
f4ca22a6ad | ||
|
|
7e38a9f442 | ||
|
|
a0a77818b1 | ||
|
|
bdccd633fe | ||
|
|
1f3eb8f2a3 | ||
|
|
8891ef347e | ||
|
|
3b8ccbad4f | ||
|
|
cc1b4bbd1b | ||
|
|
9dd95101f9 | ||
|
|
20724f6fb5 | ||
|
|
e1ea84fcd4 | ||
|
|
8d730543c3 | ||
|
|
a3d133bfee | ||
|
|
7b37ad0594 | ||
|
|
4aeb228c3a | ||
|
|
b52d6dbf59 | ||
|
|
76f7797e56 | ||
|
|
55dd7d2981 | ||
|
|
44a43b9f86 | ||
|
|
eb95caf757 | ||
|
|
1085a6583b | ||
|
|
e698ac5a34 | ||
|
|
020fea686d | ||
|
|
addf8523f4 | ||
|
|
044aa1c742 | ||
|
|
552e1ce796 | ||
|
|
c1246d73d4 | ||
|
|
85fa397a46 | ||
|
|
af47e43c34 | ||
|
|
2539f15dfb | ||
|
|
5c1b2846a1 | ||
|
|
af8100d262 | ||
|
|
63f85c97ae | ||
|
|
2b1e3ffd2a | ||
|
|
5f470afd7c | ||
|
|
37c6f94ba2 | ||
|
|
b42cd5786e | ||
|
|
5072ab3d72 | ||
|
|
4b5ec018f5 | ||
|
|
efe5c7b3cb | ||
|
|
ba0c32aacf | ||
|
|
b6e142e8d0 | ||
|
|
750d565477 | ||
|
|
2b8515b91d | ||
|
|
68636f27da | ||
|
|
cf94cc8cab | ||
|
|
669b7452cd | ||
|
|
1601c9098d | ||
|
|
f132b0e718 | ||
|
|
5d12986e3f | ||
|
|
74e0197dc6 | ||
|
|
2dd133fb4c | ||
|
|
02c1de55c4 | ||
|
|
cbc63c853d | ||
|
|
b5c4a3a80c | ||
|
|
a8ea5e11ab | ||
|
|
812430b9cf | ||
|
|
9d7b6d3d9a | ||
|
|
c2b116de70 | ||
|
|
22e594e253 | ||
|
|
0d9c03e0f2 | ||
|
|
72bb6609a1 | ||
|
|
b259072e6a | ||
|
|
ffcc48ec87 | ||
|
|
5aa0b6a0cf | ||
|
|
b60efbd259 | ||
|
|
34ac29fcce | ||
|
|
75f3e930cd | ||
|
|
4ff8815fe1 | ||
|
|
850ec7efb0 | ||
|
|
115d1c3618 | ||
|
|
6d26497bbb | ||
|
|
06305eb6b3 | ||
|
|
7bd84a845c | ||
|
|
165ee9e7f3 | ||
|
|
2c9becec10 | ||
|
|
1463bce24c | ||
|
|
5c56d07215 | ||
|
|
6fb314b326 | ||
|
|
9b01666ec6 | ||
|
|
14c862d284 | ||
|
|
bcfd72c645 | ||
|
|
499f66b7ae | ||
|
|
8e37d5cb76 | ||
|
|
57ba073874 | ||
|
|
329de3aab3 | ||
|
|
74a74b7ec7 | ||
|
|
2063da9eec | ||
|
|
7a7e97770c | ||
|
|
bea131cfd9 | ||
|
|
5a0d1f1dc6 | ||
|
|
b5035f9d2b | ||
|
|
07bd9de0c6 | ||
|
|
efde364224 | ||
|
|
fde1763eef | ||
|
|
429c7ca2c1 | ||
|
|
bf56311faa | ||
|
|
c38aaeb224 | ||
|
|
1b9139b9b0 | ||
|
|
13763e3540 | ||
|
|
56bab7485f | ||
|
|
8bfd0493b2 | ||
|
|
435cb2e7f7 | ||
|
|
9ed33f5c08 | ||
|
|
83669fd9e0 | ||
|
|
35578e0021 | ||
|
|
e281de30b5 | ||
|
|
ec2c2e74f0 | ||
|
|
b031c9772f | ||
|
|
f22420d502 | ||
|
|
ebfd5f54d2 | ||
|
|
28b4eaee31 | ||
|
|
b83448b7a6 | ||
|
|
eed783e42f | ||
|
|
1d624612ee | ||
|
|
9576f72d7c | ||
|
|
7efc4d1bc8 | ||
|
|
670e605275 | ||
|
|
871b3ce071 | ||
|
|
ad3c5fcee5 | ||
|
|
e2a87036e0 | ||
|
|
40bb86bee5 | ||
|
|
4a4ed2cf89 | ||
|
|
7751e124a7 | ||
|
|
6137d07138 | ||
|
|
66d316f0b5 | ||
|
|
bf2d56c9d4 | ||
|
|
413798b321 | ||
|
|
bf1c8c6119 | ||
|
|
49fa0a8468 | ||
|
|
784cd42a7a | ||
|
|
4f74be0247 | ||
|
|
7ef1e0a3e5 | ||
|
|
bc5fd380e5 | ||
|
|
bcd306b84d | ||
|
|
ad2870335d | ||
|
|
917748777f | ||
|
|
324c966dda | ||
|
|
0984821b41 | ||
|
|
e8be657d97 | ||
|
|
75ce261f74 | ||
|
|
5f9d0fe763 | ||
|
|
bbfd5270db | ||
|
|
1e632397d2 | ||
|
|
d35454c099 | ||
|
|
2a6ba7e7f0 | ||
|
|
1e523a1a39 | ||
|
|
fb91e73a3c | ||
|
|
ebd6139c1f | ||
|
|
6990be705c |
+13
-13
@@ -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
|
||||
@@ -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
|
||||
@@ -901,7 +901,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
|
||||
@@ -962,7 +962,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
|
||||
@@ -1352,6 +1352,6 @@ steps:
|
||||
- failure
|
||||
---
|
||||
kind: signature
|
||||
hmac: 196e215a44cc1b6b5cb89e6ef09196166af17969f69e3e7e8c571a0e44285714
|
||||
hmac: eff6e40dc763fa1395078daaabcbef2aaa74fe0a36818a87e89015dbc4e9dae4
|
||||
|
||||
...
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
use flake
|
||||
source_url "https://raw.githubusercontent.com/cachix/devenv/95f329d49a8a5289d31e0982652f7058a189bfca/direnvrc" "sha256-d+8cBpDfDBj41inrADaJt+bDWhOktwslgoP5YiGJ1v0="
|
||||
|
||||
use devenv
|
||||
+11
-1
@@ -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
|
||||
|
||||
Vendored
+13
-11
@@ -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"
|
||||
]
|
||||
}
|
||||
+52
-1
@@ -5,7 +5,58 @@ 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.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
|
||||
|
||||
|
||||
+13
-6
@@ -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/ ./
|
||||
|
||||
@@ -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}"
|
||||
@@ -35,7 +35,14 @@ RUN export PATH=$PATH:$GOPATH/bin && \
|
||||
|
||||
# The actual image
|
||||
FROM scratch
|
||||
LABEL maintainer="maintainers@vikunja.io"
|
||||
|
||||
LABEL org.opencontainers.image.authors='maintainers@vikunja.io'
|
||||
LABEL org.opencontainers.image.url='https://vikunja.io'
|
||||
LABEL org.opencontainers.image.documentation='https://vikunja.io/docs'
|
||||
LABEL org.opencontainers.image.source='https://code.vikunja.io/vikunja'
|
||||
LABEL org.opencontainers.image.licenses='AGPLv3'
|
||||
LABEL org.opencontainers.image.title='Vikunja'
|
||||
|
||||
WORKDIR /app/vikunja
|
||||
ENTRYPOINT [ "/app/vikunja/vikunja" ]
|
||||
EXPOSE 3456
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://drone.kolaente.de/vikunja/vikunja)
|
||||
[](LICENSE)
|
||||
[](https://vikunja.io/docs/installing)
|
||||
[](https://vikunja.io/docs/installing)
|
||||
[](https://hub.docker.com/r/vikunja/vikunja/)
|
||||
[](https://try.vikunja.io/api/v1/docs)
|
||||
[](https://goreportcard.com/report/kolaente.dev/vikunja/vikunja)
|
||||
|
||||
+6
-5
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+39
-34
@@ -9,13 +9,13 @@ dependencies:
|
||||
specifier: 2.0.0
|
||||
version: 2.0.0
|
||||
express:
|
||||
specifier: 4.19.2
|
||||
version: 4.19.2
|
||||
specifier: 4.21.0
|
||||
version: 4.21.0
|
||||
|
||||
devDependencies:
|
||||
electron:
|
||||
specifier: 29.4.5
|
||||
version: 29.4.5
|
||||
specifier: 29.4.6
|
||||
version: 29.4.6
|
||||
electron-builder:
|
||||
specifier: 24.13.3
|
||||
version: 24.13.3(electron-builder-squirrel-windows@24.13.3)
|
||||
@@ -445,8 +445,8 @@ packages:
|
||||
resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
|
||||
dev: true
|
||||
|
||||
/body-parser@1.20.2:
|
||||
resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
|
||||
/body-parser@1.20.3:
|
||||
resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
|
||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
@@ -457,7 +457,7 @@ packages:
|
||||
http-errors: 2.0.0
|
||||
iconv-lite: 0.4.24
|
||||
on-finished: 2.4.1
|
||||
qs: 6.11.0
|
||||
qs: 6.13.0
|
||||
raw-body: 2.5.2
|
||||
type-is: 1.6.18
|
||||
unpipe: 1.0.0
|
||||
@@ -917,8 +917,8 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/electron@29.4.5:
|
||||
resolution: {integrity: sha512-DlEuzGbWBYl1Qr0qUYgNZdoixJg4YGHy2HC6fkRjSXSlb01UrQ5ORi8hNLzelzyYx8rNQyyE3zDUuk9EnZwYuA==}
|
||||
/electron@29.4.6:
|
||||
resolution: {integrity: sha512-fz8ndj8cmmf441t4Yh2FDP3Rn0JhLkVGvtUf2YVMbJ5SdJPlc0JWll9jYkhh60jDKVVCr/tBAmfxqRnXMWJpzg==}
|
||||
engines: {node: '>= 12.20.55'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
@@ -943,6 +943,11 @@ packages:
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/encodeurl@2.0.0:
|
||||
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/end-of-stream@1.4.4:
|
||||
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
|
||||
dependencies:
|
||||
@@ -995,36 +1000,36 @@ packages:
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/express@4.19.2:
|
||||
resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==}
|
||||
/express@4.21.0:
|
||||
resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
dependencies:
|
||||
accepts: 1.3.8
|
||||
array-flatten: 1.1.1
|
||||
body-parser: 1.20.2
|
||||
body-parser: 1.20.3
|
||||
content-disposition: 0.5.4
|
||||
content-type: 1.0.5
|
||||
cookie: 0.6.0
|
||||
cookie-signature: 1.0.6
|
||||
debug: 2.6.9
|
||||
depd: 2.0.0
|
||||
encodeurl: 1.0.2
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
finalhandler: 1.2.0
|
||||
finalhandler: 1.3.1
|
||||
fresh: 0.5.2
|
||||
http-errors: 2.0.0
|
||||
merge-descriptors: 1.0.1
|
||||
merge-descriptors: 1.0.3
|
||||
methods: 1.1.2
|
||||
on-finished: 2.4.1
|
||||
parseurl: 1.3.3
|
||||
path-to-regexp: 0.1.7
|
||||
path-to-regexp: 0.1.10
|
||||
proxy-addr: 2.0.7
|
||||
qs: 6.11.0
|
||||
qs: 6.13.0
|
||||
range-parser: 1.2.1
|
||||
safe-buffer: 5.2.1
|
||||
send: 0.18.0
|
||||
serve-static: 1.15.0
|
||||
send: 0.19.0
|
||||
serve-static: 1.16.2
|
||||
setprototypeof: 1.2.0
|
||||
statuses: 2.0.1
|
||||
type-is: 1.6.18
|
||||
@@ -1077,12 +1082,12 @@ packages:
|
||||
minimatch: 5.1.6
|
||||
dev: true
|
||||
|
||||
/finalhandler@1.2.0:
|
||||
resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
|
||||
/finalhandler@1.3.1:
|
||||
resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
encodeurl: 1.0.2
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
on-finished: 2.4.1
|
||||
parseurl: 1.3.3
|
||||
@@ -1543,8 +1548,8 @@ packages:
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/merge-descriptors@1.0.1:
|
||||
resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
|
||||
/merge-descriptors@1.0.3:
|
||||
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
|
||||
dev: false
|
||||
|
||||
/methods@1.1.2:
|
||||
@@ -1724,8 +1729,8 @@ packages:
|
||||
minipass: 7.0.4
|
||||
dev: true
|
||||
|
||||
/path-to-regexp@0.1.7:
|
||||
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
|
||||
/path-to-regexp@0.1.10:
|
||||
resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==}
|
||||
dev: false
|
||||
|
||||
/pend@1.2.0:
|
||||
@@ -1780,8 +1785,8 @@ packages:
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
|
||||
/qs@6.11.0:
|
||||
resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
|
||||
/qs@6.13.0:
|
||||
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
|
||||
engines: {node: '>=0.6'}
|
||||
dependencies:
|
||||
side-channel: 1.0.6
|
||||
@@ -1919,8 +1924,8 @@ packages:
|
||||
lru-cache: 6.0.0
|
||||
dev: true
|
||||
|
||||
/send@0.18.0:
|
||||
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
|
||||
/send@0.19.0:
|
||||
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
@@ -1949,14 +1954,14 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/serve-static@1.15.0:
|
||||
resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
|
||||
/serve-static@1.16.2:
|
||||
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dependencies:
|
||||
encodeurl: 1.0.2
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
parseurl: 1.3.3
|
||||
send: 0.18.0
|
||||
send: 0.19.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
+139
@@ -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
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
{ 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;
|
||||
};
|
||||
};
|
||||
|
||||
services.mailpit = {
|
||||
enable = true;
|
||||
package = pkgs-unstable.mailpit;
|
||||
};
|
||||
}
|
||||
+17
@@ -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
|
||||
Generated
-25
@@ -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
|
||||
}
|
||||
@@ -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
@@ -1 +1 @@
|
||||
20.15.0
|
||||
20.16.0
|
||||
@@ -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')
|
||||
|
||||
+75
-75
@@ -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.0.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.30.0",
|
||||
"@tiptap/core": "2.7.2",
|
||||
"@tiptap/extension-blockquote": "2.7.2",
|
||||
"@tiptap/extension-bold": "2.7.2",
|
||||
"@tiptap/extension-bullet-list": "2.7.2",
|
||||
"@tiptap/extension-code": "2.7.2",
|
||||
"@tiptap/extension-code-block": "2.7.2",
|
||||
"@tiptap/extension-code-block-lowlight": "2.7.2",
|
||||
"@tiptap/extension-document": "2.7.2",
|
||||
"@tiptap/extension-dropcursor": "2.7.2",
|
||||
"@tiptap/extension-gapcursor": "2.7.2",
|
||||
"@tiptap/extension-hard-break": "2.7.2",
|
||||
"@tiptap/extension-heading": "2.7.2",
|
||||
"@tiptap/extension-history": "2.7.2",
|
||||
"@tiptap/extension-horizontal-rule": "2.7.2",
|
||||
"@tiptap/extension-image": "2.7.2",
|
||||
"@tiptap/extension-italic": "2.7.2",
|
||||
"@tiptap/extension-link": "2.7.2",
|
||||
"@tiptap/extension-list-item": "2.7.2",
|
||||
"@tiptap/extension-ordered-list": "2.7.2",
|
||||
"@tiptap/extension-paragraph": "2.7.2",
|
||||
"@tiptap/extension-placeholder": "2.7.2",
|
||||
"@tiptap/extension-strike": "2.7.2",
|
||||
"@tiptap/extension-table": "2.7.2",
|
||||
"@tiptap/extension-table-cell": "2.7.2",
|
||||
"@tiptap/extension-table-header": "2.7.2",
|
||||
"@tiptap/extension-table-row": "2.7.2",
|
||||
"@tiptap/extension-task-item": "2.7.2",
|
||||
"@tiptap/extension-task-list": "2.7.2",
|
||||
"@tiptap/extension-text": "2.7.2",
|
||||
"@tiptap/extension-typography": "2.7.2",
|
||||
"@tiptap/extension-underline": "2.7.2",
|
||||
"@tiptap/pm": "2.7.2",
|
||||
"@tiptap/suggestion": "2.7.2",
|
||||
"@tiptap/vue-3": "2.7.2",
|
||||
"@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",
|
||||
"date-fns": "4.1.0",
|
||||
"dayjs": "1.11.13",
|
||||
"dompurify": "3.1.6",
|
||||
"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.12",
|
||||
"tippy.js": "6.3.7",
|
||||
"ufo": "1.5.4",
|
||||
"vue": "3.4.32",
|
||||
"vue": "3.5.6",
|
||||
"vue-advanced-cropper": "2.8.9",
|
||||
"vue-flatpickr-component": "11.0.5",
|
||||
"vue-i18n": "9.13.1",
|
||||
"vue-router": "4.4.0",
|
||||
"vue-i18n": "10.0.1",
|
||||
"vue-router": "4.4.5",
|
||||
"vuemoji-picker": "0.2.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/lodash.clonedeep": "4.5.9",
|
||||
"@types/lodash.debounce": "4.0.9",
|
||||
"@types/node": "20.14.11",
|
||||
"@types/node": "20.16.5",
|
||||
"@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.23.3",
|
||||
"caniuse-lite": "1.0.30001660",
|
||||
"csstype": "3.1.3",
|
||||
"cypress": "13.13.1",
|
||||
"esbuild": "0.23.0",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-vue": "9.27.0",
|
||||
"cypress": "13.14.2",
|
||||
"esbuild": "0.23.1",
|
||||
"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.21.3",
|
||||
"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.1",
|
||||
"start-server-and-test": "2.0.8",
|
||||
"typescript": "5.6.2",
|
||||
"unplugin-inject-preload": "2.0.5",
|
||||
"vite": "5.4.6",
|
||||
"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",
|
||||
Generated
+2709
-2418
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
|
||||
@@ -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,13 +33,6 @@
|
||||
'is-wide': wide
|
||||
}"
|
||||
>
|
||||
<BaseButton
|
||||
class="close"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<Icon icon="times" />
|
||||
</BaseButton>
|
||||
|
||||
<slot>
|
||||
<div class="header">
|
||||
<slot name="header" />
|
||||
@@ -137,8 +136,8 @@ $modal-width: 1024px;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
margin: 0;
|
||||
top: 25%;
|
||||
transform: translate(-50%, -25%);
|
||||
position: static;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
@@ -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,42 @@ $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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -219,4 +248,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>
|
||||
|
||||
@@ -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,17 +566,18 @@ async function addTaskToBucket(bucketId: IBucket['id']) {
|
||||
bucketId,
|
||||
projectId: project.value.id,
|
||||
})
|
||||
toggleShowNewTaskInput(bucketId)
|
||||
newTaskText.value = ''
|
||||
kanbanStore.addTaskToBucket(task)
|
||||
scrollTaskContainerToBottom(bucketId)
|
||||
scrollTaskContainerToTop(bucketId)
|
||||
}
|
||||
|
||||
function scrollTaskContainerToBottom(bucketId: IBucket['id']) {
|
||||
function scrollTaskContainerToTop(bucketId: IBucket['id']) {
|
||||
const bucketEl = taskContainerRefs.value[bucketId]
|
||||
if (!bucketEl) {
|
||||
return
|
||||
}
|
||||
bucketEl.scrollTop = bucketEl.scrollHeight
|
||||
bucketEl.scrollTop = 0
|
||||
}
|
||||
|
||||
async function createNewBucket() {
|
||||
@@ -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
|
||||
|
||||
@@ -180,7 +180,7 @@ import ProgressBar from '@/components/misc/ProgressBar.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
||||
import AttachmentService from '@/services/attachment'
|
||||
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
|
||||
import {canPreview} from '@/models/attachment'
|
||||
import type {IAttachment} from '@/modelTypes/IAttachment'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
|
||||
@@ -274,10 +274,6 @@ async function viewOrDownload(attachment: IAttachment) {
|
||||
}
|
||||
}
|
||||
|
||||
function canPreview(attachment: IAttachment): boolean {
|
||||
return SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))
|
||||
}
|
||||
|
||||
const copy = useCopyToClipboard()
|
||||
|
||||
function copyUrl(attachment: IAttachment) {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
</span>
|
||||
<div
|
||||
v-for="c in comments"
|
||||
:id="`comment-${c.id}`"
|
||||
:key="c.id"
|
||||
class="media comment"
|
||||
>
|
||||
@@ -56,6 +57,13 @@
|
||||
>
|
||||
· {{ $t('task.comment.edited', {date: formatDateSince(c.updated)}) }}
|
||||
</span>
|
||||
<a
|
||||
v-tooltip="$t('task.comment.permalink')"
|
||||
:href="`#comment-${c.id}`"
|
||||
class="comment-permalink"
|
||||
:title="$t('task.comment.permalink')"
|
||||
@click.prevent.stop="copy(getCommentUrl(`${c.id}`))"
|
||||
><Icon icon="link" /></a>
|
||||
<CustomTransition name="fade">
|
||||
<span
|
||||
v-if="
|
||||
@@ -199,6 +207,7 @@ import {getAvatarUrl, getDisplayName} from '@/models/user'
|
||||
import {useConfigStore} from '@/stores/config'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import Reactions from '@/components/input/Reactions.vue'
|
||||
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
taskId: number,
|
||||
@@ -207,6 +216,8 @@ const props = withDefaults(defineProps<{
|
||||
canWrite: true,
|
||||
})
|
||||
|
||||
const copy = useCopyToClipboard()
|
||||
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
const configStore = useConfigStore()
|
||||
const authStore = useAuthStore()
|
||||
@@ -242,6 +253,8 @@ const actions = computed(() => {
|
||||
])))
|
||||
})
|
||||
|
||||
const frontendUrl = computed(() => configStore.frontendUrl)
|
||||
|
||||
async function attachmentUpload(files: File[] | FileList): (Promise<string[]>) {
|
||||
|
||||
const uploadPromises: Promise<string>[] = []
|
||||
@@ -364,6 +377,10 @@ async function deleteComment(commentToDelete: ITaskComment) {
|
||||
showDeleteModal.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getCommentUrl(commentId: string) {
|
||||
return `${frontendUrl.value}${location.pathname}${location.search}#comment-${commentId}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -391,10 +408,23 @@ async function deleteComment(commentToDelete: ITaskComment) {
|
||||
}
|
||||
|
||||
|
||||
span {
|
||||
span,
|
||||
.comment-permalink {
|
||||
font-size: .75rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.comment-permalink {
|
||||
font-size: 1rem;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.25rem;
|
||||
border-radius: 1rem;
|
||||
color: var(--grey, hsl(0, 0%, 48%));
|
||||
}
|
||||
.comment-permalink:hover {
|
||||
color: var(--grey-dark, hsl(0, 0%, 29%));
|
||||
border-color: var(--grey-dark, hsl(0, 0%, 29%));
|
||||
}
|
||||
}
|
||||
|
||||
.image.is-avatar {
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, shallowReactive, watchEffect} from 'vue'
|
||||
import AttachmentService from '@/services/attachment'
|
||||
import AttachmentService, {PREVIEW_SIZE} from '@/services/attachment'
|
||||
import type {IAttachment} from '@/modelTypes/IAttachment'
|
||||
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
|
||||
import {canPreview} from '@/models/attachment'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: IAttachment
|
||||
@@ -33,13 +33,9 @@ const blobUrl = ref<string | undefined>(undefined)
|
||||
|
||||
watchEffect(async () => {
|
||||
if (props.modelValue && canPreview(props.modelValue)) {
|
||||
blobUrl.value = await attachmentService.getBlobUrl(props.modelValue)
|
||||
blobUrl.value = await attachmentService.getBlobUrl(props.modelValue, PREVIEW_SIZE.MD)
|
||||
}
|
||||
})
|
||||
|
||||
function canPreview(attachment: IAttachment): boolean {
|
||||
return SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
<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
|
||||
class="close"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<Icon icon="times" />
|
||||
</BaseButton>
|
||||
</div>
|
||||
<h1
|
||||
class="title input"
|
||||
@@ -26,6 +32,12 @@
|
||||
>
|
||||
{{ task.title.trim() }}
|
||||
</h1>
|
||||
<BaseButton
|
||||
class="close"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<Icon icon="times" />
|
||||
</BaseButton>
|
||||
<CustomTransition name="fade">
|
||||
<span
|
||||
v-if="loading && saving"
|
||||
@@ -71,7 +83,8 @@ const props = withDefaults(defineProps<{
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:task': [task: ITask]
|
||||
'update:task': [task: ITask],
|
||||
'close': [],
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
@@ -138,14 +151,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 +163,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">
|
||||
@@ -93,7 +93,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, computed, watch} from 'vue'
|
||||
import {computed, ref, watch} from 'vue'
|
||||
import {useRouter} from 'vue-router'
|
||||
|
||||
import PriorityLabel from '@/components/tasks/partials/PriorityLabel.vue'
|
||||
@@ -105,9 +105,9 @@ import ChecklistSummary from './ChecklistSummary.vue'
|
||||
import {getHexColor} from '@/models/task'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
|
||||
import AttachmentService from '@/services/attachment'
|
||||
import AttachmentService, {PREVIEW_SIZE} from '@/services/attachment'
|
||||
|
||||
import {formatDateLong, formatISO, formatDateSince} from '@/helpers/time/formatDate'
|
||||
import {formatDateLong, formatDateSince, formatISO} from '@/helpers/time/formatDate'
|
||||
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
||||
import {useTaskStore} from '@/stores/tasks'
|
||||
import AssigneeList from '@/components/tasks/partials/AssigneeList.vue'
|
||||
@@ -165,7 +165,7 @@ async function maybeDownloadCoverImage() {
|
||||
}
|
||||
|
||||
const attachmentService = new AttachmentService()
|
||||
coverImageBlobUrl.value = await attachmentService.getBlobUrl(attachment)
|
||||
coverImageBlobUrl.value = await attachmentService.getBlobUrl(attachment, PREVIEW_SIZE.LG)
|
||||
}
|
||||
|
||||
watch(
|
||||
|
||||
@@ -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,8 @@ export const SUPPORTED_LOCALES = {
|
||||
'sl-SI': 'Slovenščina',
|
||||
'pt-BR': 'Português Brasileiro',
|
||||
'hr-HR': 'Hrvatski',
|
||||
'uk-UA': 'українська',
|
||||
'uk-UA': 'Українська',
|
||||
'lt-LT': 'Lietuvių Kalba',
|
||||
// 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?",
|
||||
@@ -848,7 +848,8 @@
|
||||
"delete": "Delete this comment",
|
||||
"deleteText1": "Are you sure you want to delete this comment?",
|
||||
"deleteSuccess": "The comment was deleted successfully.",
|
||||
"addedSuccess": "The comment was added successfully."
|
||||
"addedSuccess": "The comment was added successfully.",
|
||||
"permalink": "Copy permalink to this comment"
|
||||
},
|
||||
"deferDueDate": {
|
||||
"title": "Defer due date",
|
||||
@@ -1154,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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -25,6 +25,7 @@ export const DAYJS_LOCALE_MAPPING = {
|
||||
'pt-BR': 'pt',
|
||||
'hr-HR': 'hr',
|
||||
'uk-UA': 'uk',
|
||||
'lt-LT': 'lt',
|
||||
} as Record<SupportedLocale, ISOLanguage>
|
||||
|
||||
export const DAYJS_LANGUAGE_IMPORTS = {
|
||||
@@ -48,6 +49,7 @@ export const DAYJS_LANGUAGE_IMPORTS = {
|
||||
'sl-si': () => import('dayjs/locale/sl'),
|
||||
'pt-br': () => import('dayjs/locale/pt-br'),
|
||||
'uk-ua': () => import('dayjs/locale/uk'),
|
||||
'lt-lt': () => import('dayjs/locale/lt'),
|
||||
} as Record<SupportedLocale, () => Promise<ILocale>>
|
||||
|
||||
export function useDayjsLanguageSync(dayjsGlobal: typeof dayjs) {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@ import type { IAttachment } from '@/modelTypes/IAttachment'
|
||||
|
||||
export const SUPPORTED_IMAGE_SUFFIX = ['.jpg', '.png', '.bmp', '.gif']
|
||||
|
||||
export function canPreview(attachment: IAttachment): boolean {
|
||||
return SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))
|
||||
}
|
||||
|
||||
export default class AttachmentModel extends AbstractModel<IAttachment> implements IAttachment {
|
||||
id = 0
|
||||
taskId = 0
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,13 @@ import type { IAttachment } from '@/modelTypes/IAttachment'
|
||||
|
||||
import {downloadBlob} from '@/helpers/downloadBlob'
|
||||
|
||||
export enum PREVIEW_SIZE {
|
||||
SM = 'sm',
|
||||
MD = 'md',
|
||||
LG = 'lg',
|
||||
XL = 'xl',
|
||||
}
|
||||
|
||||
export default class AttachmentService extends AbstractService<IAttachment> {
|
||||
constructor() {
|
||||
super({
|
||||
@@ -37,8 +44,13 @@ export default class AttachmentService extends AbstractService<IAttachment> {
|
||||
return data
|
||||
}
|
||||
|
||||
getBlobUrl(model: IAttachment) {
|
||||
return AbstractService.prototype.getBlobUrl.call(this, '/tasks/' + model.taskId + '/attachments/' + model.id)
|
||||
getBlobUrl(model: IAttachment, size?: PREVIEW_SIZE) {
|
||||
let mainUrl = '/tasks/' + model.taskId + '/attachments/' + model.id
|
||||
if (size !== undefined) {
|
||||
mainUrl += `?preview_size=${size}`
|
||||
}
|
||||
|
||||
return AbstractService.prototype.getBlobUrl.call(this, mainUrl)
|
||||
}
|
||||
|
||||
async download(model: IAttachment) {
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -194,8 +194,8 @@ export const useKanbanStore = defineStore('kanban', () => {
|
||||
...oldBucket,
|
||||
count: (oldBucket?.count || 0) + 1,
|
||||
tasks: [
|
||||
...oldBucket.tasks,
|
||||
task,
|
||||
...oldBucket.tasks,
|
||||
],
|
||||
}
|
||||
buckets.value[bucketIndex] = newBucket
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user