Compare commits
255 Commits
507917dd19
...
a2e2cf32cb
Author | SHA1 | Date |
---|---|---|
renovate | a2e2cf32cb | |
renovate | 61322d2e2e | |
renovate | a41e248e5f | |
kolaente | 6e37934b61 | |
renovate | d64322bb7a | |
renovate | fa3b657e7e | |
Raymi306 | 1adaa73141 | |
renovate | 3e77e3043e | |
kolaente | d082c0399d | |
kolaente | 0b9ef27d04 | |
kolaente | 7acd1a7e51 | |
kolaente | 8bee5aa806 | |
kolaente | 6641cbebc2 | |
kolaente | 5892622676 | |
kolaente | 191a476823 | |
renovate | c146b72d64 | |
kolaente | ca33c0b2bc | |
kolaente | 4d78ae7fa8 | |
kolaente | c1d06c5e5a | |
kolaente | f1c3ce5eeb | |
kolaente | 2f6b395334 | |
kolaente | 1cd5dd2b2f | |
kolaente | 521300613f | |
kolaente | 037022e857 | |
kolaente | ec1ff80791 | |
kolaente | 7b8fab33a5 | |
kolaente | e0417c8bda | |
kolaente | 6fbd24d5f6 | |
kolaente | e534a6a5bf | |
kolaente | bf85cb0505 | |
kolaente | 20e2314128 | |
kolaente | 1ebb551864 | |
renovate | 30c1a46ed4 | |
kolaente | 1910f69392 | |
renovate | fe4a093825 | |
renovate | 90055d063c | |
waza-ari | f0d695e789 | |
kolaente | 95276ceebe | |
kolaente | 1558921f42 | |
kolaente | bf5088e546 | |
kolaente | 6f366d4907 | |
kolaente | d7554d9e70 | |
kolaente | 8a72fe26f8 | |
kolaente | 13cab62d14 | |
kolaente | 81de986d8d | |
kolaente | 915f677c2a | |
kolaente | 8a6e3d5bd7 | |
kolaente | 81fe8391e4 | |
kolaente | 89e37b88d9 | |
kolaente | cc6801c5b1 | |
kolaente | 767b058915 | |
kolaente | 2c0d3f2885 | |
renovate | fa170b9397 | |
kolaente | a5fd6f834a | |
renovate | 8984e0e9f0 | |
renovate | 176c41dc40 | |
waza-ari | c4d3d99cd4 | |
renovate | 30b4ed6b23 | |
renovate | aed92d1cd2 | |
Frederick [Bot] | 2239d73797 | |
Frederick [Bot] | 98b833f61a | |
Frederick [Bot] | ecd002dca3 | |
renovate | 5e3616bda3 | |
renovate | 3e5ff77b91 | |
kolaente | 403db6adbf | |
kolaente | fd4312382e | |
kolaente | 2dcf6c6861 | |
kolaente | 8f85af07ca | |
kolaente | 9f89fbe5a6 | |
kolaente | 6ad83c0685 | |
kolaente | 97b7592e7c | |
kolaente | 19c9cd9bc2 | |
Frederick [Bot] | e53fcd3367 | |
kolaente | d635fd2dd3 | |
renovate | b8584301a3 | |
renovate | 2bb4c31f20 | |
Frederick [Bot] | d22ebef0b3 | |
Frederick [Bot] | 68d8ed5a7a | |
konrad | 7230db1603 | |
kolaente | 32e8a15f1f | |
renovate | f80ffcd541 | |
kolaente | 89ed71777e | |
kolaente | 5b01710943 | |
kolaente | d7b40f393e | |
kolaente | fee75e55a3 | |
kolaente | fa137b1ffc | |
kolaente | e7d6ee2392 | |
kolaente | 62ff05695f | |
kolaente | 6f51b56589 | |
kolaente | 5e9edef3b3 | |
kolaente | f18fcc5569 | |
kolaente | bc8b5da61d | |
kolaente | b3a96ea251 | |
kolaente | b0ad087a36 | |
kolaente | b65d05ec3d | |
kolaente | 974c9cdd21 | |
kolaente | 8206cc8767 | |
kolaente | 53e57d524a | |
kolaente | 165d291cd5 | |
kolaente | e940db6d32 | |
kolaente | 7c30b00668 | |
renovate | 0ee8150e24 | |
kolaente | cf9b2fa203 | |
renovate | a9622fe03a | |
renovate | c67f0ae3cc | |
renovate | 8e7828f71d | |
renovate | 5e60edd9ae | |
kolaente | 511c9aa824 | |
kolaente | 4b903c4f48 | |
kolaente | 30b41bd143 | |
kolaente | f3cdd7d15f | |
kolaente | 8b90eb4a15 | |
kolaente | 803f58f402 | |
kolaente | b7b3169169 | |
kolaente | 409f9a0cc6 | |
kolaente | 9075a45cb8 | |
kolaente | d4bdd2d4e8 | |
kolaente | 9cc273d9bd | |
kolaente | 0f60a92873 | |
kolaente | bec9e3eb7d | |
kolaente | 3f8c5a5feb | |
kolaente | 24fa3b206f | |
kolaente | 434b1ea0e8 | |
kolaente | 433584813a | |
kolaente | 3ec3bb76af | |
kolaente | 6e53bf4ebe | |
kolaente | b8ff7910b0 | |
kolaente | f6485be9e2 | |
kolaente | 004f1e06bb | |
kolaente | 27cb6e3372 | |
kolaente | 445f1c06fa | |
kolaente | 4c1a53beed | |
kolaente | 7368a51f18 | |
kolaente | e1774cc49a | |
kolaente | 61e27ae3eb | |
kolaente | 7f1788eba9 | |
kolaente | 39c9928421 | |
kolaente | 59ced554cd | |
kolaente | bc34a33922 | |
kolaente | 2dfb3a6379 | |
kolaente | 337d289a39 | |
kolaente | 5451ddf58d | |
kolaente | a3714c74fd | |
kolaente | 4170f5468f | |
kolaente | f364f3bec8 | |
kolaente | 786e67f692 | |
kolaente | c36fdb9f5d | |
kolaente | dee78be579 | |
kolaente | 398c9f1056 | |
kolaente | ca0550acea | |
kolaente | cb111df2b7 | |
kolaente | df415f97a9 | |
kolaente | 86039b1dd2 | |
kolaente | 73e5483e87 | |
kolaente | 6913334b17 | |
kolaente | 7866543198 | |
kolaente | cf15cc6f12 | |
kolaente | ee6ea03506 | |
kolaente | 43f24661d7 | |
kolaente | 5641da27f7 | |
kolaente | 14353b24d7 | |
kolaente | ca4e3e01c5 | |
kolaente | 8ce476491e | |
kolaente | f2a0d69670 | |
kolaente | a13276e28e | |
kolaente | 9cf84646a1 | |
kolaente | 006f932dc4 | |
kolaente | 0a3f45ab11 | |
kolaente | d1d07f462c | |
kolaente | 2502776460 | |
kolaente | 238baf86f7 | |
kolaente | 652bf4b4ed | |
kolaente | a9020e976d | |
kolaente | 38457aaca5 | |
kolaente | 98b7cc9254 | |
kolaente | 4149ebed3a | |
kolaente | 2096fc5274 | |
kolaente | e4b1a5d2db | |
kolaente | 2fa3e2c2f5 | |
kolaente | ee228106fc | |
kolaente | b39c5580c2 | |
kolaente | 6bdb33fb46 | |
renovate | 091e03a39d | |
renovate | 55c9403dda | |
renovate | 3f33f903b5 | |
renovate | 650c6cb339 | |
kolaente | 2fff9f1c59 | |
renovate | 2cbd20a084 | |
renovate | 15949adc2b | |
renovate | 11f2db0e9c | |
Frederick [Bot] | 87ebe85972 | |
renovate | 0cf11228cf | |
renovate | 9d01b9105a | |
renovate | d6bc09b0cf | |
waza-ari | be54a361fd | |
renovate | 725a04b93c | |
renovate | 2add517d6e | |
kolaente | 96186250f4 | |
kolaente | 6cf3a578c0 | |
kolaente | c8b35d49ca | |
kolaente | 161bb1b192 | |
kolaente | 3ab22d8e06 | |
renovate | 273f5ddf59 | |
Frederick [Bot] | 88fdfb50b7 | |
kolaente | 07e84f2abf | |
kolaente | d4605905d3 | |
kolaente | 8c826c44d2 | |
kolaente | 117079bbda | |
kolaente | f34577f293 | |
kolaente | 8ff59d4649 | |
kolaente | 7bf2664e55 | |
kolaente | ccb708a56f | |
kolaente | 1de39b1cd1 | |
kolaente | b3caece256 | |
kolaente | a6edf1d325 | |
kolaente | fc4eed6eb4 | |
kolaente | 15215b30a0 | |
kolaente | 79577c14b7 | |
kolaente | 99c5524115 | |
renovate | 17e222edfd | |
Frederick [Bot] | fb5b2542a5 | |
kolaente | 5b2b7f7bdc | |
kolaente | e1c972d64d | |
kolaente | cf6b476b7d | |
kolaente | eb4f880c64 | |
kolaente | e44897e0d4 | |
renovate | 0e2ad5dde6 | |
Frederick [Bot] | 792bf88dcf | |
kolaente | a5c51d4b1e | |
renovate | b9c513f681 | |
renovate | 40bdecfe0d | |
renovate | da53c8e7ef | |
kolaente | 85fb8e3443 | |
Frederick [Bot] | 3f380e0d61 | |
kolaente | 659de54db1 | |
kolaente | 49ab90fc19 | |
kolaente | 0910d5d2f2 | |
kolaente | 09d5128050 | |
kolaente | e097721817 | |
kolaente | a66e26678e | |
kolaente | 6fc3d1e98f | |
kolaente | dbfe162cd2 | |
kolaente | 0529f30e77 | |
kolaente | 3896c680d3 | |
kolaente | 3b77fff4c9 | |
renovate | 12fbde8e84 | |
waza-ari | 6c98052176 | |
kolaente | 0057ac5836 | |
kolaente | 22dcedcd7d | |
kolaente | ca0de680ad | |
waza-ari | 4bb1d5edfc | |
Elscrux | 4b4a7f3c0a | |
waza-ari | ffa82556e0 | |
renovate | d7fdefcead | |
Elscrux | 1d5517b53a |
|
@ -139,7 +139,7 @@ steps:
|
|||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: api-lint
|
||||
image: golangci/golangci-lint:v1.55.2
|
||||
image: golangci/golangci-lint:v1.57.2
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
|
@ -461,7 +461,6 @@ steps:
|
|||
- cp -r dist-test dist-preview
|
||||
# Override the default api url used for preview
|
||||
- sed -i 's|http://localhost:3456|https://try.vikunja.io|g' dist-preview/index.html
|
||||
- apk add --no-cache perl-utils
|
||||
# create via:
|
||||
# `shasum -a 384 ./scripts/deploy-preview-netlify.mjs > ./scripts/deploy-preview-netlify.mjs.sha384`
|
||||
- shasum -a 384 -c ./scripts/deploy-preview-netlify.mjs.sha384
|
||||
|
@ -717,7 +716,7 @@ steps:
|
|||
|
||||
# Build os packages and push it to our bucket
|
||||
- name: build-os-packages-unstable
|
||||
image: goreleaser/nfpm:v2.35.3
|
||||
image: goreleaser/nfpm:v2.36.1
|
||||
pull: always
|
||||
commands:
|
||||
- apk add git go
|
||||
|
@ -733,7 +732,7 @@ steps:
|
|||
depends_on: [ after-build-compress ]
|
||||
|
||||
- name: build-os-packages-version
|
||||
image: goreleaser/nfpm:v2.35.3
|
||||
image: goreleaser/nfpm:v2.36.1
|
||||
pull: always
|
||||
commands:
|
||||
- apk add git go
|
||||
|
@ -1401,6 +1400,6 @@ steps:
|
|||
- failure
|
||||
---
|
||||
kind: signature
|
||||
hmac: bd616ecf66fe95bd25c5f4bd73fc9ccfc20601a87a4f8dd4574d66393eacd077
|
||||
hmac: 2c9cb0483fb346988188515f6423929f46eefb9e14eb26b0f312a0b694d5fe8c
|
||||
|
||||
...
|
||||
|
|
|
@ -28,3 +28,4 @@ vendor/
|
|||
os-packages/
|
||||
mage_output_file.go
|
||||
mage-static
|
||||
.direnv/
|
||||
|
|
|
@ -18,6 +18,7 @@ linters:
|
|||
- scopelint # Obsolete, using exportloopref instead
|
||||
- durationcheck
|
||||
- goconst
|
||||
- musttag
|
||||
presets:
|
||||
- bugs
|
||||
- unused
|
||||
|
@ -99,3 +100,7 @@ issues:
|
|||
- path: pkg/modules/migration/ticktick/ticktick_test.go
|
||||
linters:
|
||||
- testifylint
|
||||
- path: pkg/migration/*
|
||||
text: "parameter 'tx' seems to be unused, consider removing or renaming it as"
|
||||
linters:
|
||||
- revive
|
||||
|
|
|
@ -5,6 +5,7 @@ WORKDIR /build
|
|||
|
||||
ENV PNPM_CACHE_FOLDER .cache/pnpm/
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD true
|
||||
ENV CYPRESS_INSTALL_BINARY 0
|
||||
|
||||
COPY frontend/ ./
|
||||
|
||||
|
|
|
@ -62,6 +62,9 @@ service:
|
|||
allowiconchanges: true
|
||||
# Allow using a custom logo via external URL.
|
||||
customlogourl: ''
|
||||
# Enables the public team feature. If enabled, it is possible to configure teams to be public, which makes them
|
||||
# discoverable when sharing a project, therefore not only showing teams the user is member of.
|
||||
enablepublicteams: false
|
||||
|
||||
sentry:
|
||||
# If set to true, enables anonymous error tracking of api errors via Sentry. This allows us to gather more
|
||||
|
|
|
@ -51,11 +51,11 @@
|
|||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "29.1.1",
|
||||
"electron": "29.2.0",
|
||||
"electron-builder": "24.13.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"connect-history-api-fallback": "2.0.0",
|
||||
"express": "4.18.3"
|
||||
"express": "4.19.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -581,10 +581,10 @@ cookie-signature@1.0.6:
|
|||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
|
||||
|
||||
cookie@0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
|
||||
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
|
||||
cookie@0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
|
||||
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
|
||||
|
||||
core-util-is@1.0.2:
|
||||
version "1.0.2"
|
||||
|
@ -769,10 +769,10 @@ electron-publish@24.13.1:
|
|||
lazy-val "^1.0.5"
|
||||
mime "^2.5.2"
|
||||
|
||||
electron@29.1.1:
|
||||
version "29.1.1"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-29.1.1.tgz#e9cb11311324e4b43a3e73667cd2b65a30e8fa34"
|
||||
integrity sha512-cXN15NgCi7MkzGo5/23ZQbii+0UfhmUiDjACunmzcUofYCjF42XhFbL7JZnwgI0qtBCCeJU8qZNZt9lU91gUFw==
|
||||
electron@29.2.0:
|
||||
version "29.2.0"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-29.2.0.tgz#98e9d45dcebda124fb0bd1ff20fc509ec692101c"
|
||||
integrity sha512-ALKrCN52RG4g9prx4DriXSPnY5WoiyRUCNp7zEVQuoiNOpHTNqMMpRidQAHzntV4hajF1LMWHVoBkwqIs1jHhg==
|
||||
dependencies:
|
||||
"@electron/get" "^2.0.0"
|
||||
"@types/node" "^20.9.0"
|
||||
|
@ -830,17 +830,17 @@ etag@~1.8.1:
|
|||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
|
||||
|
||||
express@4.18.3:
|
||||
version "4.18.3"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.18.3.tgz#6870746f3ff904dee1819b82e4b51509afffb0d4"
|
||||
integrity sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==
|
||||
express@4.19.2:
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
|
||||
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
|
||||
dependencies:
|
||||
accepts "~1.3.8"
|
||||
array-flatten "1.1.1"
|
||||
body-parser "1.20.2"
|
||||
content-disposition "0.5.4"
|
||||
content-type "~1.0.4"
|
||||
cookie "0.5.0"
|
||||
cookie "0.6.0"
|
||||
cookie-signature "1.0.6"
|
||||
debug "2.6.9"
|
||||
depd "2.0.0"
|
||||
|
|
|
@ -35,7 +35,7 @@ That means compiling it boils down to these steps:
|
|||
|
||||
1. Make sure [Go](https://golang.org/doc/install) is properly installed on your system. You'll need at least Go `1.21`.
|
||||
2. Make sure [Mage](https://magefile.org) is properly installed on your system.
|
||||
3. If you did not build the frontend in the steps before, you need to either do that or create a dummy index file with `mkdir -p frontend/dist && touch index.html`.
|
||||
3. If you did not build the frontend in the steps before, you need to either do that or create a dummy index file with `mkdir -p frontend/dist && touch frontend/dist/index.html`.
|
||||
4. Run `mage build` in the source of the main repo. This will build a binary in the root of the repo which will be able to run on your system.
|
||||
|
||||
### Build for different architectures
|
||||
|
|
|
@ -346,6 +346,17 @@ Full path: `service.customlogourl`
|
|||
Environment path: `VIKUNJA_SERVICE_CUSTOMLOGOURL`
|
||||
|
||||
|
||||
### enablepublicteams
|
||||
|
||||
discoverable when sharing a project, therefore not only showing teams the user is member of.
|
||||
|
||||
Default: `false`
|
||||
|
||||
Full path: `service.enablepublicteams`
|
||||
|
||||
Environment path: `VIKUNJA_SERVICE_ENABLEPUBLICTEAMS`
|
||||
|
||||
|
||||
---
|
||||
|
||||
## sentry
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
date: "2023-03-09:00:00+02:00"
|
||||
title: "Migration from third-party services"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
weight: 5
|
||||
---
|
||||
|
||||
# Migration from third-party services
|
||||
|
||||
There are several importers available for third-party services like Trello, Microsoft To Do or Todoist.
|
||||
All available migration options can be found [here](https://kolaente.dev/vikunja/vikunja/src/branch/main/config.yml.sample#L218).
|
||||
|
||||
You can develop migrations for more services, see the [documentation]({{< ref "../development/migration.md">}}) for more info.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Trello
|
||||
|
||||
### Config Setup
|
||||
|
||||
Log into Trello and navigate to the [site](https://trello.com/app-key) to manage your API keys.
|
||||
Save your `Personal Key` for later and add your Vikunja domain to the Allowed Origins list.
|
||||
|
||||
Create a `config.yml` file based on [default config file](https://kolaente.dev/vikunja/vikunja/src/branch/main/config.yml.sample) if you haven't already.
|
||||
|
||||
- Copy the [Trello options](https://kolaente.dev/vikunja/vikunja/src/branch/main/config.yml.sample#L233) from the default config file
|
||||
- Set `enable` to true
|
||||
- Set `key` to your [trello API key](https://trello.com/app-key)
|
||||
- Replace `<frontend url>` in `redirecturl` with your url
|
||||
|
||||
### Config Loading
|
||||
|
||||
To load the config with Vikunja, see the [installation]({{< ref "install.md">}}) documentation for instructions to load the `config.yml` file and start Vikunja.
|
||||
|
||||
### Config Loading with Docker Compose
|
||||
|
||||
In case you are using Docker Compose you need to edit `docker-compose.yml` to load `config.yml`.
|
||||
Mount the `config.yml` file into the Vikunja container, by adding this line to the volumes of the Vikunja container and replacing the `./path/to/config.yml` with the relative path from the `docker-compose.yml` to your `config.yml`.
|
||||
```yaml
|
||||
volumes:
|
||||
- ./path/to/config.yml:/etc/vikunja/config.yml
|
||||
```
|
||||
|
||||
After all the setup is done, start Vikunja as shown in the [Docker Compose setup]({{< ref "full-docker-example.md">}}).
|
||||
|
||||
### Start the Migration Process
|
||||
|
||||
Log in, and navigate to Settings > Import from other services. In the list of available third-party services, there should be a Trello icon now.
|
||||
If not, ensure that you are properly loading your config file. Refer to the Vikunja log to see if the config file was loaded or not.
|
||||
In case the config file was loaded, and there is no Trello icon, make sure your [config setup](#config-setup) is correct.
|
||||
|
||||
Click on Trello and on Get Started. This will redirect you to Trello where you need to allow Vikunja Migration to access your account. In case there is an error when being directed to Trello, make sure that your Vikunja domain is in your Trello Allowed Origins list.
|
||||
Once this is done, you will be redirected to Vikunja which should tell you that the migration is in progress now. Note that this can take up to several hours depending on the amount of boards in your Trello account.
|
|
@ -34,6 +34,22 @@ Claims in turn are assertions containing information about the token bearer, usu
|
|||
**Scopes** are requested by the client when redirecting the end-user to the Authorization Server for authentication, and indirectly control which claims are included in the resulting tokens.
|
||||
There's certain default scopes, but its also possible to define custom scopes, which are used by the feature assigning users to Teams automatically.
|
||||
|
||||
## Supported and required claims
|
||||
|
||||
Vikunja only requires a few claims to be present in the ID token to successfully authenticate the user.
|
||||
Additional claims can be added though to customize behaviour during user creation.
|
||||
|
||||
The following table gives an overview about the claims supported by Vikunja. The scope column lists the scope that should request the claim according to the [OpenID Connect Standard](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims). It omits the claims such as `sub` or `issuer` required by the `openid` scope, which must always be present.
|
||||
|
||||
| Claim | Type | Scope | Comment |
|
||||
| ------|------|-------|---------|
|
||||
| email | required | email | Sets the email address of the user. Taken from the `userinfo` endpoint if not present in ID token. User creation fails if claim not present and userinfo lookup fails. |
|
||||
| name | optional | profile | Sets the display name of the user. Taken from the `userinfo` endpoint if not present in ID token. |
|
||||
| preferred_username | optional | profile | Sets the username of the user. Taken from the `userinfo` endpoint if not present in ID token. If this also doesn't contain the claim, use the `nickname` claim from `userinfo` instead. If that one is not available either, the username is auto-generated by Vikunja. |
|
||||
| vikunja_groups | optional | N/A | Can be used to automatically assign users to teams. See below for a more detailed explanation about the expected format and implementation examples. |
|
||||
|
||||
If one of the claims `email`, `name` or `preferred_username` is missing from the ID token, Vikunja will attempt to query the `userinfo` endpoint to obtain the information from there.
|
||||
|
||||
## Configuring OIDC Authentication
|
||||
|
||||
To achieve authentication via an external provider, it is required to (a) configure a confidential Client on your OAuth 2.0 provider and (b) configure Vikunja to authenticate against this provider.
|
||||
|
@ -64,7 +80,7 @@ auth:
|
|||
redirecturl: https://vikunja.mydomain.com/auth/openid/ <---- slash at the end is important
|
||||
providers:
|
||||
- name: <provider-name>
|
||||
authurl: <auth-url> <----- Used for OIDC Discovery, usually the issuer
|
||||
authurl: <auth-url> <----- Used for OIDC Discovery, usually the issuer
|
||||
clientid: <vikunja client-id>
|
||||
clientsecret: <vikunja client-secret>
|
||||
scope: openid profile email
|
||||
|
@ -99,7 +115,7 @@ It depends on the provider being used as well as the preferences of the administ
|
|||
Typically you'd want to request an additional scope (e.g. `vikunja_scope`) which then triggers the identity provider to add the claim.
|
||||
If the `vikunja_groups` is part of the **ID token**, Vikunja will start the procedure and import teams and team memberships.
|
||||
|
||||
The claim structure expexted by Vikunja is as follows:
|
||||
The minimal claim structure expected by Vikunja is as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -116,6 +132,21 @@ The claim structure expexted by Vikunja is as follows:
|
|||
}
|
||||
```
|
||||
|
||||
It is also possible to pass the `description` and the `isPublic` flag as optional parameters. If not present, the description will be empty and project visibility defaults to false.
|
||||
|
||||
```json
|
||||
{
|
||||
"vikunja_groups": [
|
||||
{
|
||||
"name": "team 3",
|
||||
"oidcID": 33349,
|
||||
"description": "My Team Description",
|
||||
"isPublic": true
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
For each team, you need to define a team `name` and an `oidcID`, where the `oidcID` can be any string with a length of less than 250 characters.
|
||||
The `oidcID` is used to uniquely identify the team, so please make sure to keep this unique.
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ Vikunja **currently does not** support these properties:
|
|||
* [Evolution](https://wiki.gnome.org/Apps/Evolution/)
|
||||
* [OpenTasks](https://opentasks.app/) & [DAVx⁵](https://www.davx5.com/)
|
||||
* [Tasks (Android)](https://tasks.org/)
|
||||
* [Korganizer](https://apps.kde.org/korganizer/)
|
||||
|
||||
### Not working
|
||||
|
||||
|
|
|
@ -55,19 +55,20 @@ This document describes the different errors Vikunja can return.
|
|||
|
||||
## Project
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 3001 | 404 | The project does not exist. |
|
||||
| 3004 | 403 | The user needs to have read permissions on that project to perform that action. |
|
||||
| 3005 | 400 | The project title cannot be empty. |
|
||||
| 3006 | 404 | The project share does not exist. |
|
||||
| 3007 | 400 | A project with this identifier already exists. |
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 3001 | 404 | The project does not exist. |
|
||||
| 3004 | 403 | The user needs to have read permissions on that project to perform that action. |
|
||||
| 3005 | 400 | The project title cannot be empty. |
|
||||
| 3006 | 404 | The project share does not exist. |
|
||||
| 3007 | 400 | A project with this identifier already exists. |
|
||||
| 3008 | 412 | The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project. |
|
||||
| 3009 | 412 | The project cannot belong to a dynamically generated parent project like "Favorites". |
|
||||
| 3010 | 412 | This project cannot be a child of itself. |
|
||||
| 3011 | 412 | This project cannot have a cyclic relationship to a parent project. |
|
||||
| 3012 | 412 | This project cannot be deleted because a user has set it as their default project. |
|
||||
| 3013 | 412 | This project cannot be archived because a user has set it as their default project. |
|
||||
| 3009 | 412 | The project cannot belong to a dynamically generated parent project like "Favorites". |
|
||||
| 3010 | 412 | This project cannot be a child of itself. |
|
||||
| 3011 | 412 | This project cannot have a cyclic relationship to a parent project. |
|
||||
| 3012 | 412 | This project cannot be deleted because a user has set it as their default project. |
|
||||
| 3013 | 412 | This project cannot be archived because a user has set it as their default project. |
|
||||
| 3014 | 404 | This project view does not exist. |
|
||||
|
||||
## Task
|
||||
|
||||
|
@ -97,6 +98,8 @@ This document describes the different errors Vikunja can return.
|
|||
| 4022 | 400 | The task has a relative reminder which does not specify relative to what. |
|
||||
| 4023 | 409 | Tried to create a task relation which would create a cycle. |
|
||||
| 4024 | 400 | The provided filter expression is invalid. |
|
||||
| 4025 | 400 | The reaction kind is invalid. |
|
||||
| 4026 | 400 | You must provide a project view ID when sorting by position. |
|
||||
|
||||
## Team
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ The available operators for filtering include:
|
|||
* `<`: Less than
|
||||
* `<=`: Less than or equal to
|
||||
* `like`: Matches a pattern (using wildcard `%`)
|
||||
* `in`: Matches any value in a list
|
||||
* `in`: Matches any value in a comma-seperated list of values
|
||||
|
||||
To combine multiple conditions, you can use the following logical operators:
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1701336116,
|
||||
"narHash": "sha256-kEmpezCR/FpITc6yMbAh4WrOCiT2zg5pSjnKrq51h5Y=",
|
||||
"lastModified": 1712449641,
|
||||
"narHash": "sha256-U9DDWMexN6o5Td2DznEgguh8TRIUnIl9levmit43GcI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f5c27c6136db4d76c30e533c20517df6864c46ee",
|
||||
"rev": "600b15aea1b36eeb43833a50b0e96579147099ff",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
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
|
||||
nodePackages.pnpm cypress
|
||||
# API tools
|
||||
go golangci-lint mage
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -13,7 +13,6 @@ node_modules
|
|||
/dist*
|
||||
coverage
|
||||
*.zip
|
||||
.direnv/
|
||||
|
||||
# Test files
|
||||
cypress/screenshots
|
||||
|
|
|
@ -1,15 +1,50 @@
|
|||
import {ProjectFactory} from '../../factories/project'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {ProjectViewFactory} from "../../factories/project_view";
|
||||
|
||||
export function createDefaultViews(projectId) {
|
||||
ProjectViewFactory.truncate()
|
||||
const list = ProjectViewFactory.create(1, {
|
||||
id: 1,
|
||||
project_id: projectId,
|
||||
view_kind: 0,
|
||||
}, false)
|
||||
const gantt = ProjectViewFactory.create(1, {
|
||||
id: 2,
|
||||
project_id: projectId,
|
||||
view_kind: 1,
|
||||
}, false)
|
||||
const table = ProjectViewFactory.create(1, {
|
||||
id: 3,
|
||||
project_id: projectId,
|
||||
view_kind: 2,
|
||||
}, false)
|
||||
const kanban = ProjectViewFactory.create(1, {
|
||||
id: 4,
|
||||
project_id: projectId,
|
||||
view_kind: 3,
|
||||
bucket_configuration_mode: 1,
|
||||
}, false)
|
||||
|
||||
return [
|
||||
list[0],
|
||||
gantt[0],
|
||||
table[0],
|
||||
kanban[0],
|
||||
]
|
||||
}
|
||||
|
||||
export function createProjects() {
|
||||
const projects = ProjectFactory.create(1, {
|
||||
title: 'First Project'
|
||||
})
|
||||
TaskFactory.truncate()
|
||||
projects.views = createDefaultViews(projects[0].id)
|
||||
return projects
|
||||
}
|
||||
|
||||
export function prepareProjects(setProjects = (...args: any[]) => {}) {
|
||||
export function prepareProjects(setProjects = (...args: any[]) => {
|
||||
}) {
|
||||
beforeEach(() => {
|
||||
const projects = createProjects()
|
||||
setProjects(projects)
|
||||
|
|
|
@ -2,6 +2,7 @@ import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
|||
|
||||
import {ProjectFactory} from '../../factories/project'
|
||||
import {prepareProjects} from './prepareProjects'
|
||||
import {ProjectViewFactory} from '../../factories/project_view'
|
||||
|
||||
describe('Project History', () => {
|
||||
createFakeUserAndLogin()
|
||||
|
@ -11,24 +12,31 @@ describe('Project History', () => {
|
|||
cy.intercept(Cypress.env('API_URL') + '/projects*').as('loadProjectArray')
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/*').as('loadProject')
|
||||
|
||||
const projects = ProjectFactory.create(6)
|
||||
const projects = ProjectFactory.create(7)
|
||||
ProjectViewFactory.truncate()
|
||||
projects.forEach(p => ProjectViewFactory.create(1, {
|
||||
id: p.id,
|
||||
project_id: p.id,
|
||||
}, false))
|
||||
|
||||
cy.visit('/')
|
||||
cy.wait('@loadProjectArray')
|
||||
cy.get('body')
|
||||
.should('not.contain', 'Last viewed')
|
||||
|
||||
cy.visit(`/projects/${projects[0].id}`)
|
||||
cy.visit(`/projects/${projects[0].id}/${projects[0].id}`)
|
||||
cy.wait('@loadProject')
|
||||
cy.visit(`/projects/${projects[1].id}`)
|
||||
cy.visit(`/projects/${projects[1].id}/${projects[1].id}`)
|
||||
cy.wait('@loadProject')
|
||||
cy.visit(`/projects/${projects[2].id}`)
|
||||
cy.visit(`/projects/${projects[2].id}/${projects[2].id}`)
|
||||
cy.wait('@loadProject')
|
||||
cy.visit(`/projects/${projects[3].id}`)
|
||||
cy.visit(`/projects/${projects[3].id}/${projects[3].id}`)
|
||||
cy.wait('@loadProject')
|
||||
cy.visit(`/projects/${projects[4].id}`)
|
||||
cy.visit(`/projects/${projects[4].id}/${projects[4].id}`)
|
||||
cy.wait('@loadProject')
|
||||
cy.visit(`/projects/${projects[5].id}`)
|
||||
cy.visit(`/projects/${projects[5].id}/${projects[5].id}`)
|
||||
cy.wait('@loadProject')
|
||||
cy.visit(`/projects/${projects[6].id}/${projects[6].id}`)
|
||||
cy.wait('@loadProject')
|
||||
|
||||
// cy.visit('/')
|
||||
|
@ -46,5 +54,6 @@ describe('Project History', () => {
|
|||
.should('contain', projects[3].title)
|
||||
.should('contain', projects[4].title)
|
||||
.should('contain', projects[5].title)
|
||||
.should('contain', projects[6].title)
|
||||
})
|
||||
})
|
|
@ -11,7 +11,7 @@ describe('Project View Gantt', () => {
|
|||
|
||||
it('Hides tasks with no dates', () => {
|
||||
const tasks = TaskFactory.create(1)
|
||||
cy.visit('/projects/1/gantt')
|
||||
cy.visit('/projects/1/2')
|
||||
|
||||
cy.get('.g-gantt-rows-container')
|
||||
.should('not.contain', tasks[0].title)
|
||||
|
@ -25,7 +25,7 @@ describe('Project View Gantt', () => {
|
|||
nextMonth.setDate(1)
|
||||
nextMonth.setMonth(9)
|
||||
|
||||
cy.visit('/projects/1/gantt')
|
||||
cy.visit('/projects/1/2')
|
||||
|
||||
cy.get('.g-timeunits-container')
|
||||
.should('contain', format(now, 'MMMM'))
|
||||
|
@ -38,7 +38,7 @@ describe('Project View Gantt', () => {
|
|||
start_date: now.toISOString(),
|
||||
end_date: new Date(new Date(now).setDate(now.getDate() + 4)).toISOString(),
|
||||
})
|
||||
cy.visit('/projects/1/gantt')
|
||||
cy.visit('/projects/1/2')
|
||||
|
||||
cy.get('.g-gantt-rows-container')
|
||||
.should('not.be.empty')
|
||||
|
@ -50,7 +50,7 @@ describe('Project View Gantt', () => {
|
|||
start_date: null,
|
||||
end_date: null,
|
||||
})
|
||||
cy.visit('/projects/1/gantt')
|
||||
cy.visit('/projects/1/2')
|
||||
|
||||
cy.get('.gantt-options .fancycheckbox')
|
||||
.contains('Show tasks which don\'t have dates set')
|
||||
|
@ -69,7 +69,7 @@ describe('Project View Gantt', () => {
|
|||
start_date: now.toISOString(),
|
||||
end_date: new Date(new Date(now).setDate(now.getDate() + 4)).toISOString(),
|
||||
})
|
||||
cy.visit('/projects/1/gantt')
|
||||
cy.visit('/projects/1/2')
|
||||
|
||||
cy.get('.g-gantt-rows-container .g-gantt-row .g-gantt-row-bars-container div .g-gantt-bar')
|
||||
.first()
|
||||
|
@ -83,7 +83,7 @@ describe('Project View Gantt', () => {
|
|||
const now = Date.UTC(2022, 10, 9)
|
||||
cy.clock(now, ['Date'])
|
||||
|
||||
cy.visit('/projects/1/gantt')
|
||||
cy.visit('/projects/1/2')
|
||||
|
||||
cy.get('.project-gantt .gantt-options .field .control input.input.form-control')
|
||||
.click()
|
||||
|
@ -99,7 +99,7 @@ describe('Project View Gantt', () => {
|
|||
})
|
||||
|
||||
it('Should change the date range based on date query parameters', () => {
|
||||
cy.visit('/projects/1/gantt?dateFrom=2022-09-25&dateTo=2022-11-05')
|
||||
cy.visit('/projects/1/2?dateFrom=2022-09-25&dateTo=2022-11-05')
|
||||
|
||||
cy.get('.g-timeunits-container')
|
||||
.should('contain', 'September 2022')
|
||||
|
@ -115,7 +115,7 @@ describe('Project View Gantt', () => {
|
|||
start_date: formatISO(now),
|
||||
end_date: formatISO(now.setDate(now.getDate() + 4)),
|
||||
})
|
||||
cy.visit('/projects/1/gantt')
|
||||
cy.visit('/projects/1/2')
|
||||
|
||||
cy.get('.gantt-container .g-gantt-chart .g-gantt-row-bars-container .g-gantt-bar')
|
||||
.dblclick()
|
||||
|
|
|
@ -4,35 +4,64 @@ import {BucketFactory} from '../../factories/bucket'
|
|||
import {ProjectFactory} from '../../factories/project'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {prepareProjects} from './prepareProjects'
|
||||
import {ProjectViewFactory} from "../../factories/project_view";
|
||||
import {TaskBucketFactory} from "../../factories/task_buckets";
|
||||
|
||||
function createSingleTaskInBucket(count = 1, attrs = {}) {
|
||||
const projects = ProjectFactory.create(1)
|
||||
const buckets = BucketFactory.create(2, {
|
||||
const views = ProjectViewFactory.create(1, {
|
||||
id: 1,
|
||||
project_id: projects[0].id,
|
||||
view_kind: 3,
|
||||
bucket_configuration_mode: 1,
|
||||
})
|
||||
const buckets = BucketFactory.create(2, {
|
||||
project_view_id: views[0].id,
|
||||
})
|
||||
const tasks = TaskFactory.create(count, {
|
||||
project_id: projects[0].id,
|
||||
bucket_id: buckets[0].id,
|
||||
...attrs,
|
||||
})
|
||||
return tasks[0]
|
||||
TaskBucketFactory.create(1, {
|
||||
task_id: tasks[0].id,
|
||||
bucket_id: buckets[0].id,
|
||||
project_view_id: views[0].id,
|
||||
})
|
||||
return {
|
||||
task: tasks[0],
|
||||
view: views[0],
|
||||
project: projects[0],
|
||||
}
|
||||
}
|
||||
|
||||
function createTaskWithBuckets(buckets, count = 1) {
|
||||
const data = TaskFactory.create(10, {
|
||||
project_id: 1,
|
||||
})
|
||||
TaskBucketFactory.truncate()
|
||||
data.forEach(t => TaskBucketFactory.create(count, {
|
||||
task_id: t.id,
|
||||
bucket_id: buckets[0].id,
|
||||
project_view_id: buckets[0].project_view_id,
|
||||
}, false))
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
describe('Project View Kanban', () => {
|
||||
createFakeUserAndLogin()
|
||||
prepareProjects()
|
||||
|
||||
|
||||
let buckets
|
||||
beforeEach(() => {
|
||||
buckets = BucketFactory.create(2)
|
||||
buckets = BucketFactory.create(2, {
|
||||
project_view_id: 4,
|
||||
})
|
||||
})
|
||||
|
||||
it('Shows all buckets with their tasks', () => {
|
||||
const data = TaskFactory.create(10, {
|
||||
project_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
cy.visit('/projects/1/kanban')
|
||||
const data = createTaskWithBuckets(buckets, 10)
|
||||
cy.visit('/projects/1/4')
|
||||
|
||||
cy.get('.kanban .bucket .title')
|
||||
.contains(buckets[0].title)
|
||||
|
@ -46,11 +75,8 @@ describe('Project View Kanban', () => {
|
|||
})
|
||||
|
||||
it('Can add a new task to a bucket', () => {
|
||||
TaskFactory.create(2, {
|
||||
project_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
cy.visit('/projects/1/kanban')
|
||||
createTaskWithBuckets(buckets, 2)
|
||||
cy.visit('/projects/1/4')
|
||||
|
||||
cy.get('.kanban .bucket')
|
||||
.contains(buckets[0].title)
|
||||
|
@ -68,7 +94,7 @@ describe('Project View Kanban', () => {
|
|||
})
|
||||
|
||||
it('Can create a new bucket', () => {
|
||||
cy.visit('/projects/1/kanban')
|
||||
cy.visit('/projects/1/4')
|
||||
|
||||
cy.get('.kanban .bucket.new-bucket .button')
|
||||
.click()
|
||||
|
@ -82,7 +108,7 @@ describe('Project View Kanban', () => {
|
|||
})
|
||||
|
||||
it('Can set a bucket limit', () => {
|
||||
cy.visit('/projects/1/kanban')
|
||||
cy.visit('/projects/1/4')
|
||||
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
||||
.first()
|
||||
|
@ -103,7 +129,7 @@ describe('Project View Kanban', () => {
|
|||
})
|
||||
|
||||
it('Can rename a bucket', () => {
|
||||
cy.visit('/projects/1/kanban')
|
||||
cy.visit('/projects/1/4')
|
||||
|
||||
cy.get('.kanban .bucket .bucket-header .title')
|
||||
.first()
|
||||
|
@ -114,7 +140,7 @@ describe('Project View Kanban', () => {
|
|||
})
|
||||
|
||||
it('Can delete a bucket', () => {
|
||||
cy.visit('/projects/1/kanban')
|
||||
cy.visit('/projects/1/4')
|
||||
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
||||
.first()
|
||||
|
@ -137,17 +163,14 @@ describe('Project View Kanban', () => {
|
|||
})
|
||||
|
||||
it('Can drag tasks around', () => {
|
||||
const tasks = TaskFactory.create(2, {
|
||||
project_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
cy.visit('/projects/1/kanban')
|
||||
const tasks = createTaskWithBuckets(buckets, 2)
|
||||
cy.visit('/projects/1/4')
|
||||
|
||||
cy.get('.kanban .bucket .tasks .task')
|
||||
.contains(tasks[0].title)
|
||||
.first()
|
||||
.drag('.kanban .bucket:nth-child(2) .tasks')
|
||||
|
||||
|
||||
cy.get('.kanban .bucket:nth-child(2) .tasks')
|
||||
.should('contain', tasks[0].title)
|
||||
cy.get('.kanban .bucket:nth-child(1) .tasks')
|
||||
|
@ -155,12 +178,8 @@ describe('Project View Kanban', () => {
|
|||
})
|
||||
|
||||
it('Should navigate to the task when the task card is clicked', () => {
|
||||
const tasks = TaskFactory.create(5, {
|
||||
id: '{increment}',
|
||||
project_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
cy.visit('/projects/1/kanban')
|
||||
const tasks = createTaskWithBuckets(buckets, 5)
|
||||
cy.visit('/projects/1/4')
|
||||
|
||||
cy.get('.kanban .bucket .tasks .task')
|
||||
.contains(tasks[0].title)
|
||||
|
@ -168,28 +187,33 @@ describe('Project View Kanban', () => {
|
|||
.click()
|
||||
|
||||
cy.url()
|
||||
.should('contain', `/tasks/${tasks[0].id}`, { timeout: 1000 })
|
||||
.should('contain', `/tasks/${tasks[0].id}`, {timeout: 1000})
|
||||
})
|
||||
|
||||
it('Should remove a task from the kanban board when moving it to another project', () => {
|
||||
const projects = ProjectFactory.create(2)
|
||||
BucketFactory.create(2, {
|
||||
const views = ProjectViewFactory.create(2, {
|
||||
project_id: '{increment}',
|
||||
view_kind: 3,
|
||||
bucket_configuration_mode: 1,
|
||||
})
|
||||
BucketFactory.create(2)
|
||||
const tasks = TaskFactory.create(5, {
|
||||
id: '{increment}',
|
||||
project_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
TaskBucketFactory.create(5, {
|
||||
project_view_id: 1,
|
||||
})
|
||||
const task = tasks[0]
|
||||
cy.visit('/projects/1/kanban')
|
||||
cy.visit('/projects/1/'+views[0].id)
|
||||
|
||||
cy.get('.kanban .bucket .tasks .task')
|
||||
.contains(task.title)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.get('.task-view .action-buttons .button', { timeout: 3000 })
|
||||
cy.get('.task-view .action-buttons .button', {timeout: 3000})
|
||||
.contains('Move')
|
||||
.click()
|
||||
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
|
||||
|
@ -201,27 +225,23 @@ describe('Project View Kanban', () => {
|
|||
.first()
|
||||
.click()
|
||||
|
||||
cy.get('.global-notification', { timeout: 1000 })
|
||||
cy.get('.global-notification', {timeout: 1000})
|
||||
.should('contain', 'Success')
|
||||
cy.go('back')
|
||||
cy.get('.kanban .bucket')
|
||||
.should('not.contain', task.title)
|
||||
})
|
||||
|
||||
|
||||
it('Shows a button to filter the kanban board', () => {
|
||||
const data = TaskFactory.create(10, {
|
||||
project_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
cy.visit('/projects/1/kanban')
|
||||
|
||||
cy.visit('/projects/1/4')
|
||||
|
||||
cy.get('.project-kanban .filter-container .base-button')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
|
||||
it('Should remove a task from the board when deleting it', () => {
|
||||
const task = createSingleTaskInBucket(5)
|
||||
cy.visit('/projects/1/kanban')
|
||||
const {task, view} = createSingleTaskInBucket(5)
|
||||
cy.visit(`/projects/1/${view.id}`)
|
||||
|
||||
cy.get('.kanban .bucket .tasks .task')
|
||||
.contains(task.title)
|
||||
|
@ -239,18 +259,18 @@ describe('Project View Kanban', () => {
|
|||
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
|
||||
|
||||
cy.get('.kanban .bucket .tasks')
|
||||
.should('not.contain', task.title)
|
||||
})
|
||||
|
||||
it('Should show a task description icon if the task has a description', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/buckets**').as('loadTasks')
|
||||
const task = createSingleTaskInBucket(1, {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/views/*/tasks**').as('loadTasks')
|
||||
const {task, view} = createSingleTaskInBucket(1, {
|
||||
description: 'Lorem Ipsum',
|
||||
})
|
||||
|
||||
cy.visit(`/projects/${task.project_id}/kanban`)
|
||||
cy.visit(`/projects/${task.project_id}/${view.id}`)
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.bucket .tasks .task .footer .icon svg')
|
||||
|
@ -258,12 +278,12 @@ describe('Project View Kanban', () => {
|
|||
})
|
||||
|
||||
it('Should not show a task description icon if the task has an empty description', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/buckets**').as('loadTasks')
|
||||
const task = createSingleTaskInBucket(1, {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/views/*/tasks**').as('loadTasks')
|
||||
const {task, view} = createSingleTaskInBucket(1, {
|
||||
description: '',
|
||||
})
|
||||
|
||||
cy.visit(`/projects/${task.project_id}/kanban`)
|
||||
cy.visit(`/projects/${task.project_id}/${view.id}`)
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.bucket .tasks .task .footer .icon svg')
|
||||
|
@ -271,15 +291,15 @@ describe('Project View Kanban', () => {
|
|||
})
|
||||
|
||||
it('Should not show a task description icon if the task has a description containing only an empty p tag', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/buckets**').as('loadTasks')
|
||||
const task = createSingleTaskInBucket(1, {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/views/*/tasks**').as('loadTasks')
|
||||
const {task, view} = createSingleTaskInBucket(1, {
|
||||
description: '<p></p>',
|
||||
})
|
||||
|
||||
cy.visit(`/projects/${task.project_id}/kanban`)
|
||||
cy.visit(`/projects/${task.project_id}/${view.id}`)
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.bucket .tasks .task .footer .icon svg')
|
||||
.should('not.exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -5,15 +5,16 @@ import {TaskFactory} from '../../factories/task'
|
|||
import {UserFactory} from '../../factories/user'
|
||||
import {ProjectFactory} from '../../factories/project'
|
||||
import {prepareProjects} from './prepareProjects'
|
||||
import {BucketFactory} from '../../factories/bucket'
|
||||
|
||||
describe('Project View Project', () => {
|
||||
describe('Project View List', () => {
|
||||
createFakeUserAndLogin()
|
||||
prepareProjects()
|
||||
|
||||
it('Should be an empty project', () => {
|
||||
cy.visit('/projects/1')
|
||||
cy.url()
|
||||
.should('contain', '/projects/1/list')
|
||||
.should('contain', '/projects/1/1')
|
||||
cy.get('.project-title')
|
||||
.should('contain', 'First Project')
|
||||
cy.get('.project-title-dropdown')
|
||||
|
@ -24,6 +25,10 @@ describe('Project View Project', () => {
|
|||
})
|
||||
|
||||
it('Should create a new task', () => {
|
||||
BucketFactory.create(2, {
|
||||
project_view_id: 4,
|
||||
})
|
||||
|
||||
const newTaskTitle = 'New task'
|
||||
|
||||
cy.visit('/projects/1')
|
||||
|
@ -38,7 +43,7 @@ describe('Project View Project', () => {
|
|||
id: '{increment}',
|
||||
project_id: 1,
|
||||
})
|
||||
cy.visit('/projects/1/list')
|
||||
cy.visit('/projects/1/1')
|
||||
|
||||
cy.get('.tasks .task .tasktext')
|
||||
.contains(tasks[0].title)
|
||||
|
@ -88,10 +93,10 @@ describe('Project View Project', () => {
|
|||
title: i => `task${i}`,
|
||||
project_id: 1,
|
||||
})
|
||||
cy.visit('/projects/1/list')
|
||||
cy.visit('/projects/1/1')
|
||||
|
||||
cy.get('.tasks')
|
||||
.should('contain', tasks[1].title)
|
||||
.should('contain', tasks[20].title)
|
||||
cy.get('.tasks')
|
||||
.should('not.contain', tasks[99].title)
|
||||
|
||||
|
@ -104,6 +109,6 @@ describe('Project View Project', () => {
|
|||
cy.get('.tasks')
|
||||
.should('contain', tasks[99].title)
|
||||
cy.get('.tasks')
|
||||
.should('not.contain', tasks[1].title)
|
||||
.should('not.contain', tasks[20].title)
|
||||
})
|
||||
})
|
|
@ -1,13 +1,15 @@
|
|||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {prepareProjects} from './prepareProjects'
|
||||
|
||||
describe('Project View Table', () => {
|
||||
createFakeUserAndLogin()
|
||||
prepareProjects()
|
||||
|
||||
it('Should show a table with tasks', () => {
|
||||
const tasks = TaskFactory.create(1)
|
||||
cy.visit('/projects/1/table')
|
||||
cy.visit('/projects/1/3')
|
||||
|
||||
cy.get('.project-table table.table')
|
||||
.should('exist')
|
||||
|
@ -17,9 +19,9 @@ describe('Project View Table', () => {
|
|||
|
||||
it('Should have working column switches', () => {
|
||||
TaskFactory.create(1)
|
||||
cy.visit('/projects/1/table')
|
||||
cy.visit('/projects/1/3')
|
||||
|
||||
cy.get('.project-table .filter-container .items .button')
|
||||
cy.get('.project-table .filter-container .button')
|
||||
.contains('Columns')
|
||||
.click()
|
||||
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox')
|
||||
|
@ -42,7 +44,7 @@ describe('Project View Table', () => {
|
|||
id: '{increment}',
|
||||
project_id: 1,
|
||||
})
|
||||
cy.visit('/projects/1/table')
|
||||
cy.visit('/projects/1/3')
|
||||
|
||||
cy.get('.project-table table.table')
|
||||
.contains(tasks[0].title)
|
||||
|
|
|
@ -33,14 +33,14 @@ describe('Projects', () => {
|
|||
})
|
||||
|
||||
it('Should redirect to a specific project view after visited', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/*/buckets*').as('loadBuckets')
|
||||
cy.visit('/projects/1/kanban')
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/*/views/*/tasks**').as('loadBuckets')
|
||||
cy.visit('/projects/1/4')
|
||||
cy.url()
|
||||
.should('contain', '/projects/1/kanban')
|
||||
.should('contain', '/projects/1/4')
|
||||
cy.wait('@loadBuckets')
|
||||
cy.visit('/projects/1')
|
||||
cy.url()
|
||||
.should('contain', '/projects/1/kanban')
|
||||
.should('contain', '/projects/1/4')
|
||||
})
|
||||
|
||||
it('Should rename the project in all places', () => {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {LinkShareFactory} from '../../factories/link_sharing'
|
||||
import {ProjectFactory} from '../../factories/project'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {createProjects} from '../project/prepareProjects'
|
||||
|
||||
function prepareLinkShare() {
|
||||
const projects = ProjectFactory.create(1)
|
||||
const projects = createProjects()
|
||||
const tasks = TaskFactory.create(10, {
|
||||
project_id: projects[0].id
|
||||
})
|
||||
|
@ -32,13 +32,13 @@ describe('Link shares', () => {
|
|||
cy.get('.tasks')
|
||||
.should('contain', tasks[0].title)
|
||||
|
||||
cy.url().should('contain', `/projects/${project.id}/list#share-auth-token=${share.hash}`)
|
||||
cy.url().should('contain', `/projects/${project.id}/1#share-auth-token=${share.hash}`)
|
||||
})
|
||||
|
||||
it('Should work when directly viewing a project with share hash present', () => {
|
||||
const {share, project, tasks} = prepareLinkShare()
|
||||
|
||||
cy.visit(`/projects/${project.id}/list#share-auth-token=${share.hash}`)
|
||||
cy.visit(`/projects/${project.id}/1#share-auth-token=${share.hash}`)
|
||||
|
||||
cy.get('h1.title')
|
||||
.should('contain', project.title)
|
||||
|
|
|
@ -5,11 +5,13 @@ import {seed} from '../../support/seed'
|
|||
import {TaskFactory} from '../../factories/task'
|
||||
import {BucketFactory} from '../../factories/bucket'
|
||||
import {updateUserSettings} from '../../support/updateUserSettings'
|
||||
import {createDefaultViews} from "../project/prepareProjects";
|
||||
|
||||
function seedTasks(numberOfTasks = 50, startDueDate = new Date()) {
|
||||
const project = ProjectFactory.create()[0]
|
||||
const views = createDefaultViews(project.id)
|
||||
BucketFactory.create(1, {
|
||||
project_id: project.id,
|
||||
project_view_id: views[3].id,
|
||||
})
|
||||
const tasks = []
|
||||
let dueDate = startDueDate
|
||||
|
@ -60,7 +62,7 @@ describe('Home Page Task Overview', () => {
|
|||
})
|
||||
|
||||
it('Should show a new task with a very soon due date at the top', () => {
|
||||
const {tasks} = seedTasks()
|
||||
const {tasks} = seedTasks(49)
|
||||
const newTaskTitle = 'New Task'
|
||||
|
||||
cy.visit('/')
|
||||
|
@ -71,9 +73,8 @@ describe('Home Page Task Overview', () => {
|
|||
due_date: new Date().toISOString(),
|
||||
}, false)
|
||||
|
||||
cy.visit(`/projects/${tasks[0].project_id}/list`)
|
||||
cy.visit(`/projects/${tasks[0].project_id}/1`)
|
||||
cy.get('.tasks .task')
|
||||
.first()
|
||||
.should('contain.text', newTaskTitle)
|
||||
cy.visit('/')
|
||||
cy.get('[data-cy="showTasks"] .card .task')
|
||||
|
@ -88,7 +89,7 @@ describe('Home Page Task Overview', () => {
|
|||
|
||||
cy.visit('/')
|
||||
|
||||
cy.visit(`/projects/${tasks[0].project_id}/list`)
|
||||
cy.visit(`/projects/${tasks[0].project_id}/1`)
|
||||
cy.get('.task-add textarea')
|
||||
.type(newTaskTitle+'{enter}')
|
||||
cy.visit('/')
|
||||
|
|
|
@ -12,6 +12,7 @@ import {BucketFactory} from '../../factories/bucket'
|
|||
|
||||
import {TaskAttachmentFactory} from '../../factories/task_attachments'
|
||||
import {TaskReminderFactory} from '../../factories/task_reminders'
|
||||
import {createDefaultViews} from "../project/prepareProjects";
|
||||
|
||||
function addLabelToTaskAndVerify(labelTitle: string) {
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
|
@ -53,15 +54,16 @@ describe('Task', () => {
|
|||
beforeEach(() => {
|
||||
// UserFactory.create(1)
|
||||
projects = ProjectFactory.create(1)
|
||||
const views = createDefaultViews(projects[0].id)
|
||||
buckets = BucketFactory.create(1, {
|
||||
project_id: projects[0].id,
|
||||
project_view_id: views[3].id,
|
||||
})
|
||||
TaskFactory.truncate()
|
||||
UserProjectFactory.truncate()
|
||||
})
|
||||
|
||||
it('Should be created new', () => {
|
||||
cy.visit('/projects/1/list')
|
||||
cy.visit('/projects/1/1')
|
||||
cy.get('.input[placeholder="Add a new task…"')
|
||||
.type('New Task')
|
||||
cy.get('.button')
|
||||
|
@ -75,7 +77,7 @@ describe('Task', () => {
|
|||
it('Inserts new tasks at the top of the project', () => {
|
||||
TaskFactory.create(1)
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.visit('/projects/1/1')
|
||||
cy.get('.project-is-empty-notice')
|
||||
.should('not.exist')
|
||||
cy.get('.input[placeholder="Add a new task…"')
|
||||
|
@ -93,7 +95,7 @@ describe('Task', () => {
|
|||
it('Marks a task as done', () => {
|
||||
TaskFactory.create(1)
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.visit('/projects/1/1')
|
||||
cy.get('.tasks .task .fancycheckbox')
|
||||
.first()
|
||||
.click()
|
||||
|
@ -104,7 +106,7 @@ describe('Task', () => {
|
|||
it('Can add a task to favorites', () => {
|
||||
TaskFactory.create(1)
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.visit('/projects/1/1')
|
||||
cy.get('.tasks .task .favorite')
|
||||
.first()
|
||||
.click()
|
||||
|
@ -113,12 +115,12 @@ describe('Task', () => {
|
|||
})
|
||||
|
||||
it('Should show a task description icon if the task has a description', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/views/*/tasks**').as('loadTasks')
|
||||
TaskFactory.create(1, {
|
||||
description: 'Lorem Ipsum',
|
||||
})
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.visit('/projects/1/1')
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.tasks .task .project-task-icon')
|
||||
|
@ -126,12 +128,12 @@ describe('Task', () => {
|
|||
})
|
||||
|
||||
it('Should not show a task description icon if the task has an empty description', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/views/*/tasks**').as('loadTasks')
|
||||
TaskFactory.create(1, {
|
||||
description: '',
|
||||
})
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.visit('/projects/1/1')
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.tasks .task .project-task-icon')
|
||||
|
@ -139,12 +141,12 @@ describe('Task', () => {
|
|||
})
|
||||
|
||||
it('Should not show a task description icon if the task has a description containing only an empty p tag', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/views/*/tasks**').as('loadTasks')
|
||||
TaskFactory.create(1, {
|
||||
description: '<p></p>',
|
||||
})
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.visit('/projects/1/1')
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.tasks .task .project-task-icon')
|
||||
|
@ -314,8 +316,9 @@ describe('Task', () => {
|
|||
|
||||
it('Can move a task to another project', () => {
|
||||
const projects = ProjectFactory.create(2)
|
||||
const views = createDefaultViews(projects[0].id)
|
||||
BucketFactory.create(2, {
|
||||
project_id: '{increment}',
|
||||
project_view_id: views[3].id,
|
||||
})
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
|
@ -464,12 +467,11 @@ describe('Task', () => {
|
|||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
project_id: projects[0].id,
|
||||
bucket_id: buckets[0].id,
|
||||
})
|
||||
const labels = LabelFactory.create(1)
|
||||
LabelTaskFactory.truncate()
|
||||
|
||||
cy.visit(`/projects/${projects[0].id}/kanban`)
|
||||
cy.visit(`/projects/${projects[0].id}/4`)
|
||||
|
||||
cy.get('.bucket .task')
|
||||
.contains(tasks[0].title)
|
||||
|
@ -831,12 +833,11 @@ describe('Task', () => {
|
|||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
project_id: projects[0].id,
|
||||
bucket_id: buckets[0].id,
|
||||
})
|
||||
const labels = LabelFactory.create(1)
|
||||
LabelTaskFactory.truncate()
|
||||
|
||||
cy.visit(`/projects/${projects[0].id}/kanban`)
|
||||
cy.visit(`/projects/${projects[0].id}/4`)
|
||||
|
||||
cy.get('.bucket .task')
|
||||
.contains(tasks[0].title)
|
||||
|
|
|
@ -10,7 +10,7 @@ export class BucketFactory extends Factory {
|
|||
return {
|
||||
id: '{increment}',
|
||||
title: faker.lorem.words(3),
|
||||
project_id: 1,
|
||||
project_view_id: '{increment}',
|
||||
created_by_id: 1,
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString(),
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import {Factory} from '../support/factory'
|
||||
import {faker} from '@faker-js/faker'
|
||||
|
||||
export class ProjectViewFactory extends Factory {
|
||||
static table = 'project_views'
|
||||
|
||||
static factory() {
|
||||
const now = new Date()
|
||||
|
||||
return {
|
||||
id: '{increment}',
|
||||
title: faker.lorem.words(3),
|
||||
project_id: '{increment}',
|
||||
view_kind: 0,
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,6 @@ export class TaskFactory extends Factory {
|
|||
project_id: 1,
|
||||
created_by_id: 1,
|
||||
index: '{increment}',
|
||||
position: '{increment}',
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import {Factory} from '../support/factory'
|
||||
|
||||
export class TaskBucketFactory extends Factory {
|
||||
static table = 'task_buckets'
|
||||
|
||||
static factory() {
|
||||
return {
|
||||
task_id: '{increment}',
|
||||
bucket_id: '{increment}',
|
||||
project_view_id: '{increment}',
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
description = "Vikunja frontend dev environment";
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
||||
in {
|
||||
defaultPackage.x86_64-linux =
|
||||
pkgs.mkShell { buildInputs = [ pkgs.nodePackages.pnpm pkgs.cypress pkgs.git-cliff ]; };
|
||||
};
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
"homepage": "https://vikunja.io/",
|
||||
"funding": "https://opencollective.com/vikunja",
|
||||
"packageManager": "pnpm@8.15.4",
|
||||
"packageManager": "pnpm@8.15.6",
|
||||
"keywords": [
|
||||
"todo",
|
||||
"productivity",
|
||||
|
@ -50,16 +50,16 @@
|
|||
"story:preview": "histoire preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "6.5.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.5.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.5.1",
|
||||
"@fortawesome/fontawesome-svg-core": "6.5.2",
|
||||
"@fortawesome/free-regular-svg-icons": "6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "6.5.2",
|
||||
"@fortawesome/vue-fontawesome": "3.0.6",
|
||||
"@github/hotkey": "3.1.0",
|
||||
"@infectoone/vue-ganttastic": "2.2.0",
|
||||
"@infectoone/vue-ganttastic": "2.3.2",
|
||||
"@intlify/unplugin-vue-i18n": "3.0.1",
|
||||
"@kyvg/vue3-notification": "3.2.0",
|
||||
"@sentry/tracing": "7.106.0",
|
||||
"@sentry/vue": "7.106.0",
|
||||
"@kyvg/vue3-notification": "3.2.1",
|
||||
"@sentry/tracing": "7.109.0",
|
||||
"@sentry/vue": "7.109.0",
|
||||
"@tiptap/core": "2.2.4",
|
||||
"@tiptap/extension-blockquote": "2.2.4",
|
||||
"@tiptap/extension-bold": "2.2.4",
|
||||
|
@ -97,13 +97,13 @@
|
|||
"@types/lodash.clonedeep": "4.5.9",
|
||||
"@vueuse/core": "10.9.0",
|
||||
"@vueuse/router": "10.9.0",
|
||||
"axios": "1.6.7",
|
||||
"axios": "1.6.8",
|
||||
"blurhash": "2.0.5",
|
||||
"bulma-css-variables": "0.9.33",
|
||||
"camel-case": "4.1.2",
|
||||
"date-fns": "3.3.1",
|
||||
"date-fns": "3.6.0",
|
||||
"dayjs": "1.11.10",
|
||||
"dompurify": "3.0.9",
|
||||
"dompurify": "3.0.11",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"flatpickr": "4.6.13",
|
||||
"flexsearch": "0.7.31",
|
||||
|
@ -117,12 +117,13 @@
|
|||
"snake-case": "3.0.4",
|
||||
"sortablejs": "1.15.2",
|
||||
"tippy.js": "6.3.7",
|
||||
"ufo": "1.4.0",
|
||||
"ufo": "1.5.3",
|
||||
"vue": "3.4.21",
|
||||
"vue-advanced-cropper": "2.8.8",
|
||||
"vue-flatpickr-component": "11.0.5",
|
||||
"vue-i18n": "9.10.1",
|
||||
"vue-i18n": "9.10.2",
|
||||
"vue-router": "4.3.0",
|
||||
"vuemoji-picker": "0.2.1",
|
||||
"workbox-precaching": "7.0.0",
|
||||
"zhyswan-vuedraggable": "4.1.3"
|
||||
},
|
||||
|
@ -131,60 +132,61 @@
|
|||
"@cypress/vite-dev-server": "5.0.7",
|
||||
"@cypress/vue": "6.0.0",
|
||||
"@faker-js/faker": "8.4.1",
|
||||
"@histoire/plugin-screenshot": "0.17.8",
|
||||
"@histoire/plugin-vue": "0.17.12",
|
||||
"@rushstack/eslint-patch": "1.7.2",
|
||||
"@tsconfig/node18": "18.2.2",
|
||||
"@histoire/plugin-screenshot": "0.17.15",
|
||||
"@histoire/plugin-vue": "0.17.15",
|
||||
"@rushstack/eslint-patch": "1.10.1",
|
||||
"@tsconfig/node18": "18.2.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.debounce": "4.0.9",
|
||||
"@types/marked": "5.0.2",
|
||||
"@types/node": "20.11.25",
|
||||
"@types/node": "20.12.5",
|
||||
"@types/postcss-preset-env": "7.7.0",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "7.1.1",
|
||||
"@typescript-eslint/parser": "7.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "7.5.0",
|
||||
"@typescript-eslint/parser": "7.5.0",
|
||||
"@vitejs/plugin-legacy": "5.3.2",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"@vue/eslint-config-typescript": "13.0.0",
|
||||
"@vue/test-utils": "2.4.4",
|
||||
"@vue/test-utils": "2.4.5",
|
||||
"@vue/tsconfig": "0.5.1",
|
||||
"autoprefixer": "10.4.18",
|
||||
"autoprefixer": "10.4.19",
|
||||
"browserslist": "4.23.0",
|
||||
"caniuse-lite": "1.0.30001596",
|
||||
"css-has-pseudo": "6.0.2",
|
||||
"caniuse-lite": "1.0.30001607",
|
||||
"css-has-pseudo": "6.0.3",
|
||||
"csstype": "3.1.3",
|
||||
"cypress": "13.6.6",
|
||||
"esbuild": "0.20.1",
|
||||
"cypress": "13.7.2",
|
||||
"esbuild": "0.20.2",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-vue": "9.22.0",
|
||||
"happy-dom": "13.7.1",
|
||||
"histoire": "0.17.9",
|
||||
"postcss": "8.4.35",
|
||||
"eslint-plugin-vue": "9.24.0",
|
||||
"happy-dom": "14.7.1",
|
||||
"histoire": "0.17.15",
|
||||
"postcss": "8.4.38",
|
||||
"postcss-easing-gradients": "3.0.1",
|
||||
"postcss-easings": "4.0.0",
|
||||
"postcss-focus-within": "8.0.1",
|
||||
"postcss-preset-env": "9.5.0",
|
||||
"rollup": "4.12.1",
|
||||
"postcss-preset-env": "9.5.4",
|
||||
"rollup": "4.14.1",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"sass": "1.71.1",
|
||||
"sass": "1.74.1",
|
||||
"start-server-and-test": "2.0.3",
|
||||
"typescript": "5.4.2",
|
||||
"vite": "5.1.5",
|
||||
"typescript": "5.4.4",
|
||||
"vite": "5.2.8",
|
||||
"vite-plugin-inject-preload": "1.3.3",
|
||||
"vite-plugin-pwa": "0.19.2",
|
||||
"vite-plugin-pwa": "0.19.8",
|
||||
"vite-plugin-sentry": "1.4.0",
|
||||
"vite-svg-loader": "5.1.0",
|
||||
"vitest": "1.3.1",
|
||||
"vue-tsc": "2.0.6",
|
||||
"vitest": "1.4.0",
|
||||
"vue-tsc": "2.0.11",
|
||||
"wait-on": "7.2.0",
|
||||
"workbox-cli": "7.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"flexsearch@0.7.31": "patches/flexsearch@0.7.31.patch"
|
||||
"flexsearch@0.7.31": "patches/flexsearch@0.7.31.patch",
|
||||
"@github/hotkey@3.1.0": "patches/@github__hotkey@3.1.0.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
diff --git a/dist/index.js b/dist/index.js
|
||||
index b6e6e0a6864cb00bc085b8d4503a705cb3bc8404..0466ef46406b0df41c8d0bb9a5bac9eabf4a50de 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -368,10 +368,12 @@ const sequenceTracker = new SequenceTracker({
|
||||
function keyDownHandler(event) {
|
||||
if (event.defaultPrevented)
|
||||
return;
|
||||
- if (!(event.target instanceof Node))
|
||||
+ const target = event.explicitOriginalTarget || event.target;
|
||||
+ if (target.shadowRoot)
|
||||
return;
|
||||
- if (isFormField(event.target)) {
|
||||
- const target = event.target;
|
||||
+ if (!(target instanceof Node))
|
||||
+ return;
|
||||
+ if (isFormField(target)) {
|
||||
if (!target.id)
|
||||
return;
|
||||
if (!target.ownerDocument.querySelector(`[data-hotkey-scope="${target.id}"]`))
|
||||
@@ -385,7 +387,6 @@ function keyDownHandler(event) {
|
||||
sequenceTracker.registerKeypress(event);
|
||||
currentTriePosition = newTriePosition;
|
||||
if (newTriePosition instanceof Leaf) {
|
||||
- const target = event.target;
|
||||
let shouldFire = false;
|
||||
let elementToFire;
|
||||
const formField = isFormField(target);
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -47,7 +47,7 @@ import AddToHomeScreen from '@/components/home/AddToHomeScreen.vue'
|
|||
import DemoMode from '@/components/home/DemoMode.vue'
|
||||
|
||||
const importAccountDeleteService = () => import('@/services/accountDelete')
|
||||
const importMessage = () => import('@/message')
|
||||
import {success} from '@/message'
|
||||
|
||||
const baseStore = useBaseStore()
|
||||
const authStore = useAuthStore()
|
||||
|
@ -69,11 +69,9 @@ watch(accountDeletionConfirm, async (accountDeletionConfirm) => {
|
|||
return
|
||||
}
|
||||
|
||||
const messageP = importMessage()
|
||||
const AccountDeleteService = (await importAccountDeleteService()).default
|
||||
const accountDeletionService = new AccountDeleteService()
|
||||
await accountDeletionService.confirm(accountDeletionConfirm)
|
||||
const {success} = await messageP
|
||||
success({message: t('user.deletion.confirmSuccess')})
|
||||
authStore.refreshUserInfo()
|
||||
}, { immediate: true })
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
:project="project"
|
||||
:is-loading="projectUpdating[project.id]"
|
||||
:can-collapse="canCollapse"
|
||||
:level="level"
|
||||
:data-project-id="project.id"
|
||||
/>
|
||||
</template>
|
||||
|
@ -49,7 +48,6 @@ const props = defineProps<{
|
|||
modelValue?: IProject[],
|
||||
canEditOrder: boolean,
|
||||
canCollapse?: boolean,
|
||||
level?: number,
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', projects: IProject[]): void
|
||||
|
|
|
@ -58,7 +58,6 @@
|
|||
<ProjectSettingsDropdown
|
||||
class="menu-list-dropdown"
|
||||
:project="project"
|
||||
:level="level"
|
||||
>
|
||||
<template #trigger="{toggleOpen}">
|
||||
<BaseButton
|
||||
|
@ -78,7 +77,6 @@
|
|||
:model-value="childProjects"
|
||||
:can-edit-order="true"
|
||||
:can-collapse="canCollapse"
|
||||
:level="level + 1"
|
||||
/>
|
||||
</li>
|
||||
</template>
|
||||
|
@ -101,12 +99,10 @@ const {
|
|||
project,
|
||||
isLoading,
|
||||
canCollapse,
|
||||
level = 0,
|
||||
} = defineProps<{
|
||||
project: IProject,
|
||||
isLoading?: boolean,
|
||||
canCollapse?: boolean,
|
||||
level?: number,
|
||||
}>()
|
||||
|
||||
const projectStore = useProjectStore()
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
v-slot="{ Component }"
|
||||
:route="routeWithModal"
|
||||
>
|
||||
<keep-alive :include="['project.list', 'project.gantt', 'project.table', 'project.kanban']">
|
||||
<keep-alive :include="['project.view']">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
|
|
|
@ -33,11 +33,15 @@ import {useBaseStore} from '@/stores/base'
|
|||
|
||||
import Logo from '@/components/home/Logo.vue'
|
||||
import PoweredByLink from './PoweredByLink.vue'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
||||
const baseStore = useBaseStore()
|
||||
const currentProject = computed(() => baseStore.currentProject)
|
||||
const background = computed(() => baseStore.background)
|
||||
const logoVisible = computed(() => baseStore.logoVisible)
|
||||
|
||||
const projectStore = useProjectStore()
|
||||
projectStore.loadAllProjects()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
<script setup lang="ts">
|
||||
import type {IReactionPerEntity, ReactionKind} from '@/modelTypes/IReaction'
|
||||
import {VuemojiPicker} from 'vuemoji-picker'
|
||||
import ReactionService from '@/services/reactions'
|
||||
import ReactionModel from '@/models/reaction'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import type {IUser} from '@/modelTypes/IUser'
|
||||
import {getDisplayName} from '@/models/user'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {nextTick, onBeforeUnmount, onMounted, ref} from 'vue'
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useColorScheme} from '@/composables/useColorScheme'
|
||||
|
||||
const {
|
||||
entityKind,
|
||||
entityId,
|
||||
disabled = false,
|
||||
} = defineProps<{
|
||||
entityKind: ReactionKind,
|
||||
entityId: number,
|
||||
disabled?: boolean,
|
||||
}>()
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const {t} = useI18n()
|
||||
const reactionService = new ReactionService()
|
||||
const {isDark} = useColorScheme()
|
||||
|
||||
const model = defineModel<IReactionPerEntity>()
|
||||
|
||||
async function addReaction(value: string) {
|
||||
const reaction = new ReactionModel({
|
||||
id: entityId,
|
||||
kind: entityKind,
|
||||
value,
|
||||
})
|
||||
await reactionService.create(reaction)
|
||||
showEmojiPicker.value = false
|
||||
|
||||
if (typeof model.value === 'undefined') {
|
||||
model.value = {}
|
||||
}
|
||||
|
||||
if (typeof model.value[reaction.value] === 'undefined') {
|
||||
model.value[reaction.value] = [authStore.info]
|
||||
} else {
|
||||
model.value[reaction.value].push(authStore.info)
|
||||
}
|
||||
}
|
||||
|
||||
async function removeReaction(value: string) {
|
||||
const reaction = new ReactionModel({
|
||||
id: entityId,
|
||||
kind: entityKind,
|
||||
value,
|
||||
})
|
||||
await reactionService.delete(reaction)
|
||||
showEmojiPicker.value = false
|
||||
|
||||
const userIndex = model.value[reaction.value].findIndex(u => u.id === authStore.info?.id)
|
||||
if (userIndex !== -1) {
|
||||
model.value[reaction.value].splice(userIndex, 1)
|
||||
}
|
||||
if(model.value[reaction.value].length === 0) {
|
||||
delete model.value[reaction.value]
|
||||
}
|
||||
}
|
||||
|
||||
function getReactionTooltip(users: IUser[], value: string) {
|
||||
const names = users.map(u => getDisplayName(u))
|
||||
|
||||
if (names.length === 1) {
|
||||
return t('reaction.reactedWith', {user: names[0], value})
|
||||
}
|
||||
|
||||
if (names.length > 1 && names.length < 10) {
|
||||
return t('reaction.reactedWithAnd', {
|
||||
users: names.slice(0, names.length - 1).join(', '),
|
||||
lastUser: names[names.length - 1],
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
return t('reaction.reactedWithAndMany', {
|
||||
users: names.slice(0, 10).join(', '),
|
||||
num: names.length - 10,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
const showEmojiPicker = ref(false)
|
||||
const emojiPickerRef = ref<HTMLElement | null>(null)
|
||||
|
||||
function hideEmojiPicker(e: MouseEvent) {
|
||||
if (showEmojiPicker.value) {
|
||||
closeWhenClickedOutside(e, emojiPickerRef.value.$el, () => showEmojiPicker.value = false)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => document.addEventListener('click', hideEmojiPicker))
|
||||
onBeforeUnmount(() => document.removeEventListener('click', hideEmojiPicker))
|
||||
|
||||
const emojiPickerButtonRef = ref<HTMLElement | null>(null)
|
||||
const reactionContainerRef = ref<HTMLElement | null>(null)
|
||||
const emojiPickerPosition = ref()
|
||||
|
||||
function toggleEmojiPicker() {
|
||||
if (!showEmojiPicker.value) {
|
||||
const rect = emojiPickerButtonRef.value?.$el.getBoundingClientRect()
|
||||
const container = reactionContainerRef.value?.getBoundingClientRect()
|
||||
const left = rect.left - container.left + rect.width
|
||||
|
||||
emojiPickerPosition.value = {
|
||||
left: left === 0 ? undefined : left,
|
||||
}
|
||||
}
|
||||
|
||||
nextTick(() => showEmojiPicker.value = !showEmojiPicker.value)
|
||||
}
|
||||
|
||||
function hasCurrentUserReactedWithEmoji(value: string): boolean {
|
||||
const user = model.value[value].find(u => u.id === authStore.info.id)
|
||||
return typeof user !== 'undefined'
|
||||
}
|
||||
|
||||
async function toggleReaction(value: string) {
|
||||
if (hasCurrentUserReactedWithEmoji(value)) {
|
||||
return removeReaction(value)
|
||||
}
|
||||
|
||||
return addReaction(value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="reactionContainerRef"
|
||||
class="reactions"
|
||||
>
|
||||
<BaseButton
|
||||
v-for="(users, value) in (model as IReactionPerEntity)"
|
||||
:key="'button' + value"
|
||||
v-tooltip="getReactionTooltip(users, value)"
|
||||
class="reaction-button"
|
||||
:class="{'current-user-has-reacted': hasCurrentUserReactedWithEmoji(value)}"
|
||||
:disabled
|
||||
@click="toggleReaction(value)"
|
||||
>
|
||||
{{ value }} {{ users.length }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="!disabled"
|
||||
ref="emojiPickerButtonRef"
|
||||
v-tooltip="$t('reaction.add')"
|
||||
class="reaction-button"
|
||||
@click.stop="toggleEmojiPicker"
|
||||
>
|
||||
<icon :icon="['far', 'face-laugh']" />
|
||||
</BaseButton>
|
||||
<CustomTransition name="fade">
|
||||
<VuemojiPicker
|
||||
v-if="showEmojiPicker"
|
||||
ref="emojiPickerRef"
|
||||
class="emoji-picker"
|
||||
:style="{left: emojiPickerPosition?.left + 'px'}"
|
||||
data-source="/emojis.json"
|
||||
:is-dark="isDark"
|
||||
@emojiClick="detail => addReaction(detail.unicode)"
|
||||
/>
|
||||
</CustomTransition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.reaction-button {
|
||||
margin-right: .25rem;
|
||||
margin-bottom: .25rem;
|
||||
padding: .175rem .5rem .15rem;
|
||||
border: 1px solid var(--grey-400);
|
||||
background: var(--grey-100);
|
||||
border-radius: 100px;
|
||||
font-size: .75rem;
|
||||
|
||||
&.current-user-has-reacted {
|
||||
border-color: var(--primary);
|
||||
background-color: hsla(var(--primary-h), var(--primary-s), var(--primary-light-l), 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-picker {
|
||||
position: absolute;
|
||||
z-index: 99;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
</style>
|
|
@ -13,7 +13,7 @@
|
|||
}"
|
||||
>
|
||||
<template v-if="icon">
|
||||
<icon
|
||||
<icon
|
||||
v-if="showIconOnly"
|
||||
:icon="icon"
|
||||
:style="{'color': iconColor !== '' ? iconColor : undefined}"
|
||||
|
@ -22,7 +22,7 @@
|
|||
v-else
|
||||
class="icon is-small"
|
||||
>
|
||||
<icon
|
||||
<icon
|
||||
:icon="icon"
|
||||
:style="{'color': iconColor !== '' ? iconColor : undefined}"
|
||||
/>
|
||||
|
@ -34,20 +34,20 @@
|
|||
|
||||
<script lang="ts">
|
||||
const BUTTON_TYPES_MAP = {
|
||||
primary: 'is-primary',
|
||||
secondary: 'is-outlined',
|
||||
tertiary: 'is-text is-inverted underline-none',
|
||||
primary: 'is-primary',
|
||||
secondary: 'is-outlined',
|
||||
tertiary: 'is-text is-inverted underline-none',
|
||||
} as const
|
||||
|
||||
export type ButtonTypes = keyof typeof BUTTON_TYPES_MAP
|
||||
|
||||
export default { name: 'XButton' }
|
||||
export default {name: 'XButton'}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, useSlots} from 'vue'
|
||||
import BaseButton, {type BaseButtonProps} from '@/components/base/BaseButton.vue'
|
||||
import type { IconProp } from '@fortawesome/fontawesome-svg-core'
|
||||
import type {IconProp} from '@fortawesome/fontawesome-svg-core'
|
||||
|
||||
// extending the props of the BaseButton
|
||||
export interface ButtonProps extends /* @vue-ignore */ BaseButtonProps {
|
||||
|
@ -76,37 +76,38 @@ const showIconOnly = computed(() => icon !== '' && typeof slots.default === 'und
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.button {
|
||||
transition: all $transition;
|
||||
border: 0;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
height: auto;
|
||||
min-height: $button-height;
|
||||
box-shadow: var(--shadow-sm);
|
||||
display: inline-flex;
|
||||
white-space: var(--button-white-space);
|
||||
transition: all $transition;
|
||||
border: 0;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
height: auto;
|
||||
min-height: $button-height;
|
||||
box-shadow: var(--shadow-sm);
|
||||
display: inline-flex;
|
||||
white-space: var(--button-white-space);
|
||||
line-height: 1;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
&.fullheight {
|
||||
padding-right: 7px;
|
||||
height: 100%;
|
||||
}
|
||||
&.fullheight {
|
||||
padding-right: 7px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.is-active,
|
||||
&.is-focused,
|
||||
&:active,
|
||||
&:focus,
|
||||
&:focus:not(:active) {
|
||||
box-shadow: var(--shadow-xs) !important;
|
||||
}
|
||||
&.is-active,
|
||||
&.is-focused,
|
||||
&:active,
|
||||
&:focus,
|
||||
&:focus:not(:active) {
|
||||
box-shadow: var(--shadow-xs) !important;
|
||||
}
|
||||
|
||||
&.is-primary.is-outlined:hover {
|
||||
color: var(--white);
|
||||
}
|
||||
&.is-primary.is-outlined:hover {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
|
||||
.is-small {
|
||||
|
@ -114,6 +115,6 @@ const showIconOnly = computed(() => icon !== '' && typeof slots.default === 'und
|
|||
}
|
||||
|
||||
.underline-none {
|
||||
text-decoration: none !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
</style>
|
|
@ -63,6 +63,7 @@
|
|||
|
||||
<div class="flatpickr-container">
|
||||
<flat-pickr
|
||||
ref="flatPickrRef"
|
||||
v-model="flatPickrDate"
|
||||
:config="flatPickerConfig"
|
||||
/>
|
||||
|
@ -70,7 +71,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, toRef, watch, computed, type PropType} from 'vue'
|
||||
import {computed, onBeforeUnmount, onMounted, type PropType, ref, toRef, watch} from 'vue'
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
import 'flatpickr/dist/flatpickr.css'
|
||||
|
||||
|
@ -81,7 +82,7 @@ import {calculateDayInterval} from '@/helpers/time/calculateDayInterval'
|
|||
import {calculateNearestHours} from '@/helpers/time/calculateNearestHours'
|
||||
import {createDateFromString} from '@/helpers/time/createDateFromString'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
|
||||
import {getFlatpickrLanguage} from '@/helpers/flatpickrLanguage'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
|
@ -105,6 +106,7 @@ watch(
|
|||
{immediate: true},
|
||||
)
|
||||
|
||||
const flatPickrRef = ref<HTMLElement | null>(null)
|
||||
const flatPickerConfig = computed(() => ({
|
||||
altFormat: t('date.altFormatLong'),
|
||||
altInput: true,
|
||||
|
@ -142,6 +144,41 @@ const flatPickrDate = computed({
|
|||
},
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
const inputs = flatPickrRef.value?.$el.parentNode.querySelectorAll('.numInputWrapper > input.numInput')
|
||||
inputs.forEach(i => {
|
||||
i.addEventListener('input', handleFlatpickrInput)
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const inputs = flatPickrRef.value?.$el.parentNode.querySelectorAll('.numInputWrapper > input.numInput')
|
||||
inputs.forEach(i => {
|
||||
i.removeEventListener('input', handleFlatpickrInput)
|
||||
})
|
||||
})
|
||||
|
||||
// Flatpickr only returns a change event when the value in the input it's referring to changes.
|
||||
// That means it will usually only trigger when the focus is moved out of the input field.
|
||||
// This is fine most of the time. However, since we're displaying flatpickr in a popup,
|
||||
// the whole html dom instance might get destroyed, before the change event had a
|
||||
// chance to fire. In that case, it would not update the date value. To fix
|
||||
// this, we're now listening on every change and bubble them up as soon
|
||||
// as they happen.
|
||||
function handleFlatpickrInput(e) {
|
||||
const newDate = new Date(date?.value || 'now')
|
||||
if (e.target.classList.contains('flatpickr-minute')) {
|
||||
newDate.setMinutes(e.target.value)
|
||||
}
|
||||
if (e.target.classList.contains('flatpickr-hour')) {
|
||||
newDate.setHours(e.target.value)
|
||||
}
|
||||
if (e.target.classList.contains('cur-year')) {
|
||||
newDate.setFullYear(e.target.value)
|
||||
}
|
||||
flatPickrDate.value = newDate
|
||||
}
|
||||
|
||||
|
||||
function setDateValue(dateString: string | Date | null) {
|
||||
if (dateString === null) {
|
||||
|
|
|
@ -139,7 +139,7 @@
|
|||
<BaseButton
|
||||
v-tooltip="$t('input.editor.image')"
|
||||
class="editor-toolbar__button"
|
||||
@click="openImagePicker"
|
||||
@click="e => emit('imageUploadClicked', e)"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon icon="fa-image" />
|
||||
|
@ -347,16 +347,14 @@ const {
|
|||
editor: Editor,
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['imageUploadClicked'])
|
||||
|
||||
const tableMode = ref(false)
|
||||
|
||||
function toggleTableMode() {
|
||||
tableMode.value = !tableMode.value
|
||||
}
|
||||
|
||||
function openImagePicker() {
|
||||
document.getElementById('tiptap__image-upload').click()
|
||||
}
|
||||
|
||||
function setLink(event) {
|
||||
setLinkInEditor(event.target.getBoundingClientRect(), editor)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<EditorToolbar
|
||||
v-if="editor && isEditing"
|
||||
:editor="editor"
|
||||
:upload-callback="uploadCallback"
|
||||
@imageUploadClicked="triggerImageInput"
|
||||
/>
|
||||
<BubbleMenu
|
||||
v-if="editor && isEditing"
|
||||
|
@ -374,7 +374,7 @@ const editor = useEditor({
|
|||
Typography,
|
||||
Underline,
|
||||
Link.configure({
|
||||
openOnClick: true,
|
||||
openOnClick: false,
|
||||
validate: (href: string) => /^https?:\/\//.test(href),
|
||||
}),
|
||||
Table.configure({
|
||||
|
@ -489,6 +489,15 @@ function uploadAndInsertFiles(files: File[] | FileList) {
|
|||
})
|
||||
}
|
||||
|
||||
function triggerImageInput(event) {
|
||||
if (typeof uploadCallback !== 'undefined') {
|
||||
uploadInputRef.value?.click()
|
||||
return
|
||||
}
|
||||
|
||||
addImage(event)
|
||||
}
|
||||
|
||||
async function addImage(event) {
|
||||
|
||||
if (typeof uploadCallback !== 'undefined') {
|
||||
|
@ -522,16 +531,20 @@ onMounted(async () => {
|
|||
|
||||
await nextTick()
|
||||
|
||||
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
|
||||
input?.addEventListener('paste', handleImagePaste)
|
||||
if (typeof uploadCallback !== 'undefined') {
|
||||
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
|
||||
input?.addEventListener('paste', handleImagePaste)
|
||||
}
|
||||
|
||||
setModeAndValue(modelValue)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
nextTick(() => {
|
||||
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
|
||||
input?.removeEventListener('paste', handleImagePaste)
|
||||
if (typeof uploadCallback !== 'undefined') {
|
||||
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
|
||||
input?.removeEventListener('paste', handleImagePaste)
|
||||
}
|
||||
})
|
||||
if (editShortcut !== '') {
|
||||
document.removeEventListener('keydown', setFocusToEditor)
|
||||
|
@ -558,6 +571,10 @@ function handleImagePaste(event) {
|
|||
|
||||
// See https://github.com/github/hotkey/discussions/85#discussioncomment-5214660
|
||||
function setFocusToEditor(event) {
|
||||
if (event.target.shadowRoot) {
|
||||
return
|
||||
}
|
||||
|
||||
const hotkeyString = eventToHotkeyString(event)
|
||||
if (!hotkeyString) return
|
||||
if (hotkeyString !== editShortcut ||
|
||||
|
@ -596,7 +613,7 @@ watch(
|
|||
() => isEditing.value,
|
||||
async editing => {
|
||||
await nextTick()
|
||||
|
||||
|
||||
let checkboxes = tiptapInstanceRef.value?.querySelectorAll('[data-checked]')
|
||||
if (typeof checkboxes === 'undefined' || checkboxes.length === 0) {
|
||||
// For some reason, this works when we check a second time.
|
||||
|
|
|
@ -87,7 +87,7 @@ import {
|
|||
faStar,
|
||||
faSun,
|
||||
faTimesCircle,
|
||||
faCircleQuestion,
|
||||
faCircleQuestion, faFaceLaugh,
|
||||
} from '@fortawesome/free-regular-svg-icons'
|
||||
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
|
||||
|
||||
|
@ -186,6 +186,7 @@ library.add(faXmarksLines)
|
|||
library.add(faFont)
|
||||
library.add(faRulerHorizontal)
|
||||
library.add(faUnderline)
|
||||
library.add(faFaceLaugh)
|
||||
|
||||
// overwriting the wrong types
|
||||
export default FontAwesomeIcon as unknown as FontAwesomeIconFixedTypes
|
|
@ -62,7 +62,7 @@ export const KEYBOARD_SHORTCUTS : ShortcutGroup[] = [
|
|||
},
|
||||
{
|
||||
title: 'project.kanban.title',
|
||||
available: (route) => route.name === 'project.kanban',
|
||||
available: (route) => route.name === 'project.view',
|
||||
shortcuts: [
|
||||
{
|
||||
title: 'keyboardShortcuts.task.done',
|
||||
|
|
|
@ -188,12 +188,6 @@ $modal-width: 1024px;
|
|||
.info {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,44 +6,23 @@
|
|||
<h1 class="project-title-print">
|
||||
{{ getProjectTitle(currentProject) }}
|
||||
</h1>
|
||||
|
||||
<div class="switch-view-container d-print-none">
|
||||
<div class="switch-view">
|
||||
|
||||
<div
|
||||
class="switch-view-container d-print-none"
|
||||
:class="{'is-justify-content-flex-end': views.length === 1}"
|
||||
>
|
||||
<div
|
||||
v-if="views.length > 1"
|
||||
class="switch-view"
|
||||
>
|
||||
<BaseButton
|
||||
v-shortcut="'g l'"
|
||||
:title="$t('keyboardShortcuts.project.switchToListView')"
|
||||
v-for="v in views"
|
||||
:key="v.id"
|
||||
class="switch-view-button"
|
||||
:class="{'is-active': viewName === 'project'}"
|
||||
:to="{ name: 'project.list', params: { projectId } }"
|
||||
:class="{'is-active': v.id === viewId}"
|
||||
:to="{ name: 'project.view', params: { projectId, viewId: v.id } }"
|
||||
>
|
||||
{{ $t('project.list.title') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-shortcut="'g g'"
|
||||
:title="$t('keyboardShortcuts.project.switchToGanttView')"
|
||||
class="switch-view-button"
|
||||
:class="{'is-active': viewName === 'gantt'}"
|
||||
:to="{ name: 'project.gantt', params: { projectId } }"
|
||||
>
|
||||
{{ $t('project.gantt.title') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-shortcut="'g t'"
|
||||
:title="$t('keyboardShortcuts.project.switchToTableView')"
|
||||
class="switch-view-button"
|
||||
:class="{'is-active': viewName === 'table'}"
|
||||
:to="{ name: 'project.table', params: { projectId } }"
|
||||
>
|
||||
{{ $t('project.table.title') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-shortcut="'g k'"
|
||||
:title="$t('keyboardShortcuts.project.switchToKanbanView')"
|
||||
class="switch-view-button"
|
||||
:class="{'is-active': viewName === 'kanban'}"
|
||||
:to="{ name: 'project.kanban', params: { projectId } }"
|
||||
>
|
||||
{{ $t('project.kanban.title') }}
|
||||
{{ getViewTitle(v) }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
<slot name="header" />
|
||||
|
@ -63,7 +42,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, watch} from 'vue'
|
||||
import {computed, ref, watch} from 'vue'
|
||||
import {useRoute} from 'vue-router'
|
||||
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
@ -79,26 +58,27 @@ import {useTitle} from '@/composables/useTitle'
|
|||
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
import type {IProjectView} from '@/modelTypes/IProjectView'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
const props = defineProps({
|
||||
projectId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
viewName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const {
|
||||
projectId,
|
||||
viewId,
|
||||
} = defineProps<{
|
||||
projectId: IProject['id'],
|
||||
viewId: IProjectView['id'],
|
||||
}>()
|
||||
|
||||
const route = useRoute()
|
||||
const {t} = useI18n()
|
||||
|
||||
const baseStore = useBaseStore()
|
||||
const projectStore = useProjectStore()
|
||||
const projectService = ref(new ProjectService())
|
||||
const loadedProjectId = ref(0)
|
||||
|
||||
const currentProject = computed(() => {
|
||||
const currentProject = computed<IProject>(() => {
|
||||
return typeof baseStore.currentProject === 'undefined' ? {
|
||||
id: 0,
|
||||
title: '',
|
||||
|
@ -108,13 +88,15 @@ const currentProject = computed(() => {
|
|||
})
|
||||
useTitle(() => currentProject.value?.id ? getProjectTitle(currentProject.value) : '')
|
||||
|
||||
const views = computed(() => currentProject.value?.views)
|
||||
|
||||
// watchEffect would be called every time the prop would get a value assigned, even if that value was the same as before.
|
||||
// This resulted in loading and setting the project multiple times, even when navigating away from it.
|
||||
// This caused wired bugs where the project background would be set on the home page but only right after setting a new
|
||||
// project background and then navigating to home. It also highlighted the project in the menu and didn't allow changing any
|
||||
// of it, most likely due to the rights not being properly populated.
|
||||
watch(
|
||||
() => props.projectId,
|
||||
() => projectId,
|
||||
// loadProject
|
||||
async (projectIdToLoad: number) => {
|
||||
const projectData = {id: projectIdToLoad}
|
||||
|
@ -130,11 +112,11 @@ watch(
|
|||
)
|
||||
&& typeof currentProject.value !== 'undefined' && currentProject.value.maxRight !== null
|
||||
) {
|
||||
loadedProjectId.value = props.projectId
|
||||
loadedProjectId.value = projectId
|
||||
return
|
||||
}
|
||||
|
||||
console.debug(`Loading project, props.viewName = ${props.viewName}, $route.params =`, route.params, `, loadedProjectId = ${loadedProjectId.value}, currentProject = `, currentProject.value)
|
||||
console.debug('Loading project, $route.params =', route.params, `, loadedProjectId = ${loadedProjectId.value}, currentProject = `, currentProject.value)
|
||||
|
||||
// Set the current project to the one we're about to load so that the title is already shown at the top
|
||||
loadedProjectId.value = 0
|
||||
|
@ -149,31 +131,50 @@ watch(
|
|||
const loadedProject = await projectService.value.get(project)
|
||||
baseStore.handleSetCurrentProject({project: loadedProject})
|
||||
} finally {
|
||||
loadedProjectId.value = props.projectId
|
||||
loadedProjectId.value = projectId
|
||||
}
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
function getViewTitle(view: IProjectView) {
|
||||
switch (view.title) {
|
||||
case 'List':
|
||||
return t('project.list.title')
|
||||
case 'Gantt':
|
||||
return t('project.gantt.title')
|
||||
case 'Table':
|
||||
return t('project.table.title')
|
||||
case 'Kanban':
|
||||
return t('project.kanban.title')
|
||||
}
|
||||
|
||||
return view.title
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.switch-view-container {
|
||||
@media screen and (max-width: $tablet) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
min-height: $switch-view-height;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.switch-view {
|
||||
background: var(--white);
|
||||
display: inline-flex;
|
||||
border-radius: $radius;
|
||||
font-size: .75rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
height: $switch-view-height;
|
||||
margin: 0 auto 1rem;
|
||||
padding: .5rem;
|
||||
background: var(--white);
|
||||
display: inline-flex;
|
||||
border-radius: $radius;
|
||||
font-size: .75rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.switch-view-button {
|
||||
|
@ -201,7 +202,7 @@ watch(
|
|||
|
||||
// FIXME: this should be in notification and set via a prop
|
||||
.is-archived .notification.is-warning {
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.project-title-print {
|
||||
|
@ -209,7 +210,7 @@ watch(
|
|||
font-size: 1.75rem;
|
||||
text-align: center;
|
||||
margin-bottom: .5rem;
|
||||
|
||||
|
||||
@media print {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -10,21 +10,27 @@ import User from '@/components/misc/user.vue'
|
|||
import ProjectUserService from '@/services/projectUsers'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import {
|
||||
DATE_FIELDS,
|
||||
ASSIGNEE_FIELDS,
|
||||
AUTOCOMPLETE_FIELDS,
|
||||
AVAILABLE_FILTER_FIELDS,
|
||||
DATE_FIELDS,
|
||||
FILTER_JOIN_OPERATOR,
|
||||
FILTER_OPERATORS,
|
||||
FILTER_OPERATORS_REGEX, LABEL_FIELDS,
|
||||
FILTER_OPERATORS_REGEX,
|
||||
getFilterFieldRegexPattern,
|
||||
LABEL_FIELDS,
|
||||
} from '@/helpers/filters'
|
||||
import {useDebounceFn} from '@vueuse/core'
|
||||
import {createRandomID} from '@/helpers/randomId'
|
||||
|
||||
const {
|
||||
modelValue,
|
||||
projectId,
|
||||
inputLabel = undefined,
|
||||
} = defineProps<{
|
||||
modelValue: string,
|
||||
projectId?: number,
|
||||
inputLabel?: string,
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'blur'])
|
||||
|
@ -35,6 +41,8 @@ const {
|
|||
height,
|
||||
} = useAutoHeightTextarea(filterQuery)
|
||||
|
||||
const id = ref(createRandomID())
|
||||
|
||||
watch(
|
||||
() => modelValue,
|
||||
() => {
|
||||
|
@ -83,7 +91,15 @@ const highlightedFilterQuery = computed(() => {
|
|||
value = ''
|
||||
}
|
||||
|
||||
return `${o}${spacesBefore}${token}${spacesAfter}<button class="is-primary filter-query__date_value" data-position="${position}">${value}</button><span class="filter-query__date_value_placeholder">${value}</span>`
|
||||
let endPadding = ''
|
||||
if(value.endsWith(' ')) {
|
||||
const fullLength = value.length
|
||||
value = value.trimEnd()
|
||||
const numberOfRemovedSpaces = fullLength - value.length
|
||||
endPadding = endPadding.padEnd(numberOfRemovedSpaces, ' ')
|
||||
}
|
||||
|
||||
return `${o}${spacesBefore}${token}${spacesAfter}<button class="is-primary filter-query__date_value" data-position="${position}">${value}</button><span class="filter-query__date_value_placeholder">${value}</span>${endPadding}`
|
||||
})
|
||||
})
|
||||
ASSIGNEE_FIELDS
|
||||
|
@ -104,15 +120,26 @@ const highlightedFilterQuery = computed(() => {
|
|||
})
|
||||
LABEL_FIELDS
|
||||
.forEach(f => {
|
||||
const pattern = new RegExp(f + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*([\'"]?)([^\'"\\s]+\\1?)?', 'ig')
|
||||
highlighted = highlighted.replaceAll(pattern, (match, token, start, value) => {
|
||||
const pattern = getFilterFieldRegexPattern(f)
|
||||
highlighted = highlighted.replaceAll(pattern, (match, prefix, operator, space, value) => {
|
||||
|
||||
if (typeof value === 'undefined') {
|
||||
value = ''
|
||||
}
|
||||
|
||||
const label = labelStore.getLabelsByExactTitles([value])[0] || undefined
|
||||
let labelTitles = [value.trim()]
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
labelTitles = value.split(',').map(v => v.trim())
|
||||
}
|
||||
|
||||
return `${f} ${token} <span class="filter-query__label_value" style="background-color: ${label?.hexColor}; color: ${label?.textColor}">${label?.title ?? value}<span>`
|
||||
const labelsHtml: string[] = []
|
||||
labelTitles.forEach(t => {
|
||||
const label = labelStore.getLabelByExactTitle(t) || undefined
|
||||
labelsHtml.push(`<span class="filter-query__label_value" style="background-color: ${label?.hexColor}; color: ${label?.textColor}">${label?.title ?? t}</span>`)
|
||||
})
|
||||
|
||||
const endSpace = value.endsWith(' ') ? ' ' : ''
|
||||
return `${f} ${operator} ${labelsHtml.join(', ')}${endSpace}`
|
||||
})
|
||||
})
|
||||
FILTER_OPERATORS
|
||||
|
@ -175,6 +202,7 @@ const projectStore = useProjectStore()
|
|||
function handleFieldInput() {
|
||||
const cursorPosition = filterInput.value.selectionStart
|
||||
const textUpToCursor = filterQuery.value.substring(0, cursorPosition)
|
||||
autocompleteResults.value = []
|
||||
|
||||
AUTOCOMPLETE_FIELDS.forEach(field => {
|
||||
const pattern = new RegExp('(' + field + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&|()]+\\1?)?$', 'ig')
|
||||
|
@ -184,26 +212,31 @@ 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([], keyword)
|
||||
autocompleteResults.value = labelStore.filterLabelsByQuery([], search)
|
||||
}
|
||||
if (matched.startsWith('assignee')) {
|
||||
autocompleteResultType.value = 'assignees'
|
||||
if (projectId) {
|
||||
projectUserService.getAll({projectId}, {s: keyword})
|
||||
projectUserService.getAll({projectId}, {s: search})
|
||||
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
|
||||
} else {
|
||||
userService.getAll({}, {s: keyword})
|
||||
userService.getAll({}, {s: search})
|
||||
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
|
||||
}
|
||||
}
|
||||
if (!projectId && matched.startsWith('project')) {
|
||||
autocompleteResultType.value = 'projects'
|
||||
autocompleteResults.value = projectStore.searchProject(keyword)
|
||||
autocompleteResults.value = projectStore.searchProject(search)
|
||||
}
|
||||
autocompleteMatchText.value = keyword
|
||||
autocompleteMatchPosition.value = prefix.length - 1
|
||||
autocompleteMatchPosition.value = match.index + prefix.length - 1 + keyword.replace(search, '').length
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -211,18 +244,27 @@ function handleFieldInput() {
|
|||
|
||||
function autocompleteSelect(value) {
|
||||
filterQuery.value = filterQuery.value.substring(0, autocompleteMatchPosition.value + 1) +
|
||||
(autocompleteResultType.value === 'labels'
|
||||
? value.title
|
||||
: value.username) +
|
||||
(autocompleteResultType.value === 'assignees'
|
||||
? value.username
|
||||
: value.title) +
|
||||
filterQuery.value.substring(autocompleteMatchPosition.value + autocompleteMatchText.value.length + 1)
|
||||
|
||||
autocompleteResults.value = []
|
||||
}
|
||||
|
||||
// The blur from the textarea might happen before the replacement after autocomplete select was done.
|
||||
// That caused listeners to try and replace values earlier, resulting in broken queries.
|
||||
const blurDebounced = useDebounceFn(() => emit('blur'), 500)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('filters.query.title') }}</label>
|
||||
<label
|
||||
class="label"
|
||||
:for="id"
|
||||
>
|
||||
{{ inputLabel ?? $t('filters.query.title') }}
|
||||
</label>
|
||||
<AutocompleteDropdown
|
||||
:options="autocompleteResults"
|
||||
@blur="filterInput?.blur()"
|
||||
|
@ -233,10 +275,10 @@ function autocompleteSelect(value) {
|
|||
>
|
||||
<div class="control filter-input">
|
||||
<textarea
|
||||
:id
|
||||
ref="filterInput"
|
||||
v-model="filterQuery"
|
||||
autocomplete="off"
|
||||
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
|
@ -246,7 +288,7 @@ function autocompleteSelect(value) {
|
|||
@input="handleFieldInput"
|
||||
@focus="onFocusField"
|
||||
@keydown="onKeydown"
|
||||
@blur="e => emit('blur', e)"
|
||||
@blur="blurDebounced"
|
||||
/>
|
||||
<div
|
||||
class="filter-input-highlight"
|
||||
|
@ -283,6 +325,11 @@ function autocompleteSelect(value) {
|
|||
|
||||
<style lang="scss">
|
||||
.filter-input-highlight {
|
||||
|
||||
&, button.filter-query__date_value {
|
||||
color: var(--card-color);
|
||||
}
|
||||
|
||||
span {
|
||||
&.filter-query__field {
|
||||
color: var(--code-literal);
|
||||
|
@ -333,7 +380,7 @@ function autocompleteSelect(value) {
|
|||
resize: none;
|
||||
text-fill-color: transparent;
|
||||
-webkit-text-fill-color: transparent;
|
||||
|
||||
|
||||
&::placeholder {
|
||||
text-fill-color: var(--input-placeholder-color);
|
||||
-webkit-text-fill-color: var(--input-placeholder-color);
|
||||
|
@ -345,6 +392,7 @@ function autocompleteSelect(value) {
|
|||
}
|
||||
|
||||
.filter-input-highlight {
|
||||
background: var(--white);
|
||||
height: 2.5em;
|
||||
line-height: 1.5;
|
||||
padding: .5em .75em;
|
||||
|
|
|
@ -63,6 +63,10 @@ const filteredProjects = computed(() => {
|
|||
|
||||
@media screen and (min-width: $widescreen) {
|
||||
--project-grid-columns: 5;
|
||||
|
||||
.project-grid-item:nth-child(6) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
<template>
|
||||
<x-button
|
||||
v-if="hasFilters"
|
||||
variant="secondary"
|
||||
@click="clearFilters"
|
||||
>
|
||||
{{ $t('filters.clear') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
icon="filter"
|
||||
:class="{'has-filters': hasFilters}"
|
||||
@click="() => modalOpen = true"
|
||||
>
|
||||
{{ $t('filters.title') }}
|
||||
|
@ -37,20 +31,38 @@ import {computed, ref, watch} from 'vue'
|
|||
import Filters from '@/components/project/partials/filters.vue'
|
||||
|
||||
import {getDefaultTaskFilterParams, type TaskFilterParams} from '@/services/taskCollection'
|
||||
import {useRouteQuery} from '@vueuse/router'
|
||||
|
||||
const modelValue = defineModel<TaskFilterParams>({})
|
||||
|
||||
const value = ref<TaskFilterParams>({})
|
||||
const filter = useRouteQuery('filter')
|
||||
|
||||
watch(
|
||||
() => modelValue.value,
|
||||
(modelValue: TaskFilterParams) => {
|
||||
value.value = modelValue
|
||||
if (value.value.filter !== '' && value.value.filter !== getDefaultTaskFilterParams().filter) {
|
||||
filter.value = value.value.filter
|
||||
}
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => filter.value,
|
||||
val => {
|
||||
if (modelValue.value?.filter === val || typeof val === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
modelValue.value.filter = val
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
function emitChanges(newValue: TaskFilterParams) {
|
||||
filter.value = newValue.filter
|
||||
if (modelValue.value?.filter === newValue.filter && modelValue.value?.s === newValue.s) {
|
||||
return
|
||||
}
|
||||
|
@ -60,25 +72,11 @@ function emitChanges(newValue: TaskFilterParams) {
|
|||
}
|
||||
|
||||
const hasFilters = computed(() => {
|
||||
// this.value also contains the page parameter which we don't want to include in filters
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const {filter, s} = value.value
|
||||
const def = {...getDefaultTaskFilterParams()}
|
||||
|
||||
const params = {filter, s}
|
||||
const defaultParams = {
|
||||
filter: def.filter,
|
||||
s: s ? def.s : undefined,
|
||||
}
|
||||
|
||||
return JSON.stringify(params) !== JSON.stringify(defaultParams)
|
||||
return value.value.filter !== '' ||
|
||||
value.value.s !== ''
|
||||
})
|
||||
|
||||
const modalOpen = ref(false)
|
||||
|
||||
function clearFilters() {
|
||||
value.value = {...getDefaultTaskFilterParams()}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -89,4 +87,21 @@ function clearFilters() {
|
|||
margin: 2rem 0 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
$filter-bubble-size: .75rem;
|
||||
.has-filters {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: math.div($filter-bubble-size, -2);
|
||||
right: math.div($filter-bubble-size, -2);
|
||||
|
||||
width: $filter-bubble-size;
|
||||
height: $filter-bubble-size;
|
||||
border-radius: 100%;
|
||||
background: var(--primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -25,6 +25,14 @@
|
|||
v-if="hasFooter"
|
||||
#footer
|
||||
>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
class="mr-2"
|
||||
:disabled="params.filter === ''"
|
||||
@click.prevent.stop="clearFiltersAndEmit"
|
||||
>
|
||||
{{ $t('filters.clear') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="primary"
|
||||
@click.prevent.stop="changeAndEmitButton"
|
||||
|
@ -40,8 +48,7 @@ export const ALPHABETICAL_SORT = 'title'
|
|||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, ref} from 'vue'
|
||||
import {watchDebounced} from '@vueuse/core'
|
||||
import {computed, ref, watch} from 'vue'
|
||||
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
||||
import FilterInput from '@/components/project/partials/FilterInput.vue'
|
||||
import {useRoute} from 'vue-router'
|
||||
|
@ -51,8 +58,8 @@ import {useProjectStore} from '@/stores/projects'
|
|||
import {FILTER_OPERATORS, transformFilterStringForApi, transformFilterStringFromApi} from '@/helpers/filters'
|
||||
import FilterInputDocs from '@/components/project/partials/FilterInputDocs.vue'
|
||||
|
||||
const {
|
||||
hasTitle= false,
|
||||
const {
|
||||
hasTitle = false,
|
||||
hasFooter = true,
|
||||
modelValue,
|
||||
} = defineProps<{
|
||||
|
@ -81,18 +88,18 @@ const params = ref<TaskFilterParams>({
|
|||
})
|
||||
|
||||
// Using watchDebounced to prevent the filter re-triggering itself.
|
||||
watchDebounced(
|
||||
watch(
|
||||
() => modelValue,
|
||||
(value: TaskFilterParams) => {
|
||||
const val = {...value}
|
||||
val.filter = transformFilterStringFromApi(
|
||||
val?.filter || '',
|
||||
labelId => labelStore.getLabelById(labelId)?.title,
|
||||
projectId => projectStore.projects.value[projectId]?.title || null,
|
||||
projectId => projectStore.projects[projectId]?.title || null,
|
||||
)
|
||||
params.value = val
|
||||
},
|
||||
{immediate: true, debounce: 500, maxWait: 1000},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
const labelStore = useLabelStore()
|
||||
|
@ -102,7 +109,10 @@ function change() {
|
|||
const filter = transformFilterStringForApi(
|
||||
params.value.filter,
|
||||
labelTitle => labelStore.filterLabelsByQuery([], labelTitle)[0]?.id || null,
|
||||
projectTitle => projectStore.searchProject(projectTitle)[0]?.id || null,
|
||||
projectTitle => {
|
||||
const found = projectStore.findProjectByExactname(projectTitle)
|
||||
return found?.id || null
|
||||
},
|
||||
)
|
||||
|
||||
let s = ''
|
||||
|
@ -130,4 +140,9 @@ function changeAndEmitButton() {
|
|||
change()
|
||||
emit('showResultsButtonClicked')
|
||||
}
|
||||
|
||||
function clearFiltersAndEmit() {
|
||||
params.value.filter = ''
|
||||
changeAndEmitButton()
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -47,6 +47,12 @@
|
|||
>
|
||||
{{ $t('menu.edit') }}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
:to="{ name: 'project.settings.views', params: { projectId: project.id } }"
|
||||
icon="eye"
|
||||
>
|
||||
{{ $t('menu.views') }}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
v-if="backgroundsEnabled"
|
||||
:to="{ name: 'project.settings.background', params: { projectId: project.id } }"
|
||||
|
@ -90,7 +96,6 @@
|
|||
{{ $t('project.webhooks.title') }}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
v-if="level < 2"
|
||||
:to="{ name: 'project.createFromParent', params: { parentProjectId: project.id } }"
|
||||
icon="layer-group"
|
||||
>
|
||||
|
@ -129,9 +134,6 @@ const props = defineProps({
|
|||
type: Object as PropType<IProject>,
|
||||
required: true,
|
||||
},
|
||||
level: {
|
||||
type: Number,
|
||||
},
|
||||
})
|
||||
|
||||
const projectStore = useProjectStore()
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
<ProjectWrapper
|
||||
class="project-gantt"
|
||||
:project-id="filters.projectId"
|
||||
view-name="gantt"
|
||||
:view-id
|
||||
>
|
||||
<template #header>
|
||||
<template #default>
|
||||
<card :has-content="false">
|
||||
<div class="gantt-options">
|
||||
<div class="field">
|
||||
|
@ -45,9 +45,7 @@
|
|||
</Fancycheckbox>
|
||||
</div>
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<div class="gantt-chart-container">
|
||||
<card
|
||||
:has-content="false"
|
||||
|
@ -79,7 +77,7 @@ import {useI18n} from 'vue-i18n'
|
|||
import type {RouteLocationNormalized} from 'vue-router'
|
||||
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
|
||||
import {getFlatpickrLanguage} from '@/helpers/flatpickrLanguage'
|
||||
|
||||
import Foo from '@/components/misc/flatpickr/Flatpickr.vue'
|
||||
import ProjectWrapper from '@/components/project/ProjectWrapper.vue'
|
||||
|
@ -87,15 +85,19 @@ import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
|||
import TaskForm from '@/components/tasks/TaskForm.vue'
|
||||
|
||||
import {createAsyncComponent} from '@/helpers/createAsyncComponent'
|
||||
import {useGanttFilters} from './helpers/useGanttFilters'
|
||||
import {useGanttFilters} from '../../../views/project/helpers/useGanttFilters'
|
||||
import {RIGHTS} from '@/constants/rights'
|
||||
|
||||
import type {DateISO} from '@/types/DateISO'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import type {IProjectView} from '@/modelTypes/IProjectView'
|
||||
|
||||
type Options = Flatpickr.Options.Options
|
||||
|
||||
const props = defineProps<{route: RouteLocationNormalized}>()
|
||||
const props = defineProps<{
|
||||
route: RouteLocationNormalized
|
||||
viewId: IProjectView['id']
|
||||
}>()
|
||||
|
||||
const GanttChart = createAsyncComponent(() => import('@/components/tasks/GanttChart.vue'))
|
||||
|
||||
|
@ -111,7 +113,7 @@ const {
|
|||
isLoading,
|
||||
addTask,
|
||||
updateTask,
|
||||
} = useGanttFilters(route)
|
||||
} = useGanttFilters(route, props.viewId)
|
||||
|
||||
const DEFAULT_DATE_RANGE_DAYS = 7
|
||||
|
|
@ -2,16 +2,14 @@
|
|||
<ProjectWrapper
|
||||
class="project-kanban"
|
||||
:project-id="projectId"
|
||||
view-name="kanban"
|
||||
:view-id
|
||||
>
|
||||
<template #header>
|
||||
<div
|
||||
v-if="!isSavedFilter(project)"
|
||||
class="filter-container"
|
||||
>
|
||||
<div class="items">
|
||||
<FilterPopup v-model="params" />
|
||||
</div>
|
||||
<div class="filter-container">
|
||||
<FilterPopup
|
||||
v-if="!isSavedFilter(project)"
|
||||
v-model="params"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -43,7 +41,7 @@
|
|||
@click="() => unCollapseBucket(bucket)"
|
||||
>
|
||||
<span
|
||||
v-if="project?.doneBucketId === bucket.id"
|
||||
v-if="view?.doneBucketId === bucket.id"
|
||||
v-tooltip="$t('project.kanban.doneBucketHint')"
|
||||
class="icon is-small has-text-success mr-2"
|
||||
>
|
||||
|
@ -111,7 +109,7 @@
|
|||
</DropdownItem>
|
||||
<DropdownItem
|
||||
v-tooltip="$t('project.kanban.doneBucketHintExtended')"
|
||||
:icon-class="{'has-text-success': bucket.id === project?.doneBucketId}"
|
||||
:icon-class="{'has-text-success': bucket.id === view?.doneBucketId}"
|
||||
icon="check-double"
|
||||
@click.stop="toggleDoneBucket(bucket)"
|
||||
>
|
||||
|
@ -119,7 +117,7 @@
|
|||
</DropdownItem>
|
||||
<DropdownItem
|
||||
v-tooltip="$t('project.kanban.defaultBucketHint')"
|
||||
:icon-class="{'has-text-primary': bucket.id === project.defaultBucketId}"
|
||||
:icon-class="{'has-text-primary': bucket.id === view?.defaultBucketId}"
|
||||
icon="th"
|
||||
@click.stop="toggleDefaultBucket(bucket)"
|
||||
>
|
||||
|
@ -197,7 +195,9 @@
|
|||
variant="secondary"
|
||||
@click="toggleShowNewTaskInput(bucket.id)"
|
||||
>
|
||||
{{ bucket.tasks.length === 0 ? $t('project.kanban.addTask') : $t('project.kanban.addAnotherTask') }}
|
||||
{{
|
||||
bucket.tasks.length === 0 ? $t('project.kanban.addTask') : $t('project.kanban.addAnotherTask')
|
||||
}}
|
||||
</x-button>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -277,7 +277,6 @@ import {RIGHTS as Rights} from '@/constants/rights'
|
|||
import BucketModel from '@/models/bucket'
|
||||
|
||||
import type {IBucket} from '@/modelTypes/IBucket'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
|
@ -290,18 +289,30 @@ import KanbanCard from '@/components/tasks/partials/kanban-card.vue'
|
|||
import Dropdown from '@/components/misc/dropdown.vue'
|
||||
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||
|
||||
import {getCollapsedBucketState, saveCollapsedBucketState, type CollapsedBuckets} from '@/helpers/saveCollapsedBucketState'
|
||||
import {
|
||||
type CollapsedBuckets,
|
||||
getCollapsedBucketState,
|
||||
saveCollapsedBucketState,
|
||||
} from '@/helpers/saveCollapsedBucketState'
|
||||
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
|
||||
|
||||
import {isSavedFilter} from '@/services/savedFilter'
|
||||
import {success} from '@/message'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import type {TaskFilterParams} from '@/services/taskCollection'
|
||||
import type {IProjectView} from '@/modelTypes/IProjectView'
|
||||
import TaskPositionService from '@/services/taskPosition'
|
||||
import TaskPositionModel from '@/models/taskPosition'
|
||||
import {i18n} from '@/i18n'
|
||||
import ProjectViewService from '@/services/projectViews'
|
||||
import ProjectViewModel from '@/models/projectView'
|
||||
|
||||
const {
|
||||
projectId = undefined,
|
||||
projectId,
|
||||
viewId,
|
||||
} = defineProps<{
|
||||
projectId: number,
|
||||
viewId: IProjectView['id'],
|
||||
}>()
|
||||
|
||||
const DRAG_OPTIONS = {
|
||||
|
@ -321,8 +332,9 @@ const baseStore = useBaseStore()
|
|||
const kanbanStore = useKanbanStore()
|
||||
const taskStore = useTaskStore()
|
||||
const projectStore = useProjectStore()
|
||||
const taskPositionService = ref(new TaskPositionService())
|
||||
|
||||
const taskContainerRefs = ref<{[id: IBucket['id']]: HTMLElement}>({})
|
||||
const taskContainerRefs = ref<{ [id: IBucket['id']]: HTMLElement }>({})
|
||||
const bucketLimitInputRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
const drag = ref(false)
|
||||
|
@ -334,18 +346,18 @@ const bucketToDelete = ref(0)
|
|||
const bucketTitleEditable = ref(false)
|
||||
|
||||
const newTaskText = ref('')
|
||||
const showNewTaskInput = ref<{[id: IBucket['id']]: boolean}>({})
|
||||
const showNewTaskInput = ref<{ [id: IBucket['id']]: boolean }>({})
|
||||
|
||||
const newBucketTitle = ref('')
|
||||
const showNewBucketInput = ref(false)
|
||||
const newTaskError = ref<{[id: IBucket['id']]: boolean}>({})
|
||||
const newTaskError = ref<{ [id: IBucket['id']]: boolean }>({})
|
||||
const newTaskInputFocused = ref(false)
|
||||
|
||||
const showSetLimitInput = ref(false)
|
||||
const collapsedBuckets = ref<CollapsedBuckets>({})
|
||||
|
||||
// We're using this to show the loading animation only at the task when updating it
|
||||
const taskUpdating = ref<{[id: ITask['id']]: boolean}>({})
|
||||
const taskUpdating = ref<{ [id: ITask['id']]: boolean }>({})
|
||||
const oneTaskUpdating = ref(false)
|
||||
|
||||
const params = ref<TaskFilterParams>({
|
||||
|
@ -359,7 +371,7 @@ const params = ref<TaskFilterParams>({
|
|||
const getTaskDraggableTaskComponentData = computed(() => (bucket: IBucket) => {
|
||||
return {
|
||||
ref: (el: HTMLElement) => setTaskContainerRef(bucket.id, el),
|
||||
onScroll: (event: Event) => handleTaskContainerScroll(bucket.id, bucket.projectId, event.target as HTMLElement),
|
||||
onScroll: (event: Event) => handleTaskContainerScroll(bucket.id, event.target as HTMLElement),
|
||||
type: 'transition-group',
|
||||
name: !drag.value ? 'move-card' : null,
|
||||
class: [
|
||||
|
@ -378,24 +390,27 @@ const bucketDraggableComponentData = computed(() => ({
|
|||
],
|
||||
}))
|
||||
const canWrite = computed(() => baseStore.currentProject?.maxRight > Rights.READ)
|
||||
const project = computed(() => projectId ? projectStore.projects[projectId]: null)
|
||||
const project = computed(() => projectId ? projectStore.projects[projectId] : null)
|
||||
|
||||
const buckets = computed(() => kanbanStore.buckets)
|
||||
const loading = computed(() => kanbanStore.isLoading)
|
||||
|
||||
const taskLoading = computed(() => taskStore.isLoading)
|
||||
const view = computed<IProjectView | null>(() => project.value?.views.find(v => v.id === viewId) || null)
|
||||
|
||||
const taskLoading = computed(() => taskStore.isLoading || taskPositionService.value.loading)
|
||||
|
||||
watch(
|
||||
() => ({
|
||||
params: params.value,
|
||||
projectId,
|
||||
viewId,
|
||||
}),
|
||||
({params}) => {
|
||||
if (projectId === undefined || Number(projectId) === 0) {
|
||||
return
|
||||
}
|
||||
collapsedBuckets.value = getCollapsedBucketState(projectId)
|
||||
kanbanStore.loadBucketsForProject({projectId, params})
|
||||
kanbanStore.loadBucketsForProject(projectId, viewId, params)
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
|
@ -408,7 +423,7 @@ function setTaskContainerRef(id: IBucket['id'], el: HTMLElement) {
|
|||
taskContainerRefs.value[id] = el
|
||||
}
|
||||
|
||||
function handleTaskContainerScroll(id: IBucket['id'], projectId: IProject['id'], el: HTMLElement) {
|
||||
function handleTaskContainerScroll(id: IBucket['id'], el: HTMLElement) {
|
||||
if (!el) {
|
||||
return
|
||||
}
|
||||
|
@ -420,6 +435,7 @@ function handleTaskContainerScroll(id: IBucket['id'], projectId: IProject['id'],
|
|||
|
||||
kanbanStore.loadNextTasksForBucket(
|
||||
projectId,
|
||||
viewId,
|
||||
params.value,
|
||||
id,
|
||||
)
|
||||
|
@ -469,7 +485,7 @@ async function updateTaskPosition(e) {
|
|||
|
||||
const newTask = klona(task) // cloning the task to avoid pinia store manipulation
|
||||
newTask.bucketId = newBucket.id
|
||||
newTask.kanbanPosition = calculateItemPosition(
|
||||
const position = calculateItemPosition(
|
||||
taskBefore !== null ? taskBefore.kanbanPosition : null,
|
||||
taskAfter !== null ? taskAfter.kanbanPosition : null,
|
||||
)
|
||||
|
@ -479,6 +495,8 @@ async function updateTaskPosition(e) {
|
|||
) {
|
||||
newTask.done = project.value?.doneBucketId === newBucket.id
|
||||
}
|
||||
|
||||
let bucketHasChanged = false
|
||||
if (
|
||||
oldBucket !== undefined && // This shouldn't actually be `undefined`, but let's play it safe.
|
||||
newBucket.id !== oldBucket.id
|
||||
|
@ -491,13 +509,23 @@ async function updateTaskPosition(e) {
|
|||
...newBucket,
|
||||
count: newBucket.count + 1,
|
||||
})
|
||||
bucketHasChanged = true
|
||||
}
|
||||
|
||||
try {
|
||||
await taskStore.update(newTask)
|
||||
const newPosition = new TaskPositionModel({
|
||||
position,
|
||||
projectViewId: viewId,
|
||||
taskId: newTask.id,
|
||||
})
|
||||
await taskPositionService.value.update(newPosition)
|
||||
|
||||
if(bucketHasChanged) {
|
||||
await taskStore.update(newTask)
|
||||
}
|
||||
|
||||
// Make sure the first and second task don't both get position 0 assigned
|
||||
if(newTaskIndex === 0 && taskAfter !== null && taskAfter.kanbanPosition === 0) {
|
||||
if (newTaskIndex === 0 && taskAfter !== null && taskAfter.kanbanPosition === 0) {
|
||||
const taskAfterAfter = newBucket.tasks[newTaskIndex + 2] ?? null
|
||||
const newTaskAfter = klona(taskAfter) // cloning the task to avoid pinia store manipulation
|
||||
newTaskAfter.bucketId = newBucket.id
|
||||
|
@ -552,9 +580,9 @@ async function createNewBucket() {
|
|||
await kanbanStore.createBucket(new BucketModel({
|
||||
title: newBucketTitle.value,
|
||||
projectId: project.value.id,
|
||||
projectViewId: viewId,
|
||||
}))
|
||||
newBucketTitle.value = ''
|
||||
showNewBucketInput.value = false
|
||||
}
|
||||
|
||||
function deleteBucketModal(bucketId: IBucket['id']) {
|
||||
|
@ -572,6 +600,7 @@ async function deleteBucket() {
|
|||
bucket: new BucketModel({
|
||||
id: bucketToDelete.value,
|
||||
projectId: project.value.id,
|
||||
projectViewId: viewId,
|
||||
}),
|
||||
params: params.value,
|
||||
})
|
||||
|
@ -590,10 +619,19 @@ async function focusBucketTitle(e: Event) {
|
|||
}
|
||||
|
||||
async function saveBucketTitle(bucketId: IBucket['id'], bucketTitle: string) {
|
||||
await kanbanStore.updateBucketTitle({
|
||||
|
||||
const bucket = kanbanStore.getBucketById(bucketId)
|
||||
if (bucket?.title === bucketTitle) {
|
||||
bucketTitleEditable.value = false
|
||||
return
|
||||
}
|
||||
|
||||
await kanbanStore.updateBucket({
|
||||
id: bucketId,
|
||||
title: bucketTitle,
|
||||
projectId,
|
||||
})
|
||||
success({message: i18n.global.t('project.kanban.bucketTitleSavedSuccess')})
|
||||
bucketTitleEditable.value = false
|
||||
}
|
||||
|
||||
|
@ -603,7 +641,7 @@ function updateBuckets(value: IBucket[]) {
|
|||
}
|
||||
|
||||
// TODO: fix type
|
||||
function updateBucketPosition(e: {newIndex: number}) {
|
||||
function updateBucketPosition(e: { newIndex: number }) {
|
||||
// (2) bucket positon is changed
|
||||
dragBucket.value = false
|
||||
|
||||
|
@ -613,6 +651,7 @@ function updateBucketPosition(e: {newIndex: number}) {
|
|||
|
||||
kanbanStore.updateBucket({
|
||||
id: bucket.id,
|
||||
projectId,
|
||||
position: calculateItemPosition(
|
||||
bucketBefore !== null ? bucketBefore.position : null,
|
||||
bucketAfter !== null ? bucketAfter.position : null,
|
||||
|
@ -627,24 +666,25 @@ async function saveBucketLimit(bucketId: IBucket['id'], limit: number) {
|
|||
|
||||
await kanbanStore.updateBucket({
|
||||
...kanbanStore.getBucketById(bucketId),
|
||||
projectId,
|
||||
limit,
|
||||
})
|
||||
success({message: t('project.kanban.bucketLimitSavedSuccess')})
|
||||
}
|
||||
|
||||
const setBucketLimitCancel = ref<number|null>(null)
|
||||
const setBucketLimitCancel = ref<number | null>(null)
|
||||
|
||||
async function setBucketLimit(bucketId: IBucket['id'], now: boolean = false) {
|
||||
const limit = parseInt(bucketLimitInputRef.value?.value || '')
|
||||
|
||||
|
||||
if (setBucketLimitCancel.value !== null) {
|
||||
clearTimeout(setBucketLimitCancel.value)
|
||||
}
|
||||
|
||||
|
||||
if (now) {
|
||||
return saveBucketLimit(bucketId, limit)
|
||||
}
|
||||
|
||||
|
||||
setBucketLimitCancel.value = setTimeout(saveBucketLimit, 2500, bucketId, limit)
|
||||
}
|
||||
|
||||
|
@ -665,26 +705,46 @@ function dragstart(bucket: IBucket) {
|
|||
}
|
||||
|
||||
async function toggleDefaultBucket(bucket: IBucket) {
|
||||
const defaultBucketId = project.value.defaultBucketId === bucket.id
|
||||
const defaultBucketId = view.value?.defaultBucketId === bucket.id
|
||||
? 0
|
||||
: bucket.id
|
||||
|
||||
await projectStore.updateProject({
|
||||
...project.value,
|
||||
const projectViewService = new ProjectViewService()
|
||||
const updatedView = await projectViewService.update(new ProjectViewModel({
|
||||
...view.value,
|
||||
defaultBucketId,
|
||||
})
|
||||
}))
|
||||
|
||||
const views = project.value.views.map(v => v.id === view.value?.id ? updatedView : v)
|
||||
const updatedProject = {
|
||||
...project.value,
|
||||
views,
|
||||
}
|
||||
|
||||
projectStore.setProject(updatedProject)
|
||||
|
||||
success({message: t('project.kanban.defaultBucketSavedSuccess')})
|
||||
}
|
||||
|
||||
async function toggleDoneBucket(bucket: IBucket) {
|
||||
const doneBucketId = project.value?.doneBucketId === bucket.id
|
||||
const doneBucketId = view.value?.doneBucketId === bucket.id
|
||||
? 0
|
||||
: bucket.id
|
||||
|
||||
await projectStore.updateProject({
|
||||
...project.value,
|
||||
|
||||
const projectViewService = new ProjectViewService()
|
||||
const updatedView = await projectViewService.update(new ProjectViewModel({
|
||||
...view.value,
|
||||
doneBucketId,
|
||||
})
|
||||
}))
|
||||
|
||||
const views = project.value.views.map(v => v.id === view.value?.id ? updatedView : v)
|
||||
const updatedProject = {
|
||||
...project.value,
|
||||
views,
|
||||
}
|
||||
|
||||
projectStore.setProject(updatedProject)
|
||||
|
||||
success({message: t('project.kanban.doneBucketSavedSuccess')})
|
||||
}
|
||||
|
||||
|
@ -740,6 +800,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
|||
* {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
@ -781,6 +842,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
|||
&:first-of-type {
|
||||
padding-top: .5rem;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
padding-bottom: .5rem;
|
||||
}
|
|
@ -2,55 +2,15 @@
|
|||
<ProjectWrapper
|
||||
class="project-list"
|
||||
:project-id="projectId"
|
||||
view-name="project"
|
||||
:view-id
|
||||
>
|
||||
<template #header>
|
||||
<div
|
||||
v-if="!isSavedFilter(project)"
|
||||
class="filter-container"
|
||||
>
|
||||
<div class="items">
|
||||
<div class="search">
|
||||
<div
|
||||
:class="{ hidden: !showTaskSearch }"
|
||||
class="field has-addons"
|
||||
>
|
||||
<div class="control has-icons-left has-icons-right">
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
v-focus
|
||||
class="input"
|
||||
:placeholder="$t('misc.search')"
|
||||
type="text"
|
||||
@blur="hideSearchBar()"
|
||||
@keyup.enter="searchTasks"
|
||||
>
|
||||
<span class="icon is-left">
|
||||
<icon icon="search" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
:loading="loading"
|
||||
:shadow="false"
|
||||
@click="searchTasks"
|
||||
>
|
||||
{{ $t('misc.search') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
<x-button
|
||||
v-if="!showTaskSearch"
|
||||
icon="search"
|
||||
variant="secondary"
|
||||
@click="showTaskSearch = !showTaskSearch"
|
||||
/>
|
||||
</div>
|
||||
<FilterPopup
|
||||
v-model="params"
|
||||
@update:modelValue="prepareFiltersAndLoadTasks()"
|
||||
/>
|
||||
</div>
|
||||
<div class="filter-container">
|
||||
<FilterPopup
|
||||
v-if="!isSavedFilter(project)"
|
||||
v-model="params"
|
||||
@update:modelValue="prepareFiltersAndLoadTasks()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -120,7 +80,7 @@
|
|||
</template>
|
||||
</draggable>
|
||||
|
||||
<Pagination
|
||||
<Pagination
|
||||
:total-pages="totalPages"
|
||||
:current-page="currentPage"
|
||||
/>
|
||||
|
@ -131,13 +91,12 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'List' }
|
||||
export default {name: 'List'}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, nextTick, onMounted, watch} from 'vue'
|
||||
import draggable from 'zhyswan-vuedraggable'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
|
||||
import ProjectWrapper from '@/components/project/ProjectWrapper.vue'
|
||||
import ButtonLink from '@/components/misc/ButtonLink.vue'
|
||||
|
@ -155,18 +114,21 @@ import type {ITask} from '@/modelTypes/ITask'
|
|||
import {isSavedFilter} from '@/services/savedFilter'
|
||||
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
import {useTaskStore} from '@/stores/tasks'
|
||||
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
import type {IProjectView} from '@/modelTypes/IProjectView'
|
||||
import TaskPositionService from '@/services/taskPosition'
|
||||
import TaskPositionModel from '@/models/taskPosition'
|
||||
|
||||
const {
|
||||
projectId,
|
||||
viewId,
|
||||
} = defineProps<{
|
||||
projectId: IProject['id'],
|
||||
viewId: IProjectView['id'],
|
||||
}>()
|
||||
|
||||
const ctaVisible = ref(false)
|
||||
const showTaskSearch = ref(false)
|
||||
|
||||
const drag = ref(false)
|
||||
const DRAG_OPTIONS = {
|
||||
|
@ -180,10 +142,11 @@ const {
|
|||
totalPages,
|
||||
currentPage,
|
||||
loadTasks,
|
||||
searchTerm,
|
||||
params,
|
||||
sortByParam,
|
||||
} = useTaskList(() => projectId, {position: 'asc' })
|
||||
} = useTaskList(() => projectId, () => viewId, {position: 'asc'})
|
||||
|
||||
const taskPositionService = ref(new TaskPositionService())
|
||||
|
||||
const tasks = ref<ITask[]>([])
|
||||
watch(
|
||||
|
@ -203,7 +166,7 @@ watch(
|
|||
|
||||
// If the task is a subtask, make sure the parent task is available in the current view as well
|
||||
for (const pt of t.relatedTasks.parenttask) {
|
||||
if(typeof tasksById[pt.id] === 'undefined') {
|
||||
if (typeof tasksById[pt.id] === 'undefined') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -225,7 +188,6 @@ const firstNewPosition = computed(() => {
|
|||
return calculateItemPosition(null, tasks.value[0].position)
|
||||
})
|
||||
|
||||
const taskStore = useTaskStore()
|
||||
const baseStore = useBaseStore()
|
||||
const project = computed(() => baseStore.currentProject)
|
||||
|
||||
|
@ -238,43 +200,17 @@ onMounted(async () => {
|
|||
ctaVisible.value = true
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
function searchTasks() {
|
||||
// Only search if the search term changed
|
||||
if (route.query as unknown as string === searchTerm.value) {
|
||||
return
|
||||
}
|
||||
|
||||
router.push({
|
||||
name: 'project.list',
|
||||
query: {search: searchTerm.value},
|
||||
})
|
||||
}
|
||||
|
||||
function hideSearchBar() {
|
||||
// This is a workaround.
|
||||
// When clicking on the search button, @blur from the input is fired. If we
|
||||
// would then directly hide the whole search bar directly, no click event
|
||||
// from the button gets fired. To prevent this, we wait 200ms until we hide
|
||||
// everything so the button has a chance of firing the search event.
|
||||
setTimeout(() => {
|
||||
showTaskSearch.value = false
|
||||
}, 200)
|
||||
}
|
||||
|
||||
const addTaskRef = ref<typeof AddTask | null>(null)
|
||||
|
||||
function focusNewTaskInput() {
|
||||
addTaskRef.value?.focusTaskInput()
|
||||
}
|
||||
|
||||
function updateTaskList(task: ITask) {
|
||||
if (isAlphabeticalSorting.value ) {
|
||||
if (isAlphabeticalSorting.value) {
|
||||
// reload tasks with current filter and sorting
|
||||
loadTasks()
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
allTasks.value = [
|
||||
task,
|
||||
...allTasks.value,
|
||||
|
@ -300,18 +236,22 @@ async function saveTaskPosition(e) {
|
|||
const taskBefore = tasks.value[e.newIndex - 1] ?? null
|
||||
const taskAfter = tasks.value[e.newIndex + 1] ?? null
|
||||
|
||||
const newTask = {
|
||||
...task,
|
||||
position: calculateItemPosition(taskBefore !== null ? taskBefore.position : null, taskAfter !== null ? taskAfter.position : null),
|
||||
}
|
||||
const position = calculateItemPosition(taskBefore !== null ? taskBefore.position : null, taskAfter !== null ? taskAfter.position : null)
|
||||
|
||||
const updatedTask = await taskStore.update(newTask)
|
||||
tasks.value[e.newIndex] = updatedTask
|
||||
await taskPositionService.value.update(new TaskPositionModel({
|
||||
position,
|
||||
projectViewId: viewId,
|
||||
taskId: task.id,
|
||||
}))
|
||||
tasks.value[e.newIndex] = {
|
||||
...task,
|
||||
position,
|
||||
}
|
||||
}
|
||||
|
||||
function prepareFiltersAndLoadTasks() {
|
||||
if(isAlphabeticalSorting.value) {
|
||||
sortByParam.value = {}
|
||||
if (isAlphabeticalSorting.value) {
|
||||
sortByParam.value = {}
|
||||
sortByParam.value[ALPHABETICAL_SORT] = 'asc'
|
||||
}
|
||||
|
||||
|
@ -328,7 +268,7 @@ function prepareFiltersAndLoadTasks() {
|
|||
border-radius: $radius;
|
||||
background: var(--grey-100);
|
||||
border: 2px dashed var(--grey-300);
|
||||
|
||||
|
||||
* {
|
||||
opacity: 0;
|
||||
}
|
||||
|
@ -339,8 +279,8 @@ function prepareFiltersAndLoadTasks() {
|
|||
}
|
||||
|
||||
.link-share-view .card {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.control.has-icons-left .icon,
|
|
@ -2,73 +2,72 @@
|
|||
<ProjectWrapper
|
||||
class="project-table"
|
||||
:project-id="projectId"
|
||||
view-name="table"
|
||||
:view-id
|
||||
>
|
||||
<template #header>
|
||||
<div class="filter-container">
|
||||
<div class="items">
|
||||
<Popup>
|
||||
<template #trigger="{toggle}">
|
||||
<x-button
|
||||
icon="th"
|
||||
variant="secondary"
|
||||
@click.prevent.stop="toggle()"
|
||||
>
|
||||
{{ $t('project.table.columns') }}
|
||||
</x-button>
|
||||
</template>
|
||||
<template #content="{isOpen}">
|
||||
<card
|
||||
class="columns-filter"
|
||||
:class="{'is-open': isOpen}"
|
||||
>
|
||||
<Fancycheckbox v-model="activeColumns.index">
|
||||
#
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.done">
|
||||
{{ $t('task.attributes.done') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.title">
|
||||
{{ $t('task.attributes.title') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.priority">
|
||||
{{ $t('task.attributes.priority') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.labels">
|
||||
{{ $t('task.attributes.labels') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.assignees">
|
||||
{{ $t('task.attributes.assignees') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.dueDate">
|
||||
{{ $t('task.attributes.dueDate') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.startDate">
|
||||
{{ $t('task.attributes.startDate') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.endDate">
|
||||
{{ $t('task.attributes.endDate') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.percentDone">
|
||||
{{ $t('task.attributes.percentDone') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.doneAt">
|
||||
{{ $t('task.attributes.doneAt') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.created">
|
||||
{{ $t('task.attributes.created') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.updated">
|
||||
{{ $t('task.attributes.updated') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.createdBy">
|
||||
{{ $t('task.attributes.createdBy') }}
|
||||
</Fancycheckbox>
|
||||
</card>
|
||||
</template>
|
||||
</Popup>
|
||||
<FilterPopup v-model="params" />
|
||||
</div>
|
||||
<Popup>
|
||||
<template #trigger="{toggle}">
|
||||
<x-button
|
||||
icon="th"
|
||||
variant="secondary"
|
||||
class="mr-2"
|
||||
@click.prevent.stop="toggle()"
|
||||
>
|
||||
{{ $t('project.table.columns') }}
|
||||
</x-button>
|
||||
</template>
|
||||
<template #content="{isOpen}">
|
||||
<card
|
||||
class="columns-filter"
|
||||
:class="{'is-open': isOpen}"
|
||||
>
|
||||
<Fancycheckbox v-model="activeColumns.index">
|
||||
#
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.done">
|
||||
{{ $t('task.attributes.done') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.title">
|
||||
{{ $t('task.attributes.title') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.priority">
|
||||
{{ $t('task.attributes.priority') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.labels">
|
||||
{{ $t('task.attributes.labels') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.assignees">
|
||||
{{ $t('task.attributes.assignees') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.dueDate">
|
||||
{{ $t('task.attributes.dueDate') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.startDate">
|
||||
{{ $t('task.attributes.startDate') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.endDate">
|
||||
{{ $t('task.attributes.endDate') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.percentDone">
|
||||
{{ $t('task.attributes.percentDone') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.doneAt">
|
||||
{{ $t('task.attributes.doneAt') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.created">
|
||||
{{ $t('task.attributes.created') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.updated">
|
||||
{{ $t('task.attributes.updated') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.createdBy">
|
||||
{{ $t('task.attributes.createdBy') }}
|
||||
</Fancycheckbox>
|
||||
</card>
|
||||
</template>
|
||||
</Popup>
|
||||
<FilterPopup v-model="params" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -284,17 +283,19 @@ import FilterPopup from '@/components/project/partials/filter-popup.vue'
|
|||
import Pagination from '@/components/misc/pagination.vue'
|
||||
import Popup from '@/components/misc/popup.vue'
|
||||
|
||||
import {useTaskList} from '@/composables/useTaskList'
|
||||
|
||||
import type {SortBy} from '@/composables/useTaskList'
|
||||
import {useTaskList} from '@/composables/useTaskList'
|
||||
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'
|
||||
|
||||
const {
|
||||
projectId,
|
||||
viewId,
|
||||
} = defineProps<{
|
||||
projectId: IProject['id'],
|
||||
viewId: IProjectView['id'],
|
||||
}>()
|
||||
|
||||
const ACTIVE_COLUMNS_DEFAULT = {
|
||||
|
@ -321,7 +322,7 @@ const SORT_BY_DEFAULT: SortBy = {
|
|||
const activeColumns = useStorage('tableViewColumns', {...ACTIVE_COLUMNS_DEFAULT})
|
||||
const sortBy = useStorage<SortBy>('tableViewSortBy', {...SORT_BY_DEFAULT})
|
||||
|
||||
const taskList = useTaskList(() => projectId, sortBy.value)
|
||||
const taskList = useTaskList(() => projectId, () => viewId, sortBy.value)
|
||||
|
||||
const {
|
||||
loading,
|
||||
|
@ -333,9 +334,7 @@ const {
|
|||
const tasks: Ref<ITask[]> = taskList.tasks
|
||||
|
||||
Object.assign(params.value, {
|
||||
filter_by: [],
|
||||
filter_value: [],
|
||||
filter_comparator: [],
|
||||
filter: '',
|
||||
})
|
||||
|
||||
// FIXME: by doing this we can have multiple sort orders
|
||||
|
@ -383,6 +382,11 @@ const taskDetailRoutes = computed(() => Object.fromEntries(
|
|||
.columns-filter {
|
||||
margin: 0;
|
||||
|
||||
:deep(.card-content .content) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.is-open {
|
||||
margin: 2rem 0 1rem;
|
||||
}
|
||||
|
@ -392,4 +396,8 @@ const taskDetailRoutes = computed(() => Object.fromEntries(
|
|||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.filter-container :deep(.popup) {
|
||||
top: 7rem;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,233 @@
|
|||
<script setup lang="ts">
|
||||
import type {IProjectView} from '@/modelTypes/IProjectView'
|
||||
import XButton from '@/components/input/button.vue'
|
||||
import FilterInput from '@/components/project/partials/FilterInput.vue'
|
||||
import {ref, watch} from 'vue'
|
||||
import {transformFilterStringForApi, transformFilterStringFromApi} from '@/helpers/filters'
|
||||
import {useLabelStore} from '@/stores/labels'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
||||
const {
|
||||
modelValue,
|
||||
} = defineProps<{
|
||||
modelValue: IProjectView,
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const view = ref<IProjectView>()
|
||||
|
||||
const labelStore = useLabelStore()
|
||||
const projectStore = useProjectStore()
|
||||
|
||||
watch(
|
||||
() => modelValue,
|
||||
newValue => {
|
||||
const transformed = {
|
||||
...newValue,
|
||||
filter: transformFilterStringFromApi(
|
||||
newValue.filter,
|
||||
labelId => labelStore.getLabelById(labelId)?.title,
|
||||
projectId => projectStore.projects[projectId]?.title || null,
|
||||
),
|
||||
}
|
||||
|
||||
if (JSON.stringify(view.value) !== JSON.stringify(transformed)) {
|
||||
view.value = transformed
|
||||
}
|
||||
},
|
||||
{immediate: true, deep: true},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => view.value,
|
||||
newView => {
|
||||
emit('update:modelValue', {
|
||||
...newView,
|
||||
filter: transformFilterStringForApi(
|
||||
newView.filter,
|
||||
labelTitle => labelStore.filterLabelsByQuery([], labelTitle)[0]?.id || null,
|
||||
projectTitle => {
|
||||
const found = projectStore.findProjectByExactname(projectTitle)
|
||||
return found?.id || null
|
||||
},
|
||||
),
|
||||
})
|
||||
},
|
||||
{deep: true},
|
||||
)
|
||||
|
||||
const titleValid = ref(true)
|
||||
|
||||
function validateTitle() {
|
||||
titleValid.value = view.value?.title !== ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="title"
|
||||
>
|
||||
{{ $t('project.views.title') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<input
|
||||
id="title"
|
||||
v-model="view.title"
|
||||
v-focus
|
||||
class="input"
|
||||
:placeholder="$t('project.share.links.namePlaceholder')"
|
||||
@blur="validateTitle"
|
||||
>
|
||||
</div>
|
||||
<p
|
||||
v-if="!titleValid"
|
||||
class="help is-danger"
|
||||
>
|
||||
{{ $t('project.views.titleRequired') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="kind"
|
||||
>
|
||||
{{ $t('project.views.kind') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select
|
||||
id="kind"
|
||||
v-model="view.viewKind"
|
||||
>
|
||||
<option value="list">
|
||||
{{ $t('project.list.title') }}
|
||||
</option>
|
||||
<option value="gantt">
|
||||
{{ $t('project.gantt.title') }}
|
||||
</option>
|
||||
<option value="table">
|
||||
{{ $t('project.table.title') }}
|
||||
</option>
|
||||
<option value="kanban">
|
||||
{{ $t('project.kanban.title') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FilterInput
|
||||
v-model="view.filter"
|
||||
:input-label="$t('project.views.filter')"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="view.viewKind === 'kanban'"
|
||||
class="field"
|
||||
>
|
||||
<label
|
||||
class="label"
|
||||
for="configMode"
|
||||
>
|
||||
{{ $t('project.views.bucketConfigMode') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select
|
||||
id="configMode"
|
||||
v-model="view.bucketConfigurationMode"
|
||||
>
|
||||
<option value="manual">
|
||||
{{ $t('project.views.bucketConfigManual') }}
|
||||
</option>
|
||||
<option value="filter">
|
||||
{{ $t('project.views.filter') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="view.viewKind === 'kanban' && view.bucketConfigurationMode === 'filter'"
|
||||
class="field"
|
||||
>
|
||||
<label class="label">
|
||||
{{ $t('project.views.bucketConfig') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<div
|
||||
v-for="(b, index) in view.bucketConfiguration"
|
||||
:key="'bucket_'+index"
|
||||
class="filter-bucket"
|
||||
>
|
||||
<button
|
||||
class="is-danger"
|
||||
@click.prevent="() => view.bucketConfiguration.splice(index, 1)"
|
||||
>
|
||||
<icon icon="trash-alt" />
|
||||
</button>
|
||||
<div class="filter-bucket-form">
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
:for="'bucket_'+index+'_title'"
|
||||
>
|
||||
{{ $t('project.views.title') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<input
|
||||
:id="'bucket_'+index+'_title'"
|
||||
v-model="view.bucketConfiguration[index].title"
|
||||
class="input"
|
||||
:placeholder="$t('project.share.links.namePlaceholder')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FilterInput
|
||||
v-model="view.bucketConfiguration[index].filter"
|
||||
:input-label="$t('project.views.filter')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="is-flex is-justify-content-end">
|
||||
<XButton
|
||||
variant="secondary"
|
||||
icon="plus"
|
||||
@click="() => view.bucketConfiguration.push({title: '', filter: ''})"
|
||||
>
|
||||
{{ $t('project.kanban.addBucket') }}
|
||||
</XButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.filter-bucket {
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--danger);
|
||||
padding-right: .75rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-form {
|
||||
margin-bottom: .5rem;
|
||||
padding: .5rem;
|
||||
border: 1px solid var(--grey-200);
|
||||
border-radius: $radius;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -85,7 +85,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, watchEffect, shallowReactive, type ComponentPublicInstance} from 'vue'
|
||||
import {type ComponentPublicInstance, computed, ref, shallowReactive, watchEffect} from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {useRouter} from 'vue-router'
|
||||
|
||||
|
@ -107,13 +107,14 @@ import {useTaskStore} from '@/stores/tasks'
|
|||
import {useAuthStore} from '@/stores/auth'
|
||||
|
||||
import {getHistory} from '@/modules/projectHistory'
|
||||
import {parseTaskText, PrefixMode, PREFIXES} from '@/modules/parseTaskText'
|
||||
import {parseTaskText, PREFIXES, PrefixMode} from '@/modules/parseTaskText'
|
||||
import {success} from '@/message'
|
||||
|
||||
import type {ITeam} from '@/modelTypes/ITeam'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
import type {IAbstract} from '@/modelTypes/IAbstract'
|
||||
import {isSavedFilter} from '@/services/savedFilter'
|
||||
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
const router = useRouter()
|
||||
|
@ -280,10 +281,13 @@ const commands = computed<{ [key in COMMAND_TYPE]: Command }>(() => ({
|
|||
|
||||
const placeholder = computed(() => selectedCmd.value?.placeholder || t('quickActions.placeholder'))
|
||||
|
||||
const currentProject = computed(() => Object.keys(baseStore.currentProject).length === 0
|
||||
? null
|
||||
: baseStore.currentProject,
|
||||
)
|
||||
const currentProject = computed(() => {
|
||||
if (Object.keys(baseStore.currentProject).length === 0 || isSavedFilter(baseStore.currentProject)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return baseStore.currentProject
|
||||
})
|
||||
|
||||
const hintText = computed(() => {
|
||||
if (selectedCmd.value !== null && currentProject.value !== null) {
|
||||
|
@ -350,26 +354,6 @@ const isNewTaskCommand = computed(() => (
|
|||
|
||||
const taskSearchTimeout = ref<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
type Filter = { by: string, value: string | number, comparator: string }
|
||||
|
||||
function filtersToParams(filters: Filter[]) {
|
||||
const filter_by: Filter['by'][] = []
|
||||
const filter_value: Filter['value'][] = []
|
||||
const filter_comparator: Filter['comparator'][] = []
|
||||
|
||||
filters.forEach(({by, value, comparator}) => {
|
||||
filter_by.push(by)
|
||||
filter_value.push(value)
|
||||
filter_comparator.push(comparator)
|
||||
})
|
||||
|
||||
return {
|
||||
filter_by,
|
||||
filter_value,
|
||||
filter_comparator,
|
||||
}
|
||||
}
|
||||
|
||||
function searchTasks() {
|
||||
if (
|
||||
searchMode.value !== SEARCH_MODE.ALL &&
|
||||
|
@ -391,40 +375,27 @@ function searchTasks() {
|
|||
|
||||
const {text, project: projectName, labels} = parsedQuery.value
|
||||
|
||||
const filters: Filter[] = []
|
||||
|
||||
// FIXME: improve types
|
||||
function addFilter(
|
||||
by: Filter['by'],
|
||||
value: Filter['value'],
|
||||
comparator: Filter['comparator'],
|
||||
) {
|
||||
filters.push({
|
||||
by,
|
||||
value,
|
||||
comparator,
|
||||
})
|
||||
}
|
||||
let filter = ''
|
||||
|
||||
if (projectName !== null) {
|
||||
const project = projectStore.findProjectByExactname(projectName)
|
||||
console.log({project})
|
||||
if (project !== null) {
|
||||
addFilter('project_id', project.id, 'equals')
|
||||
filter += ' project = ' + project.id
|
||||
}
|
||||
}
|
||||
|
||||
if (labels.length > 0) {
|
||||
const labelIds = labelStore.getLabelsByExactTitles(labels).map((l) => l.id)
|
||||
if (labelIds.length > 0) {
|
||||
addFilter('labels', labelIds.join(), 'in')
|
||||
filter += 'labels in ' + labelIds.join(', ')
|
||||
}
|
||||
}
|
||||
|
||||
const params = {
|
||||
s: text,
|
||||
sort_by: 'done',
|
||||
...filtersToParams(filters),
|
||||
filter,
|
||||
}
|
||||
|
||||
taskSearchTimeout.value = setTimeout(async () => {
|
||||
|
|
|
@ -173,11 +173,11 @@
|
|||
<div class="select">
|
||||
<select v-model="selectedView[s.id]">
|
||||
<option
|
||||
v-for="(title, key) in availableViews"
|
||||
:key="key"
|
||||
:value="key"
|
||||
v-for="(view) in availableViews"
|
||||
:key="view.id"
|
||||
:value="view.id"
|
||||
>
|
||||
{{ title }}
|
||||
{{ view.title }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -230,9 +230,9 @@ import LinkShareService from '@/services/linkShare'
|
|||
import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
|
||||
import {success} from '@/message'
|
||||
import {getDisplayName} from '@/models/user'
|
||||
import type {ProjectView} from '@/types/ProjectView'
|
||||
import {PROJECT_VIEWS} from '@/types/ProjectView'
|
||||
import {useConfigStore} from '@/stores/config'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import type {IProjectView} from '@/modelTypes/IProjectView'
|
||||
|
||||
const props = defineProps({
|
||||
projectId: {
|
||||
|
@ -252,17 +252,13 @@ const showDeleteModal = ref(false)
|
|||
const linkIdToDelete = ref(0)
|
||||
const showNewForm = ref(false)
|
||||
|
||||
type SelectedViewMapper = Record<IProject['id'], ProjectView>
|
||||
type SelectedViewMapper = Record<IProject['id'], IProjectView['id']>
|
||||
|
||||
const selectedView = ref<SelectedViewMapper>({})
|
||||
|
||||
const availableViews = computed<Record<ProjectView, string>>(() => ({
|
||||
list: t('project.list.title'),
|
||||
gantt: t('project.gantt.title'),
|
||||
table: t('project.table.title'),
|
||||
kanban: t('project.kanban.title'),
|
||||
}))
|
||||
const projectStore = useProjectStore()
|
||||
|
||||
const availableViews = computed<IProjectView[]>(() => projectStore.projects[props.projectId]?.views || [])
|
||||
const copy = useCopyToClipboard()
|
||||
watch(
|
||||
() => props.projectId,
|
||||
|
@ -281,7 +277,7 @@ async function load(projectId: IProject['id']) {
|
|||
|
||||
const links = await linkShareService.getAll({projectId})
|
||||
links.forEach((l: ILinkShare) => {
|
||||
selectedView.value[l.id] = 'list'
|
||||
selectedView.value[l.id] = availableViews.value[0].id
|
||||
})
|
||||
linkShares.value = links
|
||||
}
|
||||
|
@ -315,8 +311,8 @@ async function remove(projectId: IProject['id']) {
|
|||
}
|
||||
}
|
||||
|
||||
function getShareLink(hash: string, view: ProjectView = PROJECT_VIEWS.LIST) {
|
||||
return frontendUrl.value + 'share/' + hash + '/auth?view=' + view
|
||||
function getShareLink(hash: string, viewId: IProjectView['id']) {
|
||||
return frontendUrl.value + 'share/' + hash + '/auth?view=' + viewId
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -16,7 +16,22 @@
|
|||
:search-results="found"
|
||||
:label="searchLabel"
|
||||
@search="find"
|
||||
/>
|
||||
>
|
||||
<template #searchResult="{option: result}">
|
||||
<User
|
||||
v-if="shareType === 'user'"
|
||||
:avatar-size="24"
|
||||
:show-username="true"
|
||||
:user="result"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="search-result"
|
||||
>
|
||||
{{ result.name }}
|
||||
</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
</p>
|
||||
<p class="control">
|
||||
<x-button @click="add()">
|
||||
|
@ -172,6 +187,8 @@ import Multiselect from '@/components/input/multiselect.vue'
|
|||
import Nothing from '@/components/misc/nothing.vue'
|
||||
import {success} from '@/message'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useConfigStore} from '@/stores/config'
|
||||
import User from '@/components/misc/user.vue'
|
||||
|
||||
// FIXME: I think this whole thing can now only manage user/team sharing for projects? Maybe remove a little generalization?
|
||||
|
||||
|
@ -210,8 +227,8 @@ const selectedRight = ref({})
|
|||
const sharables = ref([])
|
||||
const showDeleteModal = ref(false)
|
||||
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const configStore = useConfigStore()
|
||||
const userInfo = computed(() => authStore.info)
|
||||
|
||||
function createShareTypeNameComputed(count: number) {
|
||||
|
@ -360,7 +377,15 @@ async function find(query: string) {
|
|||
found.value = []
|
||||
return
|
||||
}
|
||||
const results = await searchService.getAll({}, {s: query})
|
||||
|
||||
// Include public teams here if we are sharing with teams and its enabled in the config
|
||||
let results = []
|
||||
if (props.shareType === 'team' && configStore.publicTeamsEnabled) {
|
||||
results = await searchService.getAll({}, {s: query, includePublic: true})
|
||||
} else {
|
||||
results = await searchService.getAll({}, {s: query})
|
||||
}
|
||||
|
||||
found.value = results
|
||||
.filter(m => {
|
||||
if(props.shareType === 'user' && m.id === currentUserId.value) {
|
||||
|
|
|
@ -21,12 +21,12 @@
|
|||
@dragendBar="updateGanttTask"
|
||||
@dblclickBar="openTask"
|
||||
>
|
||||
<template #timeunit="{value, date}">
|
||||
<template #timeunit="{date}">
|
||||
<div
|
||||
class="timeunit-wrapper"
|
||||
:class="{'today': dateIsToday(date)}"
|
||||
>
|
||||
<span>{{ value }}</span>
|
||||
<span>{{ date.getDate() }}</span>
|
||||
<span class="weekday">
|
||||
{{ weekDayFromDate(date) }}
|
||||
</span>
|
||||
|
|
|
@ -97,6 +97,13 @@
|
|||
editComment()
|
||||
}"
|
||||
/>
|
||||
<Reactions
|
||||
v-model="c.reactions"
|
||||
class="mt-2"
|
||||
entity-kind="comments"
|
||||
:entity-id="c.id"
|
||||
:disabled="!canWrite"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -190,6 +197,7 @@ import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
|||
import {getAvatarUrl, getDisplayName} from '@/models/user'
|
||||
import {useConfigStore} from '@/stores/config'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import Reactions from '@/components/input/Reactions.vue'
|
||||
|
||||
const props = defineProps({
|
||||
taskId: {
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, watch} from 'vue'
|
||||
import {ref, computed, watch, onBeforeUnmount} from 'vue'
|
||||
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import Editor from '@/components/input/AsyncEditor'
|
||||
|
@ -88,6 +88,12 @@ async function saveWithDelay() {
|
|||
}, 5000)
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (changeTimeout.value !== null) {
|
||||
clearTimeout(changeTimeout.value)
|
||||
}
|
||||
})
|
||||
|
||||
async function save() {
|
||||
if (changeTimeout.value !== null) {
|
||||
clearTimeout(changeTimeout.value)
|
||||
|
|
|
@ -47,7 +47,6 @@
|
|||
<ReminderPeriod
|
||||
v-if="activeForm === 'relative'"
|
||||
v-model="reminder"
|
||||
@update:modelValue="updateDataAndMaybeClose(close)"
|
||||
/>
|
||||
|
||||
<DatepickerInline
|
||||
|
@ -60,7 +59,7 @@
|
|||
v-if="showFormSwitch !== null"
|
||||
class="reminder__close-button"
|
||||
:shadow="false"
|
||||
@click="updateDataAndMaybeClose(close)"
|
||||
@click="updateDataAndMaybeCloseNow(close)"
|
||||
>
|
||||
{{ $t('misc.confirm') }}
|
||||
</x-button>
|
||||
|
@ -73,10 +72,10 @@
|
|||
<script setup lang="ts">
|
||||
import {computed, ref, watch} from 'vue'
|
||||
import {SECONDS_A_DAY, SECONDS_A_HOUR} from '@/constants/date'
|
||||
import {IReminderPeriodRelativeTo, REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo'
|
||||
import {type IReminderPeriodRelativeTo, REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import {PeriodUnit, secondsToPeriod} from '@/helpers/time/period'
|
||||
import {type PeriodUnit, secondsToPeriod} from '@/helpers/time/period'
|
||||
import type {ITaskReminder} from '@/modelTypes/ITaskReminder'
|
||||
import {formatDateShort} from '@/helpers/time/formatDate'
|
||||
|
||||
|
@ -87,6 +86,7 @@ import Popup from '@/components/misc/popup.vue'
|
|||
import TaskReminderModel from '@/models/taskReminder'
|
||||
import Card from '@/components/misc/card.vue'
|
||||
import SimpleButton from '@/components/input/SimpleButton.vue'
|
||||
import {useDebounceFn} from '@vueuse/core'
|
||||
|
||||
const {
|
||||
modelValue,
|
||||
|
@ -112,7 +112,7 @@ const presets = computed<TaskReminderModel[]>(() => [
|
|||
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 7, relativeTo: defaultRelativeTo},
|
||||
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 30, relativeTo: defaultRelativeTo},
|
||||
])
|
||||
const reminderDate = ref<Date|null>(null)
|
||||
const reminderDate = ref<Date | null>(null)
|
||||
|
||||
type availableForms = null | 'relative' | 'absolute'
|
||||
|
||||
|
@ -142,16 +142,16 @@ const reminderText = computed(() => {
|
|||
watch(
|
||||
() => modelValue,
|
||||
(newReminder) => {
|
||||
if(newReminder) {
|
||||
if (newReminder) {
|
||||
reminder.value = newReminder
|
||||
|
||||
if(newReminder.relativeTo === null) {
|
||||
|
||||
if (newReminder.relativeTo === null) {
|
||||
reminderDate.value = new Date(newReminder.reminder)
|
||||
}
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
reminder.value = new TaskReminderModel()
|
||||
},
|
||||
{immediate: true},
|
||||
|
@ -181,7 +181,9 @@ function setReminderFromPreset(preset, close) {
|
|||
close()
|
||||
}
|
||||
|
||||
function updateDataAndMaybeClose(close) {
|
||||
const updateDataAndMaybeClose = useDebounceFn(updateDataAndMaybeCloseNow, 500)
|
||||
|
||||
function updateDataAndMaybeCloseNow(close) {
|
||||
updateData()
|
||||
if (clearAfterUpdate) {
|
||||
close()
|
||||
|
|
|
@ -71,8 +71,7 @@ import {periodToSeconds, PeriodUnit, secondsToPeriod} from '@/helpers/time/perio
|
|||
import TaskReminderModel from '@/models/taskReminder'
|
||||
|
||||
import type {ITaskReminder} from '@/modelTypes/ITaskReminder'
|
||||
import {REMINDER_PERIOD_RELATIVE_TO_TYPES, type IReminderPeriodRelativeTo} from '@/types/IReminderPeriodRelativeTo'
|
||||
import {useDebounceFn} from '@vueuse/core'
|
||||
import {type IReminderPeriodRelativeTo, REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo'
|
||||
|
||||
const {
|
||||
modelValue,
|
||||
|
@ -123,7 +122,7 @@ function updateData() {
|
|||
reminder.value.relativeTo = period.value.relativeTo
|
||||
reminder.value.reminder = null
|
||||
|
||||
useDebounceFn(() => emit('update:modelValue', reminder.value), 1000)
|
||||
emit('update:modelValue', reminder.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<router-link
|
||||
v-if="showProject && typeof project !== 'undefined'"
|
||||
v-tooltip="$t('task.detail.belongsToProject', {project: project.title})"
|
||||
:to="{ name: 'project.list', params: { projectId: task.projectId } }"
|
||||
:to="{ name: 'project.index', params: { projectId: task.projectId } }"
|
||||
class="task-project mr-1"
|
||||
:class="{'mr-2': task.hexColor !== ''}"
|
||||
>
|
||||
|
@ -136,7 +136,7 @@
|
|||
<router-link
|
||||
v-if="showProjectSeparately"
|
||||
v-tooltip="$t('task.detail.belongsToProject', {project: project.title})"
|
||||
:to="{ name: 'project.list', params: { projectId: task.projectId } }"
|
||||
:to="{ name: 'project.index', params: { projectId: task.projectId } }"
|
||||
class="task-project"
|
||||
>
|
||||
{{ project.title }}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import {computed, shallowRef, watchEffect, h, type VNode} from 'vue'
|
||||
import {computed, h, shallowRef, type VNode, watchEffect} from 'vue'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
||||
export function useRouteWithModal() {
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const backdropView = computed(() => route.fullPath && window.history.state.backdropView)
|
||||
const baseStore = useBaseStore()
|
||||
const projectStore = useProjectStore()
|
||||
|
||||
const routeWithModal = computed(() => {
|
||||
return backdropView.value
|
||||
|
@ -29,7 +31,7 @@ export function useRouteWithModal() {
|
|||
if (routePropsOption === true) {
|
||||
routeProps = route.params
|
||||
} else {
|
||||
if(typeof routePropsOption === 'function') {
|
||||
if (typeof routePropsOption === 'function') {
|
||||
routeProps = routePropsOption(route)
|
||||
} else {
|
||||
routeProps = routePropsOption
|
||||
|
@ -52,7 +54,7 @@ export function useRouteWithModal() {
|
|||
}
|
||||
currentModal.value = h(component, routeProps)
|
||||
})
|
||||
|
||||
|
||||
const historyState = computed(() => route.fullPath && window.history.state)
|
||||
|
||||
function closeModal() {
|
||||
|
@ -60,12 +62,23 @@ export function useRouteWithModal() {
|
|||
// If the current project was changed because the user moved the currently opened task while coming from kanban,
|
||||
// we need to reflect that change in the route when they close the task modal.
|
||||
// The last route is only available as resolved string, therefore we need to use a regex for matching here
|
||||
const kanbanRouteMatch = new RegExp('\\/projects\\/\\d+\\/kanban', 'g')
|
||||
const kanbanRouter = {name: 'project.kanban', params: {projectId: baseStore.currentProject?.id}}
|
||||
if (kanbanRouteMatch.test(historyState.value.back)
|
||||
&& baseStore.currentProject
|
||||
&& historyState.value.back !== router.resolve(kanbanRouter).fullPath) {
|
||||
router.push(kanbanRouter)
|
||||
const routeMatch = new RegExp('\\/projects\\/\\d+\\/(\\d+)', 'g')
|
||||
const match = routeMatch.exec(historyState.value.back)
|
||||
if (match !== null && baseStore.currentProject) {
|
||||
let viewId: string | number = match[1]
|
||||
|
||||
if (!viewId) {
|
||||
viewId = projectStore.projects[baseStore.currentProject?.id].views[0]?.id
|
||||
}
|
||||
|
||||
const newRoute = {
|
||||
name: 'project.view',
|
||||
params: {
|
||||
projectId: baseStore.currentProject?.id,
|
||||
viewId,
|
||||
},
|
||||
}
|
||||
router.push(newRoute)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,12 @@ import {ref, shallowReactive, watch, computed, type ComputedGetter} from 'vue'
|
|||
import {useRoute} from 'vue-router'
|
||||
import {useRouteQuery} from '@vueuse/router'
|
||||
|
||||
import TaskCollectionService, {getDefaultTaskFilterParams} from '@/services/taskCollection'
|
||||
import TaskCollectionService, {getDefaultTaskFilterParams, type TaskFilterParams} from '@/services/taskCollection'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import {error} from '@/message'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import type {IProjectView} from '@/modelTypes/IProjectView'
|
||||
|
||||
export type Order = 'asc' | 'desc' | 'none'
|
||||
|
||||
|
@ -53,11 +55,16 @@ const SORT_BY_DEFAULT: SortBy = {
|
|||
/**
|
||||
* This mixin provides a base set of methods and properties to get tasks.
|
||||
*/
|
||||
export function useTaskList(projectIdGetter: ComputedGetter<IProject['id']>, sortByDefault: SortBy = SORT_BY_DEFAULT) {
|
||||
export function useTaskList(
|
||||
projectIdGetter: ComputedGetter<IProject['id']>,
|
||||
projectViewIdGetter: ComputedGetter<IProjectView['id']>,
|
||||
sortByDefault: SortBy = SORT_BY_DEFAULT,
|
||||
) {
|
||||
|
||||
const projectId = computed(() => projectIdGetter())
|
||||
const projectViewId = computed(() => projectViewIdGetter())
|
||||
|
||||
const params = ref({...getDefaultTaskFilterParams()})
|
||||
const params = ref<TaskFilterParams>({...getDefaultTaskFilterParams()})
|
||||
|
||||
const search = ref('')
|
||||
const page = useRouteQuery('page', '1', { transform: Number })
|
||||
|
@ -81,11 +88,19 @@ export function useTaskList(projectIdGetter: ComputedGetter<IProject['id']>, sor
|
|||
page.value = 1
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const getAllTasksParams = computed(() => {
|
||||
return [
|
||||
{projectId: projectId.value},
|
||||
allParams.value,
|
||||
{
|
||||
projectId: projectId.value,
|
||||
viewId: projectViewId.value,
|
||||
},
|
||||
{
|
||||
...allParams.value,
|
||||
filter_timezone: authStore.settings.timezone,
|
||||
},
|
||||
page.value,
|
||||
]
|
||||
})
|
||||
|
|
|
@ -17,8 +17,8 @@ describe('Filter Transformation', () => {
|
|||
'assignees': 'assignees',
|
||||
'labels': 'labels',
|
||||
}
|
||||
|
||||
describe('For api', () => {
|
||||
|
||||
describe('For API', () => {
|
||||
for (const c in fieldCases) {
|
||||
it('should transform all filter params for ' + c + ' to snake_case', () => {
|
||||
const transformed = transformFilterStringForApi(c + ' = ipsum', nullTitleToIdResolver, nullTitleToIdResolver)
|
||||
|
@ -37,23 +37,35 @@ describe('Filter Transformation', () => {
|
|||
expect(transformed).toBe('labels = 1')
|
||||
})
|
||||
|
||||
const multipleDummyResolver = (title: string) => {
|
||||
switch (title) {
|
||||
case 'lorem':
|
||||
return 1
|
||||
case 'ipsum':
|
||||
return 2
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
it('should correctly resolve multiple labels', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'labels = lorem && dueDate = now && labels = ipsum',
|
||||
(title: string) => {
|
||||
switch (title) {
|
||||
case 'lorem':
|
||||
return 1
|
||||
case 'ipsum':
|
||||
return 2
|
||||
default:
|
||||
return null
|
||||
}
|
||||
},
|
||||
multipleDummyResolver,
|
||||
nullTitleToIdResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('labels = 1&& due_date = now && labels = 2')
|
||||
expect(transformed).toBe('labels = 1 && due_date = now && labels = 2')
|
||||
})
|
||||
|
||||
it('should correctly resolve multiple labels with an in clause', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'labels in lorem, ipsum && dueDate = now',
|
||||
multipleDummyResolver,
|
||||
nullTitleToIdResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('labels in 1, 2 && due_date = now')
|
||||
})
|
||||
|
||||
it('should correctly resolve projects', () => {
|
||||
|
@ -70,19 +82,50 @@ describe('Filter Transformation', () => {
|
|||
const transformed = transformFilterStringForApi(
|
||||
'project = lorem && dueDate = now || project = ipsum',
|
||||
nullTitleToIdResolver,
|
||||
(title: string) => {
|
||||
switch (title) {
|
||||
case 'lorem':
|
||||
return 1
|
||||
case 'ipsum':
|
||||
return 2
|
||||
default:
|
||||
return null
|
||||
}
|
||||
},
|
||||
multipleDummyResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project = 1&& due_date = now || project = 2')
|
||||
expect(transformed).toBe('project = 1 && due_date = now || project = 2')
|
||||
})
|
||||
|
||||
it('should correctly resolve multiple projects with in', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'project in lorem, ipsum',
|
||||
nullTitleToIdResolver,
|
||||
multipleDummyResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project in 1, 2')
|
||||
})
|
||||
|
||||
it('should resolve projects at the correct position', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'project = pr',
|
||||
nullTitleToIdResolver,
|
||||
(title: string) => 1,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project = 1')
|
||||
})
|
||||
|
||||
it('should resolve project and labels independently', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'project = lorem && labels = ipsum',
|
||||
multipleDummyResolver,
|
||||
multipleDummyResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project = 1 && labels = 2')
|
||||
})
|
||||
|
||||
it('should transform the same attribute multiple times', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'dueDate = now/d || dueDate > now/w+1w',
|
||||
nullTitleToIdResolver,
|
||||
nullTitleToIdResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('due_date = now/d || due_date > now/w+1w')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -104,24 +147,36 @@ describe('Filter Transformation', () => {
|
|||
|
||||
expect(transformed).toBe('labels = lorem')
|
||||
})
|
||||
|
||||
|
||||
const multipleIdToTitleResolver = (id: number) => {
|
||||
switch (id) {
|
||||
case 1:
|
||||
return 'lorem'
|
||||
case 2:
|
||||
return 'ipsum'
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
it('should correctly resolve multiple labels', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'labels = 1 && due_date = now && labels = 2',
|
||||
(id: number) => {
|
||||
switch (id) {
|
||||
case 1:
|
||||
return 'lorem'
|
||||
case 2:
|
||||
return 'ipsum'
|
||||
default:
|
||||
return null
|
||||
}
|
||||
},
|
||||
multipleIdToTitleResolver,
|
||||
nullIdToTitleResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('labels = lorem&& dueDate = now && labels = ipsum')
|
||||
expect(transformed).toBe('labels = lorem && dueDate = now && labels = ipsum')
|
||||
})
|
||||
|
||||
it('should correctly resolve multiple labels in', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'labels in 1, 2',
|
||||
multipleIdToTitleResolver,
|
||||
nullIdToTitleResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('labels in lorem, ipsum')
|
||||
})
|
||||
|
||||
it('should correctly resolve projects', () => {
|
||||
|
@ -136,21 +191,32 @@ describe('Filter Transformation', () => {
|
|||
|
||||
it('should correctly resolve multiple projects', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'project = lorem && due_date = now || project = ipsum',
|
||||
'project = 1 && due_date = now || project = 2',
|
||||
nullIdToTitleResolver,
|
||||
(id: number) => {
|
||||
switch (id) {
|
||||
case 1:
|
||||
return 'lorem'
|
||||
case 2:
|
||||
return 'ipsum'
|
||||
default:
|
||||
return null
|
||||
}
|
||||
},
|
||||
multipleIdToTitleResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project = lorem && dueDate = now || project = ipsum')
|
||||
})
|
||||
|
||||
it('should correctly resolve multiple projects in', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'project in 1, 2',
|
||||
nullIdToTitleResolver,
|
||||
multipleIdToTitleResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project in lorem, ipsum')
|
||||
})
|
||||
|
||||
it('should transform the same attribute multiple times', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'due_date = now/d || due_date > now/w+1w',
|
||||
nullIdToTitleResolver,
|
||||
nullIdToTitleResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('dueDate = now/d || dueDate > now/w+1w')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -27,12 +27,13 @@ export const AUTOCOMPLETE_FIELDS = [
|
|||
]
|
||||
|
||||
export const AVAILABLE_FILTER_FIELDS = [
|
||||
'done',
|
||||
'priority',
|
||||
'percentDone',
|
||||
...DATE_FIELDS,
|
||||
...ASSIGNEE_FIELDS,
|
||||
...LABEL_FIELDS,
|
||||
...PROJECT_FIELDS,
|
||||
'done',
|
||||
'priority',
|
||||
'percentDone',
|
||||
]
|
||||
|
||||
export const FILTER_OPERATORS = [
|
||||
|
@ -54,10 +55,10 @@ export const FILTER_JOIN_OPERATOR = [
|
|||
')',
|
||||
]
|
||||
|
||||
export const FILTER_OPERATORS_REGEX = '(<|>|<=|>=|=|!=)'
|
||||
export const FILTER_OPERATORS_REGEX = '(<|>|<=|>=|=|!=|in)'
|
||||
|
||||
function getFieldPattern(field: string): RegExp {
|
||||
return new RegExp('(' + field + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&|()]+\\1?)?', 'ig')
|
||||
export function getFilterFieldRegexPattern(field: string): RegExp {
|
||||
return new RegExp('(' + field + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&|()<]+\\1?)?', 'ig')
|
||||
}
|
||||
|
||||
export function transformFilterStringForApi(
|
||||
|
@ -65,47 +66,68 @@ export function transformFilterStringForApi(
|
|||
labelResolver: (title: string) => number | null,
|
||||
projectResolver: (title: string) => number | null,
|
||||
): string {
|
||||
|
||||
|
||||
if (filter.trim() === '') {
|
||||
return ''
|
||||
}
|
||||
|
||||
|
||||
// Transform labels to ids
|
||||
LABEL_FIELDS.forEach(field => {
|
||||
const pattern = getFieldPattern(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) {
|
||||
const labelId = labelResolver(keyword.trim())
|
||||
if (labelId !== null) {
|
||||
filter = filter.replace(keyword, String(labelId))
|
||||
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 = getFieldPattern(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) {
|
||||
const projectId = projectResolver(keyword.trim())
|
||||
if (projectId !== null) {
|
||||
filter = filter.replace(keyword, String(projectId))
|
||||
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 actualKeywordStart = (match?.index || 0) + prefix.length
|
||||
filter = filter.substring(0, actualKeywordStart) +
|
||||
replaced +
|
||||
filter.substring(actualKeywordStart + keyword.length)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Transform all attributes to snake case
|
||||
AVAILABLE_FILTER_FIELDS.forEach(f => {
|
||||
filter = filter.replace(f, snakeCase(f))
|
||||
filter = filter.replaceAll(f, snakeCase(f))
|
||||
})
|
||||
|
||||
return filter
|
||||
|
@ -116,46 +138,60 @@ export function transformFilterStringFromApi(
|
|||
labelResolver: (id: number) => string | null,
|
||||
projectResolver: (id: number) => string | null,
|
||||
): string {
|
||||
|
||||
|
||||
if (filter.trim() === '') {
|
||||
return ''
|
||||
}
|
||||
|
||||
|
||||
// Transform all attributes from snake case
|
||||
AVAILABLE_FILTER_FIELDS.forEach(f => {
|
||||
filter = filter.replace(snakeCase(f), f)
|
||||
filter = filter.replaceAll(snakeCase(f), f)
|
||||
})
|
||||
|
||||
|
||||
// Transform labels to their titles
|
||||
LABEL_FIELDS.forEach(field => {
|
||||
const pattern = getFieldPattern(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) {
|
||||
const labelTitle = labelResolver(Number(keyword.trim()))
|
||||
if (labelTitle !== null) {
|
||||
filter = filter.replace(keyword, labelTitle)
|
||||
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 = getFieldPattern(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) {
|
||||
const project = projectResolver(Number(keyword.trim()))
|
||||
if (project !== null) {
|
||||
filter = filter.replace(keyword, project)
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,64 +1,17 @@
|
|||
import type { RouteRecordName } from 'vue-router'
|
||||
import router from '@/router'
|
||||
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
|
||||
export type ProjectRouteName = Extract<RouteRecordName, string>
|
||||
export type ProjectViewSettings = Record<
|
||||
IProject['id'],
|
||||
Extract<RouteRecordName, ProjectRouteName>
|
||||
>
|
||||
export type ProjectViewSettings = Record<IProject['id'], number>
|
||||
|
||||
const SETTINGS_KEY_PROJECT_VIEW = 'projectView'
|
||||
|
||||
// TODO: remove migration when releasing 1.0
|
||||
type ListViewSettings = ProjectViewSettings
|
||||
const SETTINGS_KEY_DEPRECATED_LIST_VIEW = 'listView'
|
||||
function migrateStoredProjectRouteSettings() {
|
||||
try {
|
||||
const listViewSettingsString = localStorage.getItem(SETTINGS_KEY_DEPRECATED_LIST_VIEW)
|
||||
if (listViewSettingsString === null) {
|
||||
return
|
||||
}
|
||||
|
||||
// A) the first version stored one setting for all lists in a string
|
||||
if (listViewSettingsString.startsWith('list.')) {
|
||||
const projectView = listViewSettingsString.replace('list.', 'project.')
|
||||
|
||||
if (!router.hasRoute(projectView)) {
|
||||
return
|
||||
}
|
||||
return projectView as RouteRecordName
|
||||
}
|
||||
|
||||
// B) the last version used a 'list.' prefix
|
||||
const listViewSettings: ListViewSettings = JSON.parse(listViewSettingsString)
|
||||
|
||||
const projectViewSettingEntries = Object.entries(listViewSettings).map(([id, value]) => {
|
||||
return [id, value.replace('list.', 'project.')]
|
||||
})
|
||||
const projectViewSettings = Object.fromEntries(projectViewSettingEntries)
|
||||
|
||||
localStorage.setItem(SETTINGS_KEY_PROJECT_VIEW, JSON.stringify(projectViewSettings))
|
||||
} catch(e) {
|
||||
//
|
||||
} finally {
|
||||
localStorage.removeItem(SETTINGS_KEY_DEPRECATED_LIST_VIEW)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current project view to local storage
|
||||
*/
|
||||
export function saveProjectView(projectId: IProject['id'], routeName: string) {
|
||||
if (routeName.includes('settings.')) {
|
||||
export function saveProjectView(projectId: IProject['id'], viewId: number) {
|
||||
if (!projectId || !viewId) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!projectId) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// We use local storage and not the store here to make it persistent across reloads.
|
||||
const savedProjectView = localStorage.getItem(SETTINGS_KEY_PROJECT_VIEW)
|
||||
let savedProjectViewSettings: ProjectViewSettings | false = false
|
||||
|
@ -71,30 +24,19 @@ export function saveProjectView(projectId: IProject['id'], routeName: string) {
|
|||
projectViewSettings = savedProjectViewSettings
|
||||
}
|
||||
|
||||
projectViewSettings[projectId] = routeName
|
||||
projectViewSettings[projectId] = viewId
|
||||
localStorage.setItem(SETTINGS_KEY_PROJECT_VIEW, JSON.stringify(projectViewSettings))
|
||||
}
|
||||
|
||||
export const getProjectView = (projectId: IProject['id']) => {
|
||||
// TODO: remove migration when releasing 1.0
|
||||
const migratedProjectView = migrateStoredProjectRouteSettings()
|
||||
|
||||
if (migratedProjectView !== undefined && router.hasRoute(migratedProjectView)) {
|
||||
return migratedProjectView
|
||||
export function getProjectViewId(projectId: IProject['id']): number {
|
||||
const projectViewSettingsString = localStorage.getItem(SETTINGS_KEY_PROJECT_VIEW)
|
||||
if (!projectViewSettingsString) {
|
||||
return 0
|
||||
}
|
||||
|
||||
try {
|
||||
const projectViewSettingsString = localStorage.getItem(SETTINGS_KEY_PROJECT_VIEW)
|
||||
if (!projectViewSettingsString) {
|
||||
throw new Error()
|
||||
}
|
||||
|
||||
const projectViewSettings = JSON.parse(projectViewSettingsString) as ProjectViewSettings
|
||||
if (!router.hasRoute(projectViewSettings[projectId])) {
|
||||
throw new Error()
|
||||
}
|
||||
return projectViewSettings[projectId]
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
const projectViewSettings = JSON.parse(projectViewSettingsString) as ProjectViewSettings
|
||||
if (isNaN(projectViewSettings[projectId])) {
|
||||
return 0
|
||||
}
|
||||
return projectViewSettings[projectId]
|
||||
}
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "هذا يشمل جميع المهام ولا يمكن التراجع عن هذا الإجراء!",
|
||||
"success": "تم حذف المشروع بنجاح.",
|
||||
"tasksToDelete": "سيؤدي هذا إلى حذف ما يقارب {count} من المهام.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "هذا المشروع لا يحتوي على أي مهام، يمكن حذفه بشكل آمن."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "الفلاتر",
|
||||
"clear": "مسح الفلاتر",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "العنوان",
|
||||
"titlePlaceholder": "عنوان الفلتر المحفوظ هنا…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "تعديل هذا الفلتر المحفوظ",
|
||||
"success": "تم حفظ الفلتر بنجاح."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "إلى",
|
||||
"from": "من",
|
||||
"fromto": "{from} إلى {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "اليوم",
|
||||
"thisWeek": "هذا الأسبوع",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "الشهر الماضي",
|
||||
"thisYear": "هذه السنة",
|
||||
"restOfThisYear": "المتبقي من هذه السنة"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "الوصف",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "مشرف",
|
||||
"member": "عضو"
|
||||
"member": "عضو",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "مشروع جديد",
|
||||
"createProject": "إنشاء مشروع",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "رابط Vikunja",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "خطأ",
|
||||
"success": "تم بنجاح",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -57,11 +57,11 @@
|
|||
"logout": "Odhlásit se",
|
||||
"emailInvalid": "Prosím zadejte platnou emailovou adresu.",
|
||||
"usernameRequired": "Zadejte prosím uživatelské jméno.",
|
||||
"usernameMustNotContainSpace": "The username must not contain spaces.",
|
||||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"usernameMustNotContainSpace": "Uživatelské jméno nesmí obsahovat mezery.",
|
||||
"usernameMustNotLookLikeUrl": "Uživatelské jméno nesmí vypadat jako adresa URL.",
|
||||
"passwordRequired": "Zadejte prosím heslo.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMin": "Heslo musí mít nejméně 8 znaků.",
|
||||
"passwordNotMax": "Heslo může mít maximálně 250 znaků.",
|
||||
"showPassword": "Ukázat heslo",
|
||||
"hidePassword": "Skrýt heslo",
|
||||
"noAccountYet": "Ještě nemáte účet?",
|
||||
|
@ -248,6 +248,7 @@
|
|||
"text2": "To zahrnuje všechny úkoly a JE TO NEVRATNÉ!",
|
||||
"success": "Projekt byl úspěšně smazán.",
|
||||
"tasksToDelete": "Neodvolatelně tím odstraníme asi {count} úloh.",
|
||||
"tasksAndChildProjectsToDelete": "Chystáte se neodvolatelně odstranit nějaké úkoly (cca {tasks}) a projekty (cca {projects}).",
|
||||
"noTasksToDelete": "Tento projekt neobsahuje žádné úkoly, mělo by být bezpečné ho smazat."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Tajný klíč",
|
||||
"secretHint": "Pokud je zadáno, všechny požadavky na cílovou adresu URL webhooku budou podepsány pomocí HMAC.",
|
||||
"secretDocs": "Další podrobnosti o používání tajných klíčů naleznete v dokumentaci."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filtry",
|
||||
"clear": "Vymazat filtry",
|
||||
"showResults": "Zobrazit výsledky",
|
||||
"attributes": {
|
||||
"title": "Název",
|
||||
"titlePlaceholder": "Název uloženého filtru přijde sem…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Upravit tento uložený filtr",
|
||||
"success": "Filtr byl úspěšně uložen."
|
||||
},
|
||||
"query": {
|
||||
"title": "Dotaz",
|
||||
"placeholder": "Zadejte vyhledávací nebo filtrační dotaz…",
|
||||
"help": {
|
||||
"intro": "K filtrování úkolů můžete použít syntaxi dotazů podobnou SQL. Dostupná pole pro filtrování zahrnují:",
|
||||
"link": "Jak to funguje?",
|
||||
"canUseDatemath": "Můžete použít matematiku a nastavit relativní data. Pro více informací klikněte na datum v dotazu.",
|
||||
"fields": {
|
||||
"done": "Zda je úkol dokončen nebo ne",
|
||||
"priority": "Úroveň priority úkolu (1-5)",
|
||||
"percentDone": "Procento dokončení úkolu (0-100)",
|
||||
"dueDate": "Datum dokončení úkolu",
|
||||
"startDate": "Datum zahájení úkolu",
|
||||
"endDate": "Datum dokončení úkolu",
|
||||
"doneAt": "Datum a čas, kdy byl úkol dokončen",
|
||||
"assignees": "Přiřazení uživatelé",
|
||||
"labels": "Štítky přiřazené úkolu",
|
||||
"project": "Projekt, do kterého úkol patří (k dispozici pouze pro uložené filtry, ne na úrovni projektu)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "Dostupné operátory pro filtrování zahrnují:",
|
||||
"notEqual": "Nerovná se",
|
||||
"equal": "Rovná se",
|
||||
"greaterThan": "Větší než",
|
||||
"greaterThanOrEqual": "Větší nebo rovno než",
|
||||
"lessThan": "Menší než",
|
||||
"lessThanOrEqual": "Menší nebo rovno než",
|
||||
"like": "Odpovídá vzoru (s použitím zástupného znaku %)",
|
||||
"in": "Odpovídá libovolné hodnotě v seznamu hodnot oddělených čárkou"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "Pro kombinování více podmínek můžete použít tyto logické operátory:",
|
||||
"and": "AND - shoduje se, pokud jsou splněny všechny podmínky",
|
||||
"or": "OR - shoduje se, pokud je splněna alespoň jedna podmínka",
|
||||
"parentheses": "Závorky seskupují podmínky"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Zde jsou některé příklady filtrovacích dotazů:",
|
||||
"priorityEqual": "Odpovídá úkolům s úrovní priority 4",
|
||||
"dueDatePast": "Odpovídá úkolům s termínem dokončení v minulosti",
|
||||
"undoneHighPriority": "Odpovídá nedokončeným úkolům s úrovní priority 3 nebo vyšší",
|
||||
"assigneesIn": "Odpovídá úkolům přiřazeným buď \"uživatel1\", nebo \"uživatel2\"",
|
||||
"priorityOneOrTwoPastDue": "Odpovídá úkolům s prioritou 1 nebo 2 a termínem dokončení v minulosti"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "Do",
|
||||
"from": "Od",
|
||||
"fromto": "{from} – {to}",
|
||||
"date": "Datum",
|
||||
"ranges": {
|
||||
"today": "Dnes",
|
||||
"thisWeek": "Tento týden",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Minulý měsíc",
|
||||
"thisYear": "Tento rok",
|
||||
"restOfThisYear": "Zbytek tohoto roku"
|
||||
},
|
||||
"values": {
|
||||
"now": "Teď",
|
||||
"startOfToday": "Začátek dnes",
|
||||
"endOfToday": "Konec dnes",
|
||||
"beginningOflastWeek": "Začátek minulého týdne",
|
||||
"endOfLastWeek": "Konec posledního týdne",
|
||||
"beginningOfThisWeek": "Začátek tohoto týdne",
|
||||
"endOfThisWeek": "Konec tohoto týdne",
|
||||
"startOfNextWeek": "Začátek příštího týdne",
|
||||
"endOfNextWeek": "Konec tohoto týdne",
|
||||
"in7Days": "Za 7 dní",
|
||||
"beginningOfLastMonth": "Začátek minulého měsíce",
|
||||
"endOfLastMonth": "Konec minulého měsíce",
|
||||
"startOfThisMonth": "Začátek tohoto měsíce",
|
||||
"endOfThisMonth": "Konec tohoto měsíce",
|
||||
"startOfNextMonth": "Začátek příštího měsíce",
|
||||
"endOfNextMonth": "Konec příštího měsíce",
|
||||
"in30Days": "Za 30 dní",
|
||||
"startOfThisYear": "Začátek tohoto roku",
|
||||
"endOfThisYear": "Konec tohoto roku"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -713,7 +799,7 @@
|
|||
"startDate": "Počáteční datum",
|
||||
"title": "Název",
|
||||
"updated": "Aktualizováno",
|
||||
"doneAt": "Done At"
|
||||
"doneAt": "Dokončeno"
|
||||
},
|
||||
"subscription": {
|
||||
"subscribedTaskThroughParentProject": "Zde se nemůžete odhlásit, protože jste přihlášeni k odběru tohoto úkolu prostřednictvím jeho projektu.",
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Popis",
|
||||
"descriptionPlaceholder": "Popište tým, stiskněte '/' pro více možností…",
|
||||
"admin": "Administrátor",
|
||||
"member": "Člen"
|
||||
"member": "Člen",
|
||||
"isPublic": "Veřejný tým",
|
||||
"isPublicDescription": "Učinit tým veřejně dostupným. Pokud je povoleno, každý může s tímto týmem sdílet projekty, i když není přímým členem."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -975,8 +1063,9 @@
|
|||
"share": "Sdílet",
|
||||
"newProject": "Nový projekt",
|
||||
"createProject": "Vytvořit projekt",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantArchiveIsDefault": "Nemůžete archivovat svůj výchozí projekt.",
|
||||
"cantDeleteIsDefault": "Nemůžete smazat svůj výchozí projekt.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Chyba",
|
||||
"success": "Úspěch",
|
||||
|
@ -1096,7 +1191,7 @@
|
|||
},
|
||||
"about": {
|
||||
"title": "O aplikaci",
|
||||
"version": "Version: {version}"
|
||||
"version": "Verze: {version}"
|
||||
},
|
||||
"time": {
|
||||
"units": {
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filtre",
|
||||
"clear": "Ryd Filtre",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Titel",
|
||||
"titlePlaceholder": "Det gemte filters titel skrives her…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Rediger Dette Gemte Filter",
|
||||
"success": "Filteret blev slettet."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "Til",
|
||||
"from": "Fra",
|
||||
"fromto": "{from} til {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "I dag",
|
||||
"thisWeek": "Denne uge",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Sidste måned",
|
||||
"thisYear": "Dette år",
|
||||
"restOfThisYear": "Resten af dette år"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Beskrivelse",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Administrator",
|
||||
"member": "Medlem"
|
||||
"member": "Medlem",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "d m Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Fejl",
|
||||
"success": "Succes",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Dies umfasst alle Aufgaben und kann NICHT rückgängig gemacht werden!",
|
||||
"success": "Das Projekt wurde erfolgreich gelöscht.",
|
||||
"tasksToDelete": "Dies löscht unwiderruflich ca. {count} Aufgaben.",
|
||||
"tasksAndChildProjectsToDelete": "Dies löscht unwiderruflich ca. {tasks} Aufgaben und {projects} Projekte.",
|
||||
"noTasksToDelete": "Dieses Projekt enthält keine Aufgaben, es kann sicher gelöscht werden."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Schlüssel",
|
||||
"secretHint": "Wenn angegeben, werden alle Anfragen an die Webhook Ziel-URL mit HMAC signiert.",
|
||||
"secretDocs": "In der Dokumentation findest du weitere Informationen zum Umgang mit Schlüsseln."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filter",
|
||||
"clear": "Filter zurücksetzen",
|
||||
"showResults": "Ergebnisse anzeigen",
|
||||
"attributes": {
|
||||
"title": "Titel",
|
||||
"titlePlaceholder": "Einen gespeicherten Filternamen eingeben …",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Diesen gespeicherten Filter bearbeiten",
|
||||
"success": "Filter gespeichert."
|
||||
},
|
||||
"query": {
|
||||
"title": "Abfrage",
|
||||
"placeholder": "Gib eine Suche oder Filterabfrage ein…",
|
||||
"help": {
|
||||
"intro": "Um Aufgaben zu filtern, kannst du eine SQL-ähnliche Abfragesyntax verwenden. Du kannst die folgenden Felder in deinem Filter verwenden:",
|
||||
"link": "Wie funktioniert das?",
|
||||
"canUseDatemath": "Du kannst Date Math verwenden, um relative Daten festzulegen. Klicke auf den Datumswert in einer Abfrage, um mehr zu erfahren.",
|
||||
"fields": {
|
||||
"done": "Ob die Aufgabe erledigt ist oder nicht",
|
||||
"priority": "Die Priorität der Aufgabe (1-5)",
|
||||
"percentDone": "Prozentsatz der Fertigstellung der Aufgabe (0-100)",
|
||||
"dueDate": "Das Fälligkeitsdatum der Aufgabe",
|
||||
"startDate": "Das Startdatum der Aufgabe",
|
||||
"endDate": "Das Enddatum der Aufgabe",
|
||||
"doneAt": "Datum und Uhrzeit, an dem die Aufgabe als erledigt markiert wurde",
|
||||
"assignees": "Die der Aufgabe Zugewiesenen",
|
||||
"labels": "Die der Aufgabe zugeordneten Labels",
|
||||
"project": "Das Projekt, zu dem die Aufgabe gehört (nur verfügbar für gespeicherte Filter, nicht auf Projektebene)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "Die verfügbaren Operatoren für die Filterung sind:",
|
||||
"notEqual": "Ungleich",
|
||||
"equal": "Gleich",
|
||||
"greaterThan": "Größer als",
|
||||
"greaterThanOrEqual": "Größer oder gleich",
|
||||
"lessThan": "Kleiner als",
|
||||
"lessThanOrEqual": "Kleiner als oder gleich",
|
||||
"like": "Vergleicht zu einem Muster (mit Platzhalter %)",
|
||||
"in": "Filtert einen beliebigen Wert in einer kommaseparierten Liste"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "Um mehrere Bedingungen zu kombinieren, kannst du folgende logische Operatoren verwenden:",
|
||||
"and": "UND Operator, stimmt überein, wenn alle Bedingungen wahr sind",
|
||||
"or": "ODER Operator, stimmt überein, wenn eine der Bedingungen wahr ist",
|
||||
"parentheses": "Klammern zum Gruppieren von Bedingungen"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Hier sind einige Beispiele für Filterabfragen:",
|
||||
"priorityEqual": "Findet Aufgaben mit Priorität Level 4",
|
||||
"dueDatePast": "Findet Aufgaben mit einem Fälligkeitsdatum in der Vergangenheit",
|
||||
"undoneHighPriority": "Findet Aufgaben, die nicht erledigt sind und Priorität Level mindestens 3 haben",
|
||||
"assigneesIn": "Findet Aufgaben, die entweder \"user1\" oder \"user2\" zugewiesen sind",
|
||||
"priorityOneOrTwoPastDue": "Findet Aufgaben mit Priorität Level 1 oder 2 und einem Fälligkeitsdatum in der Vergangenheit"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "Bis",
|
||||
"from": "Von",
|
||||
"fromto": "{from} bis {to}",
|
||||
"date": "Datum",
|
||||
"ranges": {
|
||||
"today": "Heute",
|
||||
"thisWeek": "Diese Woche",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Letzter Monat",
|
||||
"thisYear": "Dieses Jahr",
|
||||
"restOfThisYear": "Der Rest des Jahres"
|
||||
},
|
||||
"values": {
|
||||
"now": "Jetzt",
|
||||
"startOfToday": "Beginn des heutigen Tages",
|
||||
"endOfToday": "Ende des heutigen Tages",
|
||||
"beginningOflastWeek": "Beginn der letzten Woche",
|
||||
"endOfLastWeek": "Ende der letzten Woche",
|
||||
"beginningOfThisWeek": "Beginn dieser Woche",
|
||||
"endOfThisWeek": "Ende dieser Woche",
|
||||
"startOfNextWeek": "Beginn der nächsten Woche",
|
||||
"endOfNextWeek": "Ende der nächsten Woche",
|
||||
"in7Days": "In 7 Tagen",
|
||||
"beginningOfLastMonth": "Beginn des letzten Monats",
|
||||
"endOfLastMonth": "Ende des letzten Monats",
|
||||
"startOfThisMonth": "Beginn diesen Monats",
|
||||
"endOfThisMonth": "Ende diesen Monats",
|
||||
"startOfNextMonth": "Beginn des nächsten Monats",
|
||||
"endOfNextMonth": "Ende des nächsten Monats",
|
||||
"in30Days": "In 30 Tagen",
|
||||
"startOfThisYear": "Beginn dieses Jahres",
|
||||
"endOfThisYear": "Ende dieses Jahres"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Beschreibung",
|
||||
"descriptionPlaceholder": "Gib eine Beschreibung für dieses Team ein, drücke '/' für mehr Optionen…",
|
||||
"admin": "Admin",
|
||||
"member": "Mitglied"
|
||||
"member": "Mitglied",
|
||||
"isPublic": "Öffentliches Team",
|
||||
"isPublicDescription": "Machs das Team öffentlich sichtbar. Wenn aktiviert, kann jede:r mit diesem Team Projekte teilen, auch wenn er oder sie kein direktes Mitglied des Teams ist."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "Neues Projekt",
|
||||
"createProject": "Projekt erstellen",
|
||||
"cantArchiveIsDefault": "Du kannst dieses Projekt nicht archivieren, da es dein Standardprojekt ist.",
|
||||
"cantDeleteIsDefault": "Du kannst dieses Projekt nicht löschen, da es dein Standardprojekt ist."
|
||||
"cantDeleteIsDefault": "Du kannst dieses Projekt nicht löschen, da es dein Standardprojekt ist.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja-URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} hat mit {value} reagiert",
|
||||
"reactedWithAnd": "{users} und {lastUser} haben mit {value} reagiert",
|
||||
"reactedWithAndMany": "{users} und {num} weitere haben mit {value} reagiert",
|
||||
"add": "Reaktion hinzufügen"
|
||||
},
|
||||
"error": {
|
||||
"error": "Fehler",
|
||||
"success": "Erfolgreich",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Dies umfasst alle Aufgaben und kann NICHT rückgängig gemacht werden!",
|
||||
"success": "Das Projekt wurde erfolgreich gelöscht.",
|
||||
"tasksToDelete": "Dies löscht unwiderruflich ca. {count} Aufgaben.",
|
||||
"tasksAndChildProjectsToDelete": "Dies löscht unwiderruflich ca. {tasks} Aufgaben und {projects} Projekte.",
|
||||
"noTasksToDelete": "Dieses Projekt enthält keine Aufgaben, es kann sicher gelöscht werden."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Schlüssel",
|
||||
"secretHint": "Wenn angegeben, werden alle Anfragen an die Webhook Ziel-URL mit HMAC signiert.",
|
||||
"secretDocs": "In der Dokumentation findest du weitere Informationen zum Umgang mit Schlüsseln."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filter",
|
||||
"clear": "Filter zurücksetzen",
|
||||
"showResults": "Ergebnisse anzeigen",
|
||||
"attributes": {
|
||||
"title": "Titl",
|
||||
"titlePlaceholder": "De Name für de g'speicheret Filter chunt da ahne…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "De g'speicheret Filter bearbeite",
|
||||
"success": "De filter isch erfolgriich g'speichered wore."
|
||||
},
|
||||
"query": {
|
||||
"title": "Abfrage",
|
||||
"placeholder": "Gib eine Suche oder Filterabfrage ein…",
|
||||
"help": {
|
||||
"intro": "Um Aufgaben zu filtern, kannst du eine SQL-ähnliche Abfragesyntax verwenden. Du kannst die folgenden Felder in deinem Filter verwenden:",
|
||||
"link": "Wie funktioniert das?",
|
||||
"canUseDatemath": "Du kannst Date Math verwenden, um relative Daten festzulegen. Klicke auf den Datumswert in einer Abfrage, um mehr zu erfahren.",
|
||||
"fields": {
|
||||
"done": "Ob die Aufgabe erledigt ist oder nicht",
|
||||
"priority": "Die Priorität der Aufgabe (1-5)",
|
||||
"percentDone": "Prozentsatz der Fertigstellung der Aufgabe (0-100)",
|
||||
"dueDate": "Das Fälligkeitsdatum der Aufgabe",
|
||||
"startDate": "Das Startdatum der Aufgabe",
|
||||
"endDate": "Das Enddatum der Aufgabe",
|
||||
"doneAt": "Datum und Uhrzeit, an dem die Aufgabe als erledigt markiert wurde",
|
||||
"assignees": "Die der Aufgabe Zugewiesenen",
|
||||
"labels": "Die der Aufgabe zugeordneten Labels",
|
||||
"project": "Das Projekt, zu dem die Aufgabe gehört (nur verfügbar für gespeicherte Filter, nicht auf Projektebene)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "Die verfügbaren Operatoren für die Filterung sind:",
|
||||
"notEqual": "Ungleich",
|
||||
"equal": "Gleich",
|
||||
"greaterThan": "Größer als",
|
||||
"greaterThanOrEqual": "Größer oder gleich",
|
||||
"lessThan": "Kleiner als",
|
||||
"lessThanOrEqual": "Kleiner als oder gleich",
|
||||
"like": "Vergleicht zu einem Muster (mit Platzhalter %)",
|
||||
"in": "Filtert einen beliebigen Wert in einer kommaseparierten Liste"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "Um mehrere Bedingungen zu kombinieren, kannst du folgende logische Operatoren verwenden:",
|
||||
"and": "UND Operator, stimmt überein, wenn alle Bedingungen wahr sind",
|
||||
"or": "ODER Operator, stimmt überein, wenn eine der Bedingungen wahr ist",
|
||||
"parentheses": "Klammern zum Gruppieren von Bedingungen"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Hier sind einige Beispiele für Filterabfragen:",
|
||||
"priorityEqual": "Findet Aufgaben mit Priorität Level 4",
|
||||
"dueDatePast": "Findet Aufgaben mit einem Fälligkeitsdatum in der Vergangenheit",
|
||||
"undoneHighPriority": "Findet Aufgaben, die nicht erledigt sind und Priorität Level mindestens 3 haben",
|
||||
"assigneesIn": "Findet Aufgaben, die entweder \"user1\" oder \"user2\" zugewiesen sind",
|
||||
"priorityOneOrTwoPastDue": "Findet Aufgaben mit Priorität Level 1 oder 2 und einem Fälligkeitsdatum in der Vergangenheit"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "Bis",
|
||||
"from": "Von",
|
||||
"fromto": "{from} bis {to}",
|
||||
"date": "Datum",
|
||||
"ranges": {
|
||||
"today": "Heute",
|
||||
"thisWeek": "Diese Woche",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Letzter Monat",
|
||||
"thisYear": "Dieses Jahr",
|
||||
"restOfThisYear": "Der Rest des Jahres"
|
||||
},
|
||||
"values": {
|
||||
"now": "Jetzt",
|
||||
"startOfToday": "Beginn des heutigen Tages",
|
||||
"endOfToday": "Ende des heutigen Tages",
|
||||
"beginningOflastWeek": "Beginn der letzten Woche",
|
||||
"endOfLastWeek": "Ende der letzten Woche",
|
||||
"beginningOfThisWeek": "Beginn dieser Woche",
|
||||
"endOfThisWeek": "Ende dieser Woche",
|
||||
"startOfNextWeek": "Beginn der nächsten Woche",
|
||||
"endOfNextWeek": "Ende der nächsten Woche",
|
||||
"in7Days": "In 7 Tagen",
|
||||
"beginningOfLastMonth": "Beginn des letzten Monats",
|
||||
"endOfLastMonth": "Ende des letzten Monats",
|
||||
"startOfThisMonth": "Beginn diesen Monats",
|
||||
"endOfThisMonth": "Ende diesen Monats",
|
||||
"startOfNextMonth": "Beginn des nächsten Monats",
|
||||
"endOfNextMonth": "Ende des nächsten Monats",
|
||||
"in30Days": "In 30 Tagen",
|
||||
"startOfThisYear": "Beginn dieses Jahres",
|
||||
"endOfThisYear": "Ende dieses Jahres"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Beschriibig",
|
||||
"descriptionPlaceholder": "Gib eine Beschreibung für dieses Team ein, drücke '/' für mehr Optionen…",
|
||||
"admin": "Chef",
|
||||
"member": "Mitglied"
|
||||
"member": "Mitglied",
|
||||
"isPublic": "Öffentliches Team",
|
||||
"isPublicDescription": "Machs das Team öffentlich sichtbar. Wenn aktiviert, kann jede:r mit diesem Team Projekte teilen, auch wenn er oder sie kein direktes Mitglied des Teams ist."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "Neues Projekt",
|
||||
"createProject": "Projekt erstellen",
|
||||
"cantArchiveIsDefault": "Du kannst dieses Projekt nicht archivieren, da es dein Standardprojekt ist.",
|
||||
"cantDeleteIsDefault": "Du kannst dieses Projekt nicht löschen, da es dein Standardprojekt ist."
|
||||
"cantDeleteIsDefault": "Du kannst dieses Projekt nicht löschen, da es dein Standardprojekt ist.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} hat mit {value} reagiert",
|
||||
"reactedWithAnd": "{users} und {lastUser} haben mit {value} reagiert",
|
||||
"reactedWithAndMany": "{users} und {num} weitere haben mit {value} reagiert",
|
||||
"add": "Reaktion hinzufügen"
|
||||
},
|
||||
"error": {
|
||||
"error": "Fähler",
|
||||
"success": "Erfolg",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,6 +381,22 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
@ -445,7 +462,7 @@
|
|||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a list"
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
|
@ -986,7 +1003,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1046,7 +1065,8 @@
|
|||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1093,6 +1113,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "¡Esto incluye todas las tareas y NO SE PUEDE DESHACER!",
|
||||
"success": "El proyecto se eliminó con éxito.",
|
||||
"tasksToDelete": "Esto eliminará de forma definitiva aprox. {count} tareas.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "Este proyecto no contiene tareas. Debería ser seguro eliminarlo."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filtros",
|
||||
"clear": "Limpiar Filtros",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Título",
|
||||
"titlePlaceholder": "El título del filtro guardado va aquí…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Editar este Filtro Guardado",
|
||||
"success": "El filtro se guardó con éxito."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "Para",
|
||||
"from": "De",
|
||||
"fromto": "De {from} para {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Hoy",
|
||||
"thisWeek": "Esta Semana",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "El Mes Pasado",
|
||||
"thisYear": "Este Año",
|
||||
"restOfThisYear": "El Resto del Año"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Descripción",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Miembro"
|
||||
"member": "Miembro",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "Nuevo proyecto",
|
||||
"createProject": "Crear proyecto",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "URL de Vikunja",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Éxito",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Ceci inclut toutes les tâches et NE PEUT PAS ÊTRE ANNULÉ !",
|
||||
"success": "Le projet a bien été supprimé.",
|
||||
"tasksToDelete": "Cela supprimera définitivement environ {count} tâches.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "Ce projet ne contient aucune tâche, vous pouvez le supprimer sans problème."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filtres",
|
||||
"clear": "Effacer les filtres",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Nom",
|
||||
"titlePlaceholder": "Entre un nom de filtre enregistré…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Modifier ce filtre enregistré",
|
||||
"success": "Filtre enregistré."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "À",
|
||||
"from": "De",
|
||||
"fromto": "Du {from} au {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Aujourd’hui",
|
||||
"thisWeek": "Cette semaine",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Le mois dernier",
|
||||
"thisYear": "Cette année",
|
||||
"restOfThisYear": "Le reste de cette année"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Membre"
|
||||
"member": "Membre",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "Nouveau projet",
|
||||
"createProject": "Créer un projet",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "URL Vikunja",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Erreur",
|
||||
"success": "Succès",
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"welcomeEvening": "Jó estét {username}!",
|
||||
"lastViewed": "Utoljára megtekintve",
|
||||
"addToHomeScreen": "Adja hozzá ezt az alkalmazást a kezdőképernyőhöz a gyorsabb hozzáférés és a jobb élmény érdekében.",
|
||||
"goToOverview": "Go to overview",
|
||||
"goToOverview": "Tovább az áttekintéshez",
|
||||
"project": {
|
||||
"importText": "Importálja projektjeit és feladatait más szolgáltatásokból a Vikunjába:",
|
||||
"import": "Importálja adatait a Vikunjába"
|
||||
|
@ -57,11 +57,11 @@
|
|||
"logout": "Kijelentkezés",
|
||||
"emailInvalid": "Kérjük, adjon meg egy valós email címet!",
|
||||
"usernameRequired": "Kérjük adjon meg egy felhasználónevet.",
|
||||
"usernameMustNotContainSpace": "The username must not contain spaces.",
|
||||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"usernameMustNotContainSpace": "A felhasználónév nem tartalmazhat szóközt.",
|
||||
"usernameMustNotLookLikeUrl": "A felhasználónév nem nézhet ki URL-nek.",
|
||||
"passwordRequired": "Kérjük, adjon meg új jelszót.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMin": "A jelszónak legalább 8 karakterből kell állnia.",
|
||||
"passwordNotMax": "A jelszó legfeljebb 250 karakterből állhat.",
|
||||
"showPassword": "Jelszó megjelenítése",
|
||||
"hidePassword": "A jelszó elrejtése",
|
||||
"noAccountYet": "Még nincs fiókja?",
|
||||
|
@ -164,7 +164,7 @@
|
|||
"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!",
|
||||
"selectAll": "Select all",
|
||||
"selectAll": "Összes kijelölése",
|
||||
"delete": {
|
||||
"header": "Token törlése",
|
||||
"text1": "Biztos benne, hogy törölni akarja ezt a tokent \"{token}\"?",
|
||||
|
@ -248,6 +248,7 @@
|
|||
"text2": "Ez magában foglalja az összes feladatot és NEM VISSZAVONHATÓ!",
|
||||
"success": "A projekt sikeresen törölve.",
|
||||
"tasksToDelete": "Ezzel visszavonhatatlanul eltávolítjuk kb. {count} feladatát.",
|
||||
"tasksAndChildProjectsToDelete": "Ezzel visszavonhatatlanul eltávolítunk kb. {tasks} feladatot és {projects} projektet.",
|
||||
"noTasksToDelete": "Ez a projekt nem tartalmaz feladatokat, biztonságosan törölhető."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -264,7 +265,7 @@
|
|||
"identifier": "Projektazonosító",
|
||||
"identifierPlaceholder": "Írja be a projekt projektazonosítót...",
|
||||
"description": "Leírás",
|
||||
"descriptionPlaceholder": "Enter a description for this project, hit '/' for more options…",
|
||||
"descriptionPlaceholder": "Adja meg a projekt leírását, a további lehetőségekért nyomja meg a „/” gombot…",
|
||||
"color": "Szín",
|
||||
"success": "A projekt sikeresen frissítve."
|
||||
},
|
||||
|
@ -367,29 +368,46 @@
|
|||
}
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "Webhooks",
|
||||
"targetUrl": "Target URL",
|
||||
"targetUrlInvalid": "Please provide a valid URL.",
|
||||
"events": "Events",
|
||||
"eventsHint": "Select all events this webhook should recieve updates for (within the current project).",
|
||||
"mustSelectEvents": "You must select at least one event.",
|
||||
"delete": "Delete this webhook",
|
||||
"deleteText": "Are you sure you want to delete this webhook? External targets will not be notified of its events anymore.",
|
||||
"deleteSuccess": "The webhook was successfully deleted.",
|
||||
"create": "Create webhook",
|
||||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
"title": "Webhookok",
|
||||
"targetUrl": "Cél URL",
|
||||
"targetUrlInvalid": "Kérjük adjon meg egy érvényes URL-t.",
|
||||
"events": "Események",
|
||||
"eventsHint": "Válassza ki az összes olyan eseményt, amelyhez ez a webhook frissítéseket kap (az aktuális projekten belül).",
|
||||
"mustSelectEvents": "Legalább egy eseményt ki kell választania.",
|
||||
"delete": "Webhook törlése",
|
||||
"deleteText": "Biztosan törli ezt a webhook-ot? A külső célpontok többé nem kapnak értesítést az eseményeiről.",
|
||||
"deleteSuccess": "A webhook törlése sikeresen megtörtént.",
|
||||
"create": "Webhook létrehozása",
|
||||
"secret": "Kulcs",
|
||||
"secretHint": "Amennyiben meg van adva, a webhook cél URL-címére irányuló összes kérés HMAC használatával lesz aláírva.",
|
||||
"secretDocs": "Tekintse meg a dokumentációt a titkok használatának további részleteiért."
|
||||
},
|
||||
"views": {
|
||||
"header": "Nézetek szerkesztése",
|
||||
"title": "Cím",
|
||||
"actions": "Műveletek",
|
||||
"kind": "Fajta",
|
||||
"bucketConfigMode": "Vödör konfigurációs mód",
|
||||
"bucketConfig": "Vödör konfiguráció",
|
||||
"bucketConfigManual": "Manuális",
|
||||
"filter": "Szűrő",
|
||||
"create": "Nézet létrehozása",
|
||||
"createSuccess": "A nézet létrehozása sikeres volt.",
|
||||
"titleRequired": "Kérjük, adjon meg egy címet.",
|
||||
"delete": "Törölje ezt a nézetet",
|
||||
"deleteText": "Biztosan eltávolítja ezt a nézetet? A továbbiakban nem lesz használható a projektben szereplő feladatok megtekintésére. Ez a művelet nem töröl semmilyen feladatot. Ezt nem lehet visszacsinálni!",
|
||||
"deleteSuccess": "A nézet sikeresen törölve"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Szűrők",
|
||||
"clear": "Szűrők törlése",
|
||||
"showResults": "Eredmények megjelenítése",
|
||||
"attributes": {
|
||||
"title": "Cím",
|
||||
"titlePlaceholder": "A mentett szűrő címe ide kerül…",
|
||||
"description": "Leírás",
|
||||
"descriptionPlaceholder": "Add a description for this filter here, hit '/' for more options…",
|
||||
"descriptionPlaceholder": "Adjon hozzá leírást ehhez a szűrőhöz, nyomja meg a „/” gombot a további lehetőségekért…",
|
||||
"includeNulls": "Tartalmazza az olyan feladatokat, amelyeknek nincs beállított értéke",
|
||||
"requireAll": "A feladat megjelenítéséhez minden szűrőnek igaznak kell lennie",
|
||||
"showDoneTasks": "Elkészült feladatok megjelenítése",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Mentett szűrő szerkesztése",
|
||||
"success": "A szűrőt sikeresen mentette."
|
||||
},
|
||||
"query": {
|
||||
"title": "Lekérdezés",
|
||||
"placeholder": "Írjon be egy keresési, vagy szűrési lekérdezést…",
|
||||
"help": {
|
||||
"intro": "A feladatok szűréséhez az SQL-hez hasonló lekérdezési szintaxist használhat. A szűréshez rendelkezésre álló mezők a következők:",
|
||||
"link": "Hogyan működik?",
|
||||
"canUseDatemath": "A relatív dátumok beállításához a matematikai dátumokat is használhat. További információért kattintson a dátum értékére a lekérdezésben.",
|
||||
"fields": {
|
||||
"done": "Akár befejeződött a feladat, akár nem",
|
||||
"priority": "A feladat prioritási szintje (1-5)",
|
||||
"percentDone": "A feladat teljesítésének százalékos aránya (0-100)",
|
||||
"dueDate": "A feladat teljesítésének határideje",
|
||||
"startDate": "A feladat kezdési dátuma",
|
||||
"endDate": "A feladat befejezési dátuma",
|
||||
"doneAt": "A feladat befejezésének dátuma és időpontja",
|
||||
"assignees": "A feladattal megbízottak",
|
||||
"labels": "A feladathoz társított címkék",
|
||||
"project": "A projekt, amelyhez a feladat tartozik (csak a mentett szűrőkhöz érhető el, projekt szinten nem)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "A szűréshez elérhető operátorok a következők:",
|
||||
"notEqual": "Nem egyenlő",
|
||||
"equal": "Egyenlő",
|
||||
"greaterThan": "Nagyobb, mint",
|
||||
"greaterThanOrEqual": "Nagyobb, vagy egyenlő",
|
||||
"lessThan": "Kevesebb, mint",
|
||||
"lessThanOrEqual": "Kevesebb, vagy egyenlő mint",
|
||||
"like": "Megfelel egy mintának (helyettesítő karakterrel %)",
|
||||
"in": "A vesszővel elválasztott értéklistában szereplő bármely értéknek megfelel"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "Több feltétel kombinálásához a következő logikai operátorokat használhatja:",
|
||||
"and": "ÉS operátor, akkor egyezik, ha minden feltétel igaz",
|
||||
"or": "VAGY operátor, akkor felel meg, ha valamelyik feltétel igaz",
|
||||
"parentheses": "Zárójelek a csoportosítási feltételekhez"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Íme néhány példa a szűrőlekérdezésekre:",
|
||||
"priorityEqual": "4. prioritási szintű feladatokat egyezteti",
|
||||
"dueDatePast": "A múltbeli esedékességgel rendelkező feladatokat egyezteti",
|
||||
"undoneHighPriority": "Megfelel a 3-as, vagy magasabb prioritási szintű még nem elvégzett feladatoknak",
|
||||
"assigneesIn": "Megfelel a \"felhasználó1\", vagy a \"felhasználó2\"-nek rendelt feladatoknak",
|
||||
"priorityOneOrTwoPastDue": "Megfelel az 1-es, vagy 2-es prioritási szinttel és a múltbeli esedékességgel rendelkező feladatoknak"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -431,8 +495,8 @@
|
|||
"confirm": "Biztos vagyok benne, kezdje el a migrációt most!",
|
||||
"importUpload": "Ha adatokat szeretne importálni a(z) {name} webhelyről a Vikunjába, kattintson az alábbi gombra a fájl kiválasztásához.",
|
||||
"upload": "Fájl feltöltése",
|
||||
"migrationStartedWillReciveEmail": "Vikunja will now import your lists/projects, tasks, notes, reminders and files from {service}. As this will take a while, we will send you an email once done. You can close this window now.",
|
||||
"migrationInProgress": "A migration is currently in progress. Please wait until it is done."
|
||||
"migrationStartedWillReciveEmail": "A Vikunja mostantól importálja listáit/projektjeit, feladatait, jegyzeteit, emlékeztetőit és fájljait a {service} szolgáltatásból. Mivel ez eltart egy ideig, amint elkészült, küldünk Önnek egy e-mailt. Most bezárhatja ezt az ablakot.",
|
||||
"migrationInProgress": "A migráció folyamatban van. Kérjük várjon, amíg elkészül."
|
||||
},
|
||||
"label": {
|
||||
"title": "Címkék",
|
||||
|
@ -503,7 +567,7 @@
|
|||
"custom": "Egyéni",
|
||||
"id": "Azonosító",
|
||||
"created": "Létrehozva ekkor:",
|
||||
"createdBy": "Created by {0}",
|
||||
"createdBy": "Létrehozta: {0}",
|
||||
"actions": "Műveletek",
|
||||
"cannotBeUndone": "Ezt nem lehet visszavonni!"
|
||||
},
|
||||
|
@ -522,59 +586,59 @@
|
|||
"edit": "Szerkesztés",
|
||||
"done": "Befejezve",
|
||||
"heading1": "Címsor 1",
|
||||
"heading1Tooltip": "Big section heading.",
|
||||
"heading1Tooltip": "Nagy szakasz címsora.",
|
||||
"heading2": "Címsor 2",
|
||||
"heading2Tooltip": "Medium section heading.",
|
||||
"heading2Tooltip": "Közepes szakasz címsora.",
|
||||
"heading3": "Címsor 3",
|
||||
"heading3Tooltip": "Smaller section header.",
|
||||
"heading3Tooltip": "Kisebb szakasz címsora.",
|
||||
"headingSmaller": "Kisebb címsor",
|
||||
"headingBigger": "Nagyobb címsor",
|
||||
"bold": "Félkövér",
|
||||
"italic": "Dőlt",
|
||||
"strikethrough": "Áthúzott",
|
||||
"underline": "Underline",
|
||||
"underline": "Aláhúzott",
|
||||
"code": "Kód",
|
||||
"codeTooltip": "Capture a code snippet.",
|
||||
"codeTooltip": "Rögzítsen egy kódrészletet.",
|
||||
"quote": "Idézet",
|
||||
"quoteTooltip": "Capture a quote.",
|
||||
"bulletList": "Bullet list",
|
||||
"bulletListTooltip": "Create a simple bullet list.",
|
||||
"unorderedList": "Unordered list",
|
||||
"orderedList": "Ordered list",
|
||||
"orderedListTooltip": "Create a list with numbering.",
|
||||
"quoteTooltip": "Rögzítsen egy idézetet.",
|
||||
"bulletList": "Felsorolásos lista",
|
||||
"bulletListTooltip": "Hozzon létre egy egyszerű felsoroláslistát.",
|
||||
"unorderedList": "Rendezetlen lista",
|
||||
"orderedList": "Rendezett lista",
|
||||
"orderedListTooltip": "Készítsen listát számozással.",
|
||||
"cleanBlock": "Blokk kitisztítása",
|
||||
"link": "Hivatkozás",
|
||||
"image": "Kép",
|
||||
"imageTooltip": "Upload an image from your computer.",
|
||||
"imageTooltip": "Fájl feltöltése a számítógépről.",
|
||||
"table": {
|
||||
"title": "Table",
|
||||
"insert": "Insert table",
|
||||
"addColumnBefore": "Add column before",
|
||||
"addColumnAfter": "Add column after",
|
||||
"deleteColumn": "Delete column",
|
||||
"addRowBefore": "Add row before",
|
||||
"addRowAfter": "Add row after",
|
||||
"deleteRow": "Delete row",
|
||||
"deleteTable": "Delete table",
|
||||
"mergeCells": "Merge cells",
|
||||
"splitCell": "Split cell",
|
||||
"toggleHeaderColumn": "Toggle header column",
|
||||
"toggleHeaderRow": "Toggle header row",
|
||||
"toggleHeaderCell": "Toggle header cell",
|
||||
"mergeOrSplit": "Merge or split",
|
||||
"fixTables": "Fix tables"
|
||||
"title": "Táblázat",
|
||||
"insert": "Táblázat beszúrása",
|
||||
"addColumnBefore": "Oszlop hozzáadása előtte",
|
||||
"addColumnAfter": "Oszlop hozzáadása utána",
|
||||
"deleteColumn": "Oszlop törlése",
|
||||
"addRowBefore": "Sor hozzáadása előtte",
|
||||
"addRowAfter": "Sor hozzáadása utána",
|
||||
"deleteRow": "Sor törlése",
|
||||
"deleteTable": "Táblázat törlése",
|
||||
"mergeCells": "Cellák egyesítése",
|
||||
"splitCell": "Cellák szétválasztása",
|
||||
"toggleHeaderColumn": "A fejléc oszlopának váltása",
|
||||
"toggleHeaderRow": "Váltás a fejlécsorra",
|
||||
"toggleHeaderCell": "Kapcsolja be a fejléccellát",
|
||||
"mergeOrSplit": "Egyesítés vagy felosztás",
|
||||
"fixTables": "Táblázatok javítása"
|
||||
},
|
||||
"horizontalRule": "Vízszintes vonal",
|
||||
"horizontalRuleTooltip": "Divide a section.",
|
||||
"horizontalRuleTooltip": "Egy szakasz felosztása.",
|
||||
"sideBySide": "Egymás mellett",
|
||||
"guide": "Útmutató",
|
||||
"text": "Text",
|
||||
"textTooltip": "Just start typing with plain text.",
|
||||
"taskList": "Task list",
|
||||
"taskListTooltip": "Track tasks with a to-do list.",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo",
|
||||
"placeholder": "Type some text or hit '/' to see more options…"
|
||||
"text": "Szöveg",
|
||||
"textTooltip": "Csak kezdje el a gépelést egyszerű szöveggel.",
|
||||
"taskList": "Feladatlista",
|
||||
"taskListTooltip": "Kövesse nyomon a feladatokat egy teendőlistával.",
|
||||
"undo": "Visszavonás",
|
||||
"redo": "Újra",
|
||||
"placeholder": "Írjon be egy szöveget, vagy nyomja meg a „/” gombot a további lehetőségek megtekintéséhez…"
|
||||
},
|
||||
"multiselect": {
|
||||
"createPlaceholder": "Új létrehozása",
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "Eddig",
|
||||
"from": "Ettől",
|
||||
"fromto": "{from} - tól {to} - ig",
|
||||
"date": "Dátum",
|
||||
"ranges": {
|
||||
"today": "Ma",
|
||||
"thisWeek": "Ezen a héten",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Előző hónap",
|
||||
"thisYear": "Aktuális év",
|
||||
"restOfThisYear": "Az év hátralévő része"
|
||||
},
|
||||
"values": {
|
||||
"now": "Most",
|
||||
"startOfToday": "A mai nap kezdete",
|
||||
"endOfToday": "A mai nap vége",
|
||||
"beginningOflastWeek": "Múlt hét eleje",
|
||||
"endOfLastWeek": "Múlt hét vége",
|
||||
"beginningOfThisWeek": "E hét eleje",
|
||||
"endOfThisWeek": "E hét vége",
|
||||
"startOfNextWeek": "Jövő hét eleje",
|
||||
"endOfNextWeek": "Jövő hét vége",
|
||||
"in7Days": "7 napon belül",
|
||||
"beginningOfLastMonth": "Múlt hónap eleje",
|
||||
"endOfLastMonth": "Múlt hónap vége",
|
||||
"startOfThisMonth": "E hónap eleje",
|
||||
"endOfThisMonth": "E hónap vége",
|
||||
"startOfNextMonth": "Jövő hónap eleje",
|
||||
"endOfNextMonth": "Jövő hónap vége",
|
||||
"in30Days": "30 napon belül",
|
||||
"startOfThisYear": "Ez év eleje",
|
||||
"endOfThisYear": "Ez év vége"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -713,7 +799,7 @@
|
|||
"startDate": "Kezdő dátum",
|
||||
"title": "Cím",
|
||||
"updated": "Frissítve",
|
||||
"doneAt": "Done At"
|
||||
"doneAt": "Befejezve ekkor"
|
||||
},
|
||||
"subscription": {
|
||||
"subscribedTaskThroughParentProject": "Itt nem iratkozhat le, mert a projektjén keresztül feliratkozott erre a feladatra.",
|
||||
|
@ -749,7 +835,7 @@
|
|||
"loading": "Hozzászólások betöltése…",
|
||||
"edited": "Szerkesztve: {date}",
|
||||
"creating": "Hozzászólás létrehozása…",
|
||||
"placeholder": "Add your comment, hit '/' for more options…",
|
||||
"placeholder": "Adja hozzá megjegyzését, nyomja meg a „/” gombot a további lehetőségekért…",
|
||||
"comment": "Hozzászólás",
|
||||
"delete": "Hozzászólás törlése",
|
||||
"deleteText1": "Biztos benne, hogy törölni akarja ezt a hozzászólást?",
|
||||
|
@ -763,7 +849,7 @@
|
|||
"1week": "1 hét"
|
||||
},
|
||||
"description": {
|
||||
"placeholder": "Enter a description, hit '/' for more options…",
|
||||
"placeholder": "Írja be a leírást, nyomja meg a '/' gombot a további lehetőségekért…",
|
||||
"empty": "Nem érhető el leírás."
|
||||
},
|
||||
"assignee": {
|
||||
|
@ -914,9 +1000,11 @@
|
|||
"namePlaceholder": "A csapat nevét ide írja…",
|
||||
"nameRequired": "Kérjük, adjon meg egy nevet.",
|
||||
"description": "Leírás",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"descriptionPlaceholder": "Jellemezze a csapatot itt, kattintson a '/' gombra a további lehetőségekért…",
|
||||
"admin": "Adminisztrátor",
|
||||
"member": "Tag"
|
||||
"member": "Tag",
|
||||
"isPublic": "Nyilvános csapat",
|
||||
"isPublicDescription": "Tedd nyilvánosan felfedezhetővé a csapatot. Ha engedélyezve van, bárki megoszthat projekteket ezzel a csapattal, még akkor is, ha nem közvetlen tagja."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -975,8 +1063,9 @@
|
|||
"share": "Megosztás",
|
||||
"newProject": "Új projekt",
|
||||
"createProject": "Projekt létrehozása",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantArchiveIsDefault": "Ezt nem archiválhatja, mert ez az alapértelmezett projektje.",
|
||||
"cantDeleteIsDefault": "Ezt nem törölheti, mert ez az alapértelmezett projektje.",
|
||||
"views": "Nézetek"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -996,8 +1085,8 @@
|
|||
"title": "Értesítések",
|
||||
"none": "Nincsenek értesítései. Legyen szép napja!",
|
||||
"explainer": "Az értesítések itt jelennek meg, amikor olyan projektek vagy feladatok történnek, amelyekre feliratkozott.",
|
||||
"markAllRead": "Mark all notifications as read",
|
||||
"markAllReadSuccess": "Successfully marked all notifications as read."
|
||||
"markAllRead": "Minden értesítés megjelölése olvasottként",
|
||||
"markAllReadSuccess": "Az összes értesítést olvasottként jelölte meg."
|
||||
},
|
||||
"quickActions": {
|
||||
"commands": "Parancsok",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} a következővel reagált: {value}",
|
||||
"reactedWithAnd": "{users} és {lastUser} a következővel reagált: {value}",
|
||||
"reactedWithAndMany": "{users} és további {num} felhasználó reagált a következővel: {value}",
|
||||
"add": "Adja hozzá reakcióját"
|
||||
},
|
||||
"error": {
|
||||
"error": "Hiba",
|
||||
"success": "Siker",
|
||||
|
@ -1096,7 +1191,7 @@
|
|||
},
|
||||
"about": {
|
||||
"title": "Névjegy",
|
||||
"version": "Version: {version}"
|
||||
"version": "Verzió: {version}"
|
||||
},
|
||||
"time": {
|
||||
"units": {
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filtri",
|
||||
"clear": "Pulisci Filtri",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Titolo",
|
||||
"titlePlaceholder": "Il titolo del filtro salvato va qui…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Modifica Questo Filtro Salvato",
|
||||
"success": "Filtro salvato."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "A",
|
||||
"from": "Da",
|
||||
"fromto": "da {from} a {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Oggi",
|
||||
"thisWeek": "Questa Settimana",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Mese scorso",
|
||||
"thisYear": "Quest'anno",
|
||||
"restOfThisYear": "Il resto di quest'anno"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Descrizione",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Amministratore",
|
||||
"member": "Membro"
|
||||
"member": "Membro",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "URL Vikunja",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Errore",
|
||||
"success": "Fatto",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "このプロジェクトに含まれるタスクはすべて削除されます。この操作は元に戻せません。",
|
||||
"success": "プロジェクトは正常に削除されました。",
|
||||
"tasksToDelete": "約{count}件のタスクが抹消されます。",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "このプロジェクトにはタスクが含まれていないので問題なく削除できます。"
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "絞り込み",
|
||||
"clear": "絞り込みの解除",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "絞り込み条件名",
|
||||
"titlePlaceholder": "絞り込み条件名を入力…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "絞り込み条件の編集",
|
||||
"success": "絞り込み条件は正常に保存されました。"
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "タスクの終了日",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "終了",
|
||||
"from": "開始",
|
||||
"fromto": "{from} 〜 {to} まで",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "今日",
|
||||
"thisWeek": "今週",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "先月",
|
||||
"thisYear": "今年",
|
||||
"restOfThisYear": "今から年末まで"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "今日の終わり",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "先週の終わり",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "今週の終わり",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "来週の終わり",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "先月の終わり",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "今月の終わり",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "来月の終わり",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "今年の終わり"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "説明",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "管理者",
|
||||
"member": "メンバー"
|
||||
"member": "メンバー",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "新しいプロジェクトの作成",
|
||||
"createProject": "プロジェクトの作成",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "Y/n/j H:i",
|
||||
"altFormatShort": "Y/n/j"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "프로젝트가 성공적으로 삭제되었습니다.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "필터",
|
||||
"clear": "필터 초기화",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "제목",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} 에서 {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "오늘",
|
||||
"thisWeek": "이번 주",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "지난달",
|
||||
"thisYear": "올해",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Titel",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "Het filter is succesvol opgeslagen."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Beschrijving",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Beheerder",
|
||||
"member": "Lid"
|
||||
"member": "Lid",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Fout",
|
||||
"success": "Succes",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Dette inkluderer alle oppgaver og KAN IKKE ANGRES!",
|
||||
"success": "Prosjektet ble slettet.",
|
||||
"tasksToDelete": "Dette vil ugjenkallelig fjerne ca. {count} oppgaver.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "Dette prosjektet inneholder ingen oppgaver, det bør være trygt å slette."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filtre",
|
||||
"clear": "Fjern filtre",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Tittel",
|
||||
"titlePlaceholder": "Den lagrede filtertittelen kommer hit…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Rediger dette lagrede filteret",
|
||||
"success": "Filteret ble lagret."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "Til",
|
||||
"from": "Fra",
|
||||
"fromto": "{from} til {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Idag",
|
||||
"thisWeek": "Denne uken",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Forrige måned",
|
||||
"thisYear": "Dette året",
|
||||
"restOfThisYear": "Resten av denne uken"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Beskrivelse",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Administrator",
|
||||
"member": "Medlem"
|
||||
"member": "Medlem",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "Nytt prosjekt",
|
||||
"createProject": "Create project",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "d.m.Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Feil",
|
||||
"success": "Suksess",
|
||||
|
|
|
@ -57,11 +57,11 @@
|
|||
"logout": "Wyloguj",
|
||||
"emailInvalid": "Proszę podać poprawny adres e-mail.",
|
||||
"usernameRequired": "Proszę podać nazwę użytkownika.",
|
||||
"usernameMustNotContainSpace": "The username must not contain spaces.",
|
||||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"usernameMustNotContainSpace": "Nazwa użytkownika nie może zawierać spacji.",
|
||||
"usernameMustNotLookLikeUrl": "Nazwa użytkownika nie może wyglądać jak adres URL.",
|
||||
"passwordRequired": "Proszę podać hasło.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMin": "Hasło musi zawierać co najmniej 8 znaków.",
|
||||
"passwordNotMax": "Hasło musi zawierać co najwyżej 250 znaków.",
|
||||
"showPassword": "Pokaż hasło",
|
||||
"hidePassword": "Ukryj hasło",
|
||||
"noAccountYet": "Nie masz jeszcze konta?",
|
||||
|
@ -248,6 +248,7 @@
|
|||
"text2": "Dotyczy to wszystkich zadań i tego NIE DA SIĘ COFNĄĆ!",
|
||||
"success": "Projekt został pomyślnie usunięty.",
|
||||
"tasksToDelete": "To nieodwracalnie usunie około {count} zadań.",
|
||||
"tasksAndChildProjectsToDelete": "To nieodwracalnie usunie ok. {tasks} zadań i {projects} projektów.",
|
||||
"noTasksToDelete": "Ten projekt nie zawiera żadnych zadań, więc można go bezpiecznie usunąć."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Sekret",
|
||||
"secretHint": "Jeśli podane, wszystkie żądania do adresu docelowego webhooka zostaną podpisane przy użyciu HMAC.",
|
||||
"secretDocs": "Sprawdź dokumentację, aby uzyskać więcej informacji na temat korzystania z sekretów."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edytuj widoki",
|
||||
"title": "Tytuł",
|
||||
"actions": "Działania",
|
||||
"kind": "Rodzaj",
|
||||
"bucketConfigMode": "Tryb konfiguracji kolumny",
|
||||
"bucketConfig": "Konfiguracja kolumny",
|
||||
"bucketConfigManual": "Instrukcja",
|
||||
"filter": "Filtr",
|
||||
"create": "Utwórz widok",
|
||||
"createSuccess": "Widok utworzony pomyślnie.",
|
||||
"titleRequired": "Proszę podać tytuł.",
|
||||
"delete": "Usuń ten widok",
|
||||
"deleteText": "Czy na pewno chcesz usunąć ten widok? Nie będzie już możliwe wyświetlanie zadań w tym projekcie. Ta akcja nie usunie żadnych zadań. Tej operacji nie można cofnąć!",
|
||||
"deleteSuccess": "Widok został pomyślnie usunięty"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filtry",
|
||||
"clear": "Wyczyść filtry",
|
||||
"showResults": "Wyświetl wyniki",
|
||||
"attributes": {
|
||||
"title": "Tytuł",
|
||||
"titlePlaceholder": "Tu wpisz tytuł filtra stałego…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Edytuj ten filtr stały",
|
||||
"success": "Filtr został pomyślnie zapisany."
|
||||
},
|
||||
"query": {
|
||||
"title": "Zapytanie",
|
||||
"placeholder": "Wpisz zapytanie wyszukiwania lub filtruj…",
|
||||
"help": {
|
||||
"intro": "Aby filtrować zadania, możesz użyć składni zapytań podobnej do SQL. Pola dostępne do filtrowania to:",
|
||||
"link": "Jak to działa?",
|
||||
"canUseDatemath": "Możesz użyć matematyki dat, aby ustawić daty względne. Kliknij wartość daty w zapytaniu, aby dowiedzieć się więcej.",
|
||||
"fields": {
|
||||
"done": "Czy zadanie zostało zakończone, czy nie",
|
||||
"priority": "Priorytet zadania (1-5)",
|
||||
"percentDone": "Procent ukończenia zadania (0-100)",
|
||||
"dueDate": "Termin wykonania zadania",
|
||||
"startDate": "Data rozpoczęcia zadania",
|
||||
"endDate": "Data zakończenia zadania",
|
||||
"doneAt": "Data i czas ukończenia zadania",
|
||||
"assignees": "Osoby przypisane do zadania",
|
||||
"labels": "Etykiety przypisane do zadania",
|
||||
"project": "Projekt, do którego należy zadanie (dostępne tylko dla zapisanych filtrów, nie na poziomie projektu)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "Dostępne operatory do filtrowania to:",
|
||||
"notEqual": "Nie równa się",
|
||||
"equal": "Równa się",
|
||||
"greaterThan": "Większe niż",
|
||||
"greaterThanOrEqual": "Większe niż lub równe",
|
||||
"lessThan": "Mniejsze niż",
|
||||
"lessThanOrEqual": "Mniejsze niż lub równe",
|
||||
"like": "Pasuje do wzorca (używając symbolu %)",
|
||||
"in": "Dopasuje dowolną wartość z listy wartości oddzielonych przecinkami"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "Aby połączyć wiele warunków, możesz użyć następujących operatorów logicznych:",
|
||||
"and": "Operator AND, dopasowuje, jeśli wszystkie warunki są prawdziwe",
|
||||
"or": "Operator OR, dopasowuje, jeśli którykolwiek z warunków jest spełniony",
|
||||
"parentheses": "Nawiasy do grupowania warunków"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Oto kilka przykładów zapytań filtrujących:",
|
||||
"priorityEqual": "Dopasowuje zadania z priorytetem 4",
|
||||
"dueDatePast": "Dopasowuje zadania z terminem wykonania w przeszłości",
|
||||
"undoneHighPriority": "Dopasowuje niewykonane zadania z poziomem 3 lub wyższym",
|
||||
"assigneesIn": "Dopasowuje zadania przypisane do \"user1\" lub \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Dopasowuje zadania z poziomem 1 lub 2 i terminem wykonania w przeszłości"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "Do",
|
||||
"from": "Od",
|
||||
"fromto": "{from} do {to}",
|
||||
"date": "Data",
|
||||
"ranges": {
|
||||
"today": "Dziś",
|
||||
"thisWeek": "W tym tygodniu",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Zeszły miesiąc",
|
||||
"thisYear": "Ten rok",
|
||||
"restOfThisYear": "Reszta tego roku"
|
||||
},
|
||||
"values": {
|
||||
"now": "Teraz",
|
||||
"startOfToday": "Początek dzisiejszego dnia",
|
||||
"endOfToday": "Koniec dzisiejszego dnia",
|
||||
"beginningOflastWeek": "Początek zeszłego tygodnia",
|
||||
"endOfLastWeek": "Koniec zeszłego tygodnia",
|
||||
"beginningOfThisWeek": "Początek tego tygodnia",
|
||||
"endOfThisWeek": "Koniec tego tygodnia",
|
||||
"startOfNextWeek": "Początek następnego tygodnia",
|
||||
"endOfNextWeek": "Koniec następnego tygodnia",
|
||||
"in7Days": "Za 7 dni",
|
||||
"beginningOfLastMonth": "Początek zeszłego miesiąca",
|
||||
"endOfLastMonth": "Koniec zeszłego miesiąca",
|
||||
"startOfThisMonth": "Początek tego miesiąca",
|
||||
"endOfThisMonth": "Koniec tego miesiąca",
|
||||
"startOfNextMonth": "Początek następnego miesiąca",
|
||||
"endOfNextMonth": "Koniec następnego miesiąca",
|
||||
"in30Days": "Za 30 dni",
|
||||
"startOfThisYear": "Początek tego roku",
|
||||
"endOfThisYear": "Koniec tego roku"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -713,7 +799,7 @@
|
|||
"startDate": "Data rozpoczęcia",
|
||||
"title": "Tytuł",
|
||||
"updated": "Zaktualizowano",
|
||||
"doneAt": "Done At"
|
||||
"doneAt": "Wykonano"
|
||||
},
|
||||
"subscription": {
|
||||
"subscribedTaskThroughParentProject": "Nie możesz zrezygnować z subskrypcji, ponieważ subskrybujesz to zadanie poprzez jego projekt.",
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Opis",
|
||||
"descriptionPlaceholder": "Opisz tutaj zespół, naciśnij '/' aby uzyskać więcej opcji…",
|
||||
"admin": "Administrator",
|
||||
"member": "Członek"
|
||||
"member": "Członek",
|
||||
"isPublic": "Publiczny zespół",
|
||||
"isPublicDescription": "Udostępnij zespół publicznie. Gdy ta opcja jest włączona, każdy może udostępniać projekty temu zespołowi, nawet nie będąc jego bezpośrednim członkiem."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -975,8 +1063,9 @@
|
|||
"share": "Udostępnij",
|
||||
"newProject": "Nowy projekt",
|
||||
"createProject": "Utwórz projekt",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantArchiveIsDefault": "Nie możesz tego zarchiwizować, ponieważ jest to twój domyślny projekt.",
|
||||
"cantDeleteIsDefault": "Nie możesz tego usunąć, ponieważ jest to twój domyślny projekt.",
|
||||
"views": "Widoki"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "URL Vikunji",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} zareagował z {value}",
|
||||
"reactedWithAnd": "{users} i {lastUser} zareagowali z {value}",
|
||||
"reactedWithAndMany": "{users} i {num} innych osób zareagowali z {value}",
|
||||
"add": "Dodaj swoją reakcję"
|
||||
},
|
||||
"error": {
|
||||
"error": "Błąd",
|
||||
"success": "Sukces",
|
||||
|
@ -1096,7 +1191,7 @@
|
|||
},
|
||||
"about": {
|
||||
"title": "O aplikacji",
|
||||
"version": "Version: {version}"
|
||||
"version": "Wersja: {version}"
|
||||
},
|
||||
"time": {
|
||||
"units": {
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Isso inclui todas as tarefas e NÃO PODE SER DESFEITO!",
|
||||
"success": "Seu projeto foi excluído com sucesso.",
|
||||
"tasksToDelete": "Isto irá remover permanentemente approx. {count} tarefas.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "Este projeto não contém tarefas, é seguro excluir."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Segredo",
|
||||
"secretHint": "Se fornecido, todas as requisições para a URL de destino do webhook serão assinadas usando HMAC.",
|
||||
"secretDocs": "Confira a documentação para obter mais detalhes sobre como usar segredos."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filtros",
|
||||
"clear": "Limpar Filtros",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Título",
|
||||
"titlePlaceholder": "O título do filtro salvo fica aqui…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Editar este filtro salvo",
|
||||
"success": "O filtro foi salvo com sucesso."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "Para",
|
||||
"from": "De",
|
||||
"fromto": "{from} até {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Hoje",
|
||||
"thisWeek": "Esta semana",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Último mês",
|
||||
"thisYear": "Este ano",
|
||||
"restOfThisYear": "O resto deste ano"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Descrição",
|
||||
"descriptionPlaceholder": "Descreva a equipe aqui, aperte '/' para mais opções…",
|
||||
"admin": "Administrador",
|
||||
"member": "Membro"
|
||||
"member": "Membro",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "Novo projeto",
|
||||
"createProject": "Criar projeto",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Erro",
|
||||
"success": "Sucesso",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Isto inclui todas as tarefas e NÃO PODE SER REVERTIDO!",
|
||||
"success": "O projeto foi eliminado com sucesso.",
|
||||
"tasksToDelete": "Isto irá remover irrevogavelmente aprox. {count} tarefas.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "Este projeto não contém tarefas, deve ser seguro eliminá-lo."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Segredo",
|
||||
"secretHint": "Se fornecido, todos os pedidos para o URL de destino do webhook serão assinados utilizando HMAC.",
|
||||
"secretDocs": "Verifica a documentação para mais detalhes sobre como utilizar segredos."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filtros",
|
||||
"clear": "Limpar Filtros",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Título",
|
||||
"titlePlaceholder": "O título do filtro memorizado será aqui…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Editar Este Filtro Memorizado",
|
||||
"success": "O filtro foi memorizado com sucesso."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "Até",
|
||||
"from": "De",
|
||||
"fromto": "{from} até {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Hoje",
|
||||
"thisWeek": "Esta semana",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Mês Passado",
|
||||
"thisYear": "Este Ano",
|
||||
"restOfThisYear": "O Resto Deste Ano"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Descrição",
|
||||
"descriptionPlaceholder": "Descreve aqui a equipa, pressiona '/' para mais opções…",
|
||||
"admin": "Administrador",
|
||||
"member": "Membro"
|
||||
"member": "Membro",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "Novo projeto",
|
||||
"createProject": "Criar projeto",
|
||||
"cantArchiveIsDefault": "Não podes arquivar isto porque é o teu projeto padrão.",
|
||||
"cantDeleteIsDefault": "Não podes eliminar isto porque é o teu projeto padrão."
|
||||
"cantDeleteIsDefault": "Não podes eliminar isto porque é o teu projeto padrão.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "URL do Vikunja",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Erro",
|
||||
"success": "Sucesso",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Это включает в себя все задачи, и отменить это будет нельзя!",
|
||||
"success": "Проект успешно удалён.",
|
||||
"tasksToDelete": "Это безвозвратно удалит примерно {count} задач.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "В этом проекте нет никаких задач, можно спокойно удалять."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Секрет",
|
||||
"secretHint": "Если указан, все запросы к URL обработчика будут подписаны с помощью HMAC.",
|
||||
"secretDocs": "Подробнее об использовании секретов в документации."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Фильтры",
|
||||
"clear": "Сбросить фильтры",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Название",
|
||||
"titlePlaceholder": "Введите название сохранённого фильтра…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Изменить этот сохранённый фильтр",
|
||||
"success": "Фильтр сохранён."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "По",
|
||||
"from": "С",
|
||||
"fromto": "С {from} по {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Сегодня",
|
||||
"thisWeek": "Эта неделя",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Прошлый месяц",
|
||||
"thisYear": "Этот год",
|
||||
"restOfThisYear": "Остаток этого года"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Описание",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Администратор",
|
||||
"member": "Участник"
|
||||
"member": "Участник",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "Создать проект",
|
||||
"createProject": "Создать проект",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Ошибка",
|
||||
"success": "Успех",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -57,11 +57,11 @@
|
|||
"logout": "Odjava",
|
||||
"emailInvalid": "Prosim vnesite veljaven e-poštni naslov.",
|
||||
"usernameRequired": "Prosim vnesite uporabniško ime.",
|
||||
"usernameMustNotContainSpace": "The username must not contain spaces.",
|
||||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"usernameMustNotContainSpace": "Uporabniško ime ne sme vsebovati presledkov.",
|
||||
"usernameMustNotLookLikeUrl": "Uporabniško ime ne sme izgledati kot URL.",
|
||||
"passwordRequired": "Prosim vnesite geslo.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMin": "Geslo mora imeti vsaj 8 znakov.",
|
||||
"passwordNotMax": "Geslo mora imeti največ 250 znakov.",
|
||||
"showPassword": "Prikažite geslo",
|
||||
"hidePassword": "Skrijte geslo",
|
||||
"noAccountYet": "Še nimate računa?",
|
||||
|
@ -248,6 +248,7 @@
|
|||
"text2": "To vključuje vse naloge in GA NI MOGOČE RAZVELJAVITI!",
|
||||
"success": "Projekt je bil uspešno izbrisan.",
|
||||
"tasksToDelete": "S tem bo nepreklicno odstranjeno približno {count} nalog.",
|
||||
"tasksAndChildProjectsToDelete": "S tem bo nepreklicno odstranjeno cca. {tasks} nalog in {projects} projektov.",
|
||||
"noTasksToDelete": "Ta projekt ne vsebuje nobenih nalog, zato ga lahko varno izbrišete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Skrita koda",
|
||||
"secretHint": "Če je podana, bodo vse zahteve do ciljnega webhook URL-ja podpisane s HMAC.",
|
||||
"secretDocs": "Poglejte si dokumentacijo za več podrobnosti o uporabi skrite kode."
|
||||
},
|
||||
"views": {
|
||||
"header": "Uredi pogled",
|
||||
"title": "Naslov",
|
||||
"actions": "Dejanja",
|
||||
"kind": "Vrsta",
|
||||
"bucketConfigMode": "Način nastavitve vedra",
|
||||
"bucketConfig": "Nastavitev vedra",
|
||||
"bucketConfigManual": "Ročno",
|
||||
"filter": "Filter",
|
||||
"create": "Ustvari pogled",
|
||||
"createSuccess": "Pogled je bil uspešno ustvarjen.",
|
||||
"titleRequired": "Prosim navedite naslov.",
|
||||
"delete": "Izbriši pogled",
|
||||
"deleteText": "Ali ste prepričani, da želite odstraniti ta pogled? Ne bo ga več mogoče uporabljati za ogled nalog v tem projektu. To dejanje ne bo izbrisalo nobenih opravil. Tega ni mogoče razveljaviti!",
|
||||
"deleteSuccess": "Pogled je bil uspešno izbrisan"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filtri",
|
||||
"clear": "Počisti filtre",
|
||||
"showResults": "Prikaži rezultate",
|
||||
"attributes": {
|
||||
"title": "Naslov",
|
||||
"titlePlaceholder": "Tu je naslov shranjenega filtra…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Uredi shranjeni filter",
|
||||
"success": "Filter je bil uspešno shranjen."
|
||||
},
|
||||
"query": {
|
||||
"title": "Poizvedba",
|
||||
"placeholder": "Vnesite poizvedbo za iskanje ali filtriranje…",
|
||||
"help": {
|
||||
"intro": "Za filtriranje nalog lahko uporabite sintakso poizvedbe, podobno SQL. Razpoložljiva polja za filtriranje vključujejo:",
|
||||
"link": "Kako to deluje?",
|
||||
"canUseDatemath": "Za nastavitev relativnih datumov lahko določite datume. Če želite izvedeti več, kliknite vrednost datuma v poizvedbi.",
|
||||
"fields": {
|
||||
"done": "Ali je naloga opravljena ali ne",
|
||||
"priority": "Stopnja prioritete naloge (1-5)",
|
||||
"percentDone": "Odstotek dokončanja naloge (0–100)",
|
||||
"dueDate": "Datum zapadlosti naloge",
|
||||
"startDate": "Začetni datum opravila",
|
||||
"endDate": "Končni datum opravila",
|
||||
"doneAt": "Datum in čas, ko je bila naloga opravljena",
|
||||
"assignees": "Dodeljeni k nalogi",
|
||||
"labels": "Oznake, povezane z opravilom",
|
||||
"project": "Projekt, ki mu naloga pripada (na voljo samo za shranjene filtre, ne na ravni projekta)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "Razpoložljivi operaterji za filtriranje vključujejo:",
|
||||
"notEqual": "Ni enako",
|
||||
"equal": "Je enako",
|
||||
"greaterThan": "Večje kot",
|
||||
"greaterThanOrEqual": "Večje ali enako kot",
|
||||
"lessThan": "Manj kot",
|
||||
"lessThanOrEqual": "Manjše ali enako kot",
|
||||
"like": "Ujema se z vzorcem (z nadomestnimi znaki %)",
|
||||
"in": "Ujema se s katero koli vrednostjo na seznamu vrednosti, ločenih z vejico"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "Če želite združiti več pogojev, lahko uporabite naslednje logične operatorje:",
|
||||
"and": "IN operator, se ujema, če so izpolnjeni vsi pogoji",
|
||||
"or": "ALI operator, se ujema, če je kateri od pogojev resničen",
|
||||
"parentheses": "Oklepaji za združevanje pogojev"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Tukaj je nekaj primerov filtrirnih poizvedb:",
|
||||
"priorityEqual": "Ujema se z nalogami s prioritetno stopnjo 4",
|
||||
"dueDatePast": "Ujema se z opravili z rokom v preteklosti",
|
||||
"undoneHighPriority": "Ujema se z neopravljenimi opravili s prioritetno stopnjo 3 ali višjo",
|
||||
"assigneesIn": "Ujema se z nalogami, dodeljenimi k \"user1\" ali \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Ujema se z opravili s prioritetno stopnjo 1 ali 2 in rokom v preteklosti"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "Za",
|
||||
"from": "Od",
|
||||
"fromto": "{from} do {to}",
|
||||
"date": "Datum",
|
||||
"ranges": {
|
||||
"today": "Danes",
|
||||
"thisWeek": "Ta teden",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Prejšnji mesec",
|
||||
"thisYear": "Letos",
|
||||
"restOfThisYear": "Preostanek tega leta"
|
||||
},
|
||||
"values": {
|
||||
"now": "Zdaj",
|
||||
"startOfToday": "Začetek današnjega dne",
|
||||
"endOfToday": "Konec današnjega dne",
|
||||
"beginningOflastWeek": "Začetek prejšnjega tedna",
|
||||
"endOfLastWeek": "Konec prejšnjega tedna",
|
||||
"beginningOfThisWeek": "Začetek tega tedna",
|
||||
"endOfThisWeek": "Konec tega tedna",
|
||||
"startOfNextWeek": "Začetek naslednjega tedna",
|
||||
"endOfNextWeek": "Konec naslednjega tedna",
|
||||
"in7Days": "V 7 dneh",
|
||||
"beginningOfLastMonth": "Začetek prejšnjega meseca",
|
||||
"endOfLastMonth": "Konec prejšnjega meseca",
|
||||
"startOfThisMonth": "Začetek tega meseca",
|
||||
"endOfThisMonth": "Konec tega meseca",
|
||||
"startOfNextMonth": "Začetek naslednjega meseca",
|
||||
"endOfNextMonth": "Konec naslednjega meseca",
|
||||
"in30Days": "V 30 dneh",
|
||||
"startOfThisYear": "Začetek tega leta",
|
||||
"endOfThisYear": "Konec tega leta"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -713,7 +799,7 @@
|
|||
"startDate": "Začetni datum",
|
||||
"title": "Naslov",
|
||||
"updated": "Posodobljeno",
|
||||
"doneAt": "Done At"
|
||||
"doneAt": "Končano ob"
|
||||
},
|
||||
"subscription": {
|
||||
"subscribedTaskThroughParentProject": "Ker ste na to nalogo naročeni prek njenega projekta, se tu ne morete odjaviti.",
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Opis",
|
||||
"descriptionPlaceholder": "Tukaj opiši ekipo, pritisni '/' za več možnosti…",
|
||||
"admin": "Administrator",
|
||||
"member": "Član"
|
||||
"member": "Član",
|
||||
"isPublic": "Javna ekipa",
|
||||
"isPublicDescription": "Naj bo ekipa javno vidna. Ko je omogočeno, lahko vsakdo deli projekte s to ekipo, tudi če ni neposredni član."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -975,8 +1063,9 @@
|
|||
"share": "Skupna raba",
|
||||
"newProject": "Nov projekt",
|
||||
"createProject": "Ustvari projekt",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantArchiveIsDefault": "Tega ne morete arhivirati, ker je to vaš privzeti projekt.",
|
||||
"cantDeleteIsDefault": "Tega ne morete izbrisati, ker je to vaš privzeti projekt.",
|
||||
"views": "Pogledi"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} se je odzval z {value}",
|
||||
"reactedWithAnd": "{users} in {lastUser} sta reagirala z {value}",
|
||||
"reactedWithAndMany": "{users} in {num} drugih se je odzvalo z {value}",
|
||||
"add": "Dodaj svoj odziv"
|
||||
},
|
||||
"error": {
|
||||
"error": "Napaka",
|
||||
"success": "Uspeh",
|
||||
|
@ -1096,7 +1191,7 @@
|
|||
},
|
||||
"about": {
|
||||
"title": "O programu",
|
||||
"version": "Version: {version}"
|
||||
"version": "Verzija: {version}"
|
||||
},
|
||||
"time": {
|
||||
"units": {
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "New project",
|
||||
"createProject": "Create project",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -380,11 +381,28 @@
|
|||
"secret": "Secret",
|
||||
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
|
||||
"secretDocs": "Check out the docs for more details about how to use secrets."
|
||||
},
|
||||
"views": {
|
||||
"header": "Edit views",
|
||||
"title": "Title",
|
||||
"actions": "Actions",
|
||||
"kind": "Kind",
|
||||
"bucketConfigMode": "Bucket configuration mode",
|
||||
"bucketConfig": "Bucket configuration",
|
||||
"bucketConfigManual": "Manual",
|
||||
"filter": "Filter",
|
||||
"create": "Create view",
|
||||
"createSuccess": "The view was created successfully.",
|
||||
"titleRequired": "Please provide a title.",
|
||||
"delete": "Delete this view",
|
||||
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
|
||||
"deleteSuccess": "The view was successfully deleted"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filter",
|
||||
"clear": "Rensa filter",
|
||||
"showResults": "Visa resultat",
|
||||
"attributes": {
|
||||
"title": "Titel",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +433,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Lika med",
|
||||
"greaterThan": "Större än",
|
||||
"greaterThanOrEqual": "Större än eller lika med",
|
||||
"lessThan": "Mindre än",
|
||||
"lessThanOrEqual": "Mindre än eller lika med",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +648,7 @@
|
|||
"to": "Till",
|
||||
"from": "Från",
|
||||
"fromto": "{from} till {to}",
|
||||
"date": "Datum",
|
||||
"ranges": {
|
||||
"today": "I dag",
|
||||
"thisWeek": "Denna vecka",
|
||||
|
@ -598,6 +663,27 @@
|
|||
"lastMonth": "Förra månaden",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Nu",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +1002,9 @@
|
|||
"description": "Beskrivning",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Medlem"
|
||||
"member": "Medlem",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -976,7 +1064,8 @@
|
|||
"newProject": "Nytt projekt",
|
||||
"createProject": "Skapa projekt",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project.",
|
||||
"views": "Views"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1112,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue