Compare commits

...

250 Commits

Author SHA1 Message Date
renovate 84472d2e9c chore(deps): update dependency happy-dom to v9.1.9 2023-04-10 13:17:25 +00:00
renovate c5afcd63b0 chore(deps): update dependency postcss-preset-env to v8.3.1 2023-04-10 12:05:14 +00:00
renovate 9bdb257814 chore(deps): update dependency caniuse-lite to v1.0.30001477 2023-04-10 11:14:22 +00:00
renovate 5ad9891b16 chore(deps): update pnpm to v7.32.0 2023-04-10 11:13:57 +00:00
renovate 7c04064917 chore(deps): update dependency vitest to v0.30.0 2023-04-10 11:13:43 +00:00
renovate fb5383d86b chore(deps): update dependency esbuild to v0.17.16 2023-04-10 05:04:46 +00:00
renovate 68af314ec0 chore(deps): update dependency eslint to v8.38.0 2023-04-08 00:04:43 +00:00
renovate 8b1de5ce09 fix(deps): update dependency pinia to v2.0.34 2023-04-07 20:04:46 +00:00
renovate 724b6fe091 chore(deps): update dependency typescript to v5.0.4 2023-04-07 18:04:25 +00:00
renovate 6648cd30c3 chore(deps): update dependency sass to v1.61.0 2023-04-06 22:04:45 +00:00
kolaente 8b90b45739
fix: make sure the unread notifications indicator is correctly positioned
Resolves vikunja/frontend#3358
2023-04-06 16:11:12 +02:00
renovate 39be67eecf fix(deps): update dependency axios to v1.3.5 2023-04-06 09:06:06 +00:00
Frederick [Bot] 750f0ddeab [skip ci] Updated translations via Crowdin 2023-04-05 00:06:16 +00:00
renovate 6a5ece2f24 chore(deps): update pnpm to v7.31.0 2023-04-04 01:04:32 +00:00
Frederick [Bot] 4ce33abfe6 [skip ci] Updated translations via Crowdin 2023-04-04 00:06:21 +00:00
renovate 5b7e1af87d chore(deps): update dependency @types/dompurify to v3.0.1 2023-04-03 20:04:51 +00:00
renovate 59c6605b14 chore(deps): update typescript-eslint monorepo to v5.57.1 2023-04-03 18:05:29 +00:00
Dominik Pschenitschni 820d598ecd fix(i18n): orderedList translationid 2023-04-03 09:48:03 +00:00
Dominik Pschenitschni a263ec1273
fix(Expandable): spelling ⛈ 2023-04-03 11:31:39 +02:00
renovate b68892492c chore(deps): update dependency happy-dom to v9 2023-04-03 05:18:07 +00:00
renovate 7c97695cec chore(deps): update dependency netlify-cli to v13.2.2 2023-04-03 05:17:16 +00:00
renovate e764f34a2d chore(deps): update dependency caniuse-lite to v1.0.30001473 2023-04-03 00:05:10 +00:00
renovate 6892a28bb6 chore(deps): update dependency esbuild to v0.17.15 2023-04-01 23:04:45 +00:00
renovate 74d688b8d2 chore(deps): update dependency csstype to v3.1.2 2023-04-01 21:04:41 +00:00
renovate ed84651046 chore(deps): update dependency postcss-preset-env to v8.3.0 2023-03-31 17:04:54 +00:00
renovate 7468ed21fa chore(deps): update dependency typescript to v5.0.3 2023-03-30 21:05:23 +00:00
renovate d8015913c3 fix(deps): update sentry-javascript monorepo to v7.46.0 2023-03-30 14:04:41 +00:00
Frederick [Bot] 78789834f0 [skip ci] Updated translations via Crowdin 2023-03-30 00:06:17 +00:00
Dominik Pschenitschni 739fe0caa1
fix: move types to dev dependencies 2023-03-29 22:23:40 +02:00
Dominik Pschenitschni 4703f9c4d5
fix: has-pseudo-class polyfill 2023-03-29 17:22:06 +02:00
Dominik Pschenitschni fd699ad777
fix: checkbox label size based on icon 2023-03-29 17:17:51 +02:00
Dominik Pschenitschni 0acf44778d
fix: undo further nesting of interactive items
This is really bad for UX and accessability
2023-03-29 17:17:50 +02:00
Dominik Pschenitschni 8fc254d2db
feat: abstract BaseCheckbox 2023-03-29 17:17:49 +02:00
Dominik Pschenitschni 7d3b97d422 feat: prepare for pnpm 8 (#3331)
Related: vikunja/frontend#3317

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#3331
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2023-03-29 12:02:34 +00:00
kolaente 4a34f245db
chore(deps): update flake 2023-03-29 13:41:52 +02:00
renovate 973ea39a64 chore(deps): update dependency eslint to v8.37.0 2023-03-29 07:56:52 +00:00
renovate f94a65ce7a chore(deps): update dependency @types/node to v18.15.11 2023-03-28 22:05:12 +00:00
renovate 432fbbea78 chore(deps): update dependency cypress to v12.9.0 2023-03-28 19:05:20 +00:00
renovate e483f1cd2e chore(deps): update dependency vitest to v0.29.8 2023-03-28 14:05:19 +00:00
renovate eb34f6e136 chore(deps): update dependency postcss-preset-env to v8.2.0 2023-03-28 12:05:42 +00:00
Dominik Pschenitschni 91e9eef582 fix: use strict comparison 2023-03-28 10:49:34 +00:00
Dominik Pschenitschni dea1789a00
feat: type i18n improvements 2023-03-28 12:35:19 +02:00
WofWca 30adad5ae6 feat: mark undone if task moved from isDoneBucket (#3291)
Addresses #545 (not completely)

Reviewed-on: vikunja/frontend#3291
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-28 10:21:19 +00:00
renovate 3ed6f939e5 chore(deps): update dependency vite-plugin-pwa to v0.14.7 2023-03-28 10:05:08 +00:00
renovate a337d22c1f fix(deps): update font awesome to v6.4.0 2023-03-27 18:27:34 +00:00
renovate addfcf2510 chore(deps): update typescript-eslint monorepo to v5.57.0 2023-03-27 18:05:13 +00:00
renovate 303034f02c chore(deps): update pnpm to v7.30.5 2023-03-27 14:04:54 +00:00
renovate 0fd44e9484 chore(deps): update dependency caniuse-lite to v1.0.30001470 2023-03-27 06:06:31 +00:00
renovate 04040f20ba chore(deps): update dependency netlify-cli to v13.2.1 2023-03-27 00:06:51 +00:00
konrad 6c999ad148 fix: ensure same protocol for configured api url (#3303)
Resolves https://github.com/go-vikunja/frontend/issues/109

Vikunja would save the api url with `http` instead of `https` when the frontend was accessed via https. This was fine in most cases when the server would redirect all requests made to http to the secure https variant. However, in newer Firefox versions (and soon, Chrome probably as well) the browser would not follow that redirect anymore. Hence, we need to make sure to only make api requests to the same protocol. Doing API requests from an https hosted fronted to an http hosted api would probably fail already anyway.

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#3303
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2023-03-26 19:18:47 +00:00
renovate cc519e6773 chore(deps): update dependency esbuild to v0.17.14 2023-03-26 03:05:27 +00:00
renovate f9dcae4f65 chore(deps): update dependency @types/node to v18.15.10 2023-03-25 23:05:50 +00:00
renovate ade6c2cb18 chore(deps): update dependency postcss-preset-env to v8.1.0 2023-03-25 09:05:19 +00:00
renovate 4566b62a93 chore(deps): update dependency @types/node to v18.15.9 2023-03-25 08:05:10 +00:00
renovate 37d3ef24d2 chore(deps): update dependency @types/node to v18.15.8 2023-03-25 00:05:22 +00:00
kolaente 71265769ce fix: update logo change only every hour 2023-03-24 23:30:26 +00:00
kolaente a13c16ca03 fix: use time constant 2023-03-24 23:30:26 +00:00
kolaente a33fb72ef8 fix: use onActivated 2023-03-24 23:30:26 +00:00
kolaente c5776264c0 fix: only update daytime salutation when switching to home view 2023-03-24 23:30:26 +00:00
kolaente 078d8b39a9 fix(gantt): only update today value when changing to the gantt chart view 2023-03-24 23:30:26 +00:00
kolaente b77c7c2f45 fix: add interval to uses of useNow so that it uses less resources 2023-03-24 23:30:26 +00:00
renovate e369473dd0 chore(deps): update dependency esbuild to v0.17.13 2023-03-24 19:05:04 +00:00
renovate 70501f9da1 chore(deps): update pnpm to v7.30.3 2023-03-24 14:04:24 +00:00
renovate 9bb7019b09 chore(deps): update dependency rollup to v3.20.2 2023-03-24 13:21:24 +00:00
renovate df4fe7a644 fix(deps): update sentry-javascript monorepo to v7.45.0 2023-03-24 10:06:38 +00:00
renovate 2f009d0b27 chore(deps): update dependency @types/node to v18.15.7 2023-03-24 09:05:25 +00:00
renovate 70d7def7d7 chore(deps): update dependency sass to v1.60.0 2023-03-24 07:11:01 +00:00
renovate 0033407f96 chore(deps): update pnpm to v7.30.2 2023-03-24 02:04:25 +00:00
renovate b10a2329ca chore(deps): update dependency @types/node to v18.15.6 2023-03-23 22:05:08 +00:00
WofWca 6870db4a72 fix: list view: don't sort tasks after marking one "done" (#3285)
See https://community.vikunja.io/t/list-view-tasks-being-sorted-after-marking-one-done-throws-you-off/1257/2

Reviewed-on: vikunja/frontend#3285
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-23 20:50:17 +00:00
WofWca 3643ffe0d0 fix: improve the "pop" sound a bit
Trim the (noisy) silence (especially at the start, because
that delay from making a change and playing a sound is a little
annoying), with fade-in and fade-out

Edited with Audacity

File(s) history:
7f5140bbb4
955bd73feccefe8e8ca54d946d6223b43a0836ed
2023-03-23 23:38:52 +04:00
renovate 02971f6ff9 chore(deps): update dependency vite-plugin-pwa to v0.14.6 2023-03-23 12:05:31 +00:00
renovate 7d3c34b004 chore(deps): update pnpm to v7.30.1 2023-03-23 11:48:32 +00:00
renovate f3ea6fd4dc chore(deps): update dependency eslint-plugin-vue to v9.10.0 2023-03-23 11:05:38 +00:00
renovate bed6b81a58 chore(deps): update dependency rollup to v3.20.1 2023-03-23 09:05:02 +00:00
renovate f9bf9139b8 fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.10.0 2023-03-22 18:05:17 +00:00
Dominik Pschenitschni 96e2c81b7e
fix: ignore ts deprecations for now
see https://github.com/vuejs/tsconfig/issues/6
2023-03-22 15:47:21 +01:00
renovate e62c00a187 chore(deps): update dependency typescript to v5 2023-03-22 13:05:25 +00:00
renovate 611419888a chore(deps): update dependency vite-plugin-pwa to v0.14.5 2023-03-22 12:05:18 +00:00
renovate 5cc7e282bf fix(deps): update dependency marked to v4.3.0 2023-03-22 06:05:26 +00:00
renovate de0b71103c chore(deps): update dependency @vue/test-utils to v2.3.2 2023-03-21 16:05:20 +00:00
renovate 537e9e8044 fix(deps): update sentry-javascript monorepo to v7.44.2 2023-03-21 11:05:19 +00:00
renovate ac95c1fdc8 chore(deps): update dependency @types/node to v18.15.5 2023-03-20 22:44:25 +00:00
renovate b36da9e4d9 chore(deps): update dependency @cypress/vite-dev-server to v5.0.5 2023-03-20 22:05:07 +00:00
renovate e11ee3c136 chore(deps): update dependency vitest to v0.29.7 2023-03-20 21:05:16 +00:00
renovate 887719ea24 chore(deps): update dependency @cypress/vue to v5.0.5 2023-03-20 19:08:09 +00:00
renovate 14f1c3b26e fix(deps): update sentry-javascript monorepo to v7.44.1 2023-03-20 19:04:28 +00:00
renovate 2142729d38 chore(deps): update typescript-eslint monorepo to v5.56.0 2023-03-20 18:06:42 +00:00
renovate 9dcc2baae2 fix(deps): update sentry-javascript monorepo to v7.44.0 2023-03-20 16:50:06 +00:00
renovate 37c88d2974 chore(deps): update dependency vitest to v0.29.5 2023-03-20 15:05:22 +00:00
renovate 36fd0deec4 chore(deps): update dependency vitest to v0.29.4 2023-03-20 14:05:04 +00:00
renovate 4a4438d431 chore(deps): update dependency vite to v4.2.1 2023-03-20 12:05:22 +00:00
renovate 28a6745346 fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.9.3 2023-03-20 10:05:21 +00:00
renovate 9ae0470879 chore(deps): update pnpm to v7.30.0 2023-03-20 08:10:09 +00:00
renovate 927aed1161 chore(deps): update dependency netlify-cli to v13.1.6 2023-03-20 08:09:18 +00:00
renovate 7f3d7a656d chore(deps): update dependency caniuse-lite to v1.0.30001468 2023-03-20 08:08:31 +00:00
renovate 040a8ce095 chore(deps): update dependency rollup to v3.20.0 2023-03-20 07:05:16 +00:00
Frederick [Bot] 8974939bf2 [skip ci] Updated translations via Crowdin 2023-03-19 00:05:55 +00:00
Dominik Pschenitschni 846de369f2 chore(parseSubtasksViaIndention): fix comment (#3259)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#3259
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2023-03-18 07:41:36 +00:00
Frederick [Bot] 4d865af423 [skip ci] Updated translations via Crowdin 2023-03-18 00:06:08 +00:00
renovate 62ad01fc8f chore(deps): update dependency esbuild to v0.17.12 2023-03-17 07:05:23 +00:00
renovate da0164b97d chore(deps): update histoire to v0.15.9 2023-03-16 23:05:16 +00:00
kolaente fc8711d6d8
fix: rename list to project for parsing subtasks via indention 2023-03-16 19:25:04 +01:00
renovate 03cef1f831 chore(deps): update dependency @vitejs/plugin-vue to v4.1.0 2023-03-16 15:05:00 +00:00
WofWca ee4974a494 fix: style: "favorite" button being shown on projects without hovering 2023-03-16 14:56:32 +00:00
WofWca bfbfd6a421 chore: update JSDoc example 2023-03-16 14:56:32 +00:00
renovate 49954abbbe chore(deps): update dependency vite to v4.2.0 2023-03-16 11:06:23 +00:00
renovate 2f618512cb chore(deps): update dependency @vitejs/plugin-legacy to v4.0.2 2023-03-16 09:05:39 +00:00
Frederick [Bot] 0086ebed0d [skip ci] Updated translations via Crowdin 2023-03-16 00:06:15 +00:00
renovate 1523ed9a47 chore(deps): update dependency cypress to v12.8.1 2023-03-15 22:03:55 +00:00
renovate 79c7cbedcc chore(deps): update dependency vitest to v0.29.3 2023-03-15 21:06:10 +00:00
renovate 3e128f3966 chore(deps): update pnpm to v7.29.3 2023-03-15 11:16:41 +00:00
renovate fb45483ffc fix(deps): update dependency vue-flatpickr-component to v11.0.3 2023-03-15 10:05:24 +00:00
renovate a9bc7d7a38 fix(deps): update dependency @types/sortablejs to v1.15.1 2023-03-15 05:05:35 +00:00
renovate 78f032d678 chore(deps): update dependency sass to v1.59.3 2023-03-14 22:05:23 +00:00
Dominik Pschenitschni d73b71a097 fix: SortBy type import 2023-03-14 21:46:42 +00:00
renovate 2bdc6155d7 chore(deps): update dependency cypress to v12.8.0 2023-03-14 20:34:59 +00:00
Dominik Pschenitschni f60cebf42c
fix: force usage of @types for flexsearch instead of integrated types 2023-03-14 17:06:30 +01:00
renovate 2e4c6673d4
fix(deps): update dependency flexsearch to v0.7.31 2023-03-14 17:06:30 +01:00
kolaente 6e3d64d6ef fix(tests): make sure the task is created with a bucket 2023-03-14 14:04:23 +00:00
kolaente 2deb66855b fix(ci): always pull latest unstable api image for testing 2023-03-14 14:04:23 +00:00
kolaente a64c0c19e5 fix: make sure redirects to a saved view work as intended 2023-03-14 14:04:23 +00:00
kolaente 24b4576c00 fix(tests): wait for request instead of fixed time 2023-03-14 14:04:23 +00:00
kolaente 34ad889d90 fix(link share): redirect to list view after authenticating 2023-03-14 14:04:23 +00:00
Dominik Pschenitschni af523cfcd7 feat: add redirect for old list routes 2023-03-14 14:04:23 +00:00
Dominik Pschenitschni 842f204123 fix: improve projectView storing and add migration 2023-03-14 14:04:23 +00:00
Dominik Pschenitschni 9162002e55 fix: spacing 2023-03-14 14:04:23 +00:00
Dominik Pschenitschni 985f998a82 fix: wording 2023-03-14 14:04:23 +00:00
Dominik Pschenitschni b93639e14e fix: rebase readd CustomTransition 2023-03-14 14:04:23 +00:00
Dominik Pschenitschni a4be973e29 feat: improve variable naming for ProjectCardGrid 2023-03-14 14:04:23 +00:00
Dominik Pschenitschni 060a573fe9 fix: switching to view type now 2023-03-14 14:04:23 +00:00
Dominik Pschenitschni 7c43b7385d fix comment 2023-03-14 14:04:23 +00:00
kolaente befa6f27bb feat: rename list to project everywhere
fix: project table view

fix: e2e tests

fix: typo in readme

fix: list view route

fix: don't wait until background is loaded for list to show

fix: rename component imports

fix: lint

fix: parse task text

fix: use list card grid

fix: use correct class names

fix: i18n keys

fix: load project

fix: task overview

fix: list view spacing

fix: find project

fix: setLoading when updating a project

fix: loading saved filter

fix: project store loading

fix: color picker import

fix: cypress tests

feat: migrate old list settings

chore: add const for project settings

fix: wrong projecten rename from lists

chore: rename unused variable

fix: editor list

fix: shortcut list class name

fix: pagination list class name

fix: notifications list class name

fix: list view variable name

chore: clarify comment

fix: i18n keys

fix: router imports

fix: comment

chore: remove debugging leftover

fix: remove duplicate variables

fix: change comment

fix: list view variable name

fix: list view css class name

fix: list item property name

fix: name update tasks function correctly

fix: update comment

fix: project create route

fix: list view class names

fix: list view component name

fix: result list class name

fix: animation class list name

fix: change debug log

fix: revert a few navigation changes

fix: use @ for imports of all views

fix: rename link share list class

fix: remove unused css class

fix: dynamically import project components again
2023-03-14 14:04:23 +00:00
Dominik Pschenitschni b9d3b5c756 feat: rename files with list to project 2023-03-14 14:04:23 +00:00
renovate ee732684bc chore(deps): update dependency @types/node to v18.15.3 2023-03-14 07:05:05 +00:00
renovate 360b530dd5 chore(deps): update dependency @types/dompurify to v3 2023-03-13 21:05:38 +00:00
renovate 713c3a1a08 chore(deps): update dependency @types/node to v18.15.2 2023-03-13 19:05:09 +00:00
renovate 81b1e4035d chore(deps): update typescript-eslint monorepo to v5.55.0 2023-03-13 18:05:20 +00:00
renovate 2cde9341d4 fix(deps): update sentry-javascript monorepo to v7.43.0 2023-03-13 16:06:32 +00:00
renovate cdf0690da6 fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.9.2 2023-03-13 10:17:16 +00:00
renovate 80335e7b95 chore(deps): update dependency netlify-cli to v13.1.2 2023-03-13 09:05:32 +00:00
renovate dbc2de14c9 chore(deps): update dependency @types/node to v18.15.1 2023-03-13 08:14:47 +00:00
renovate c0f711d27f chore(deps): update dependency caniuse-lite to v1.0.30001465 2023-03-13 07:04:54 +00:00
kolaente df24522490
chore: 0.20.5 release preperations 2023-03-12 10:23:07 +01:00
kolaente 6cf2e574bf
fix(docker): revert unprivileged user
nginx runs the init process as root so that it can bind to port 80. All worker processes run as an unprivileged user and thus the attack surface is minimal.
The previous solution didn't change the user id of the user running Vikunja and thus didn't have an effect anyway.

Related to #3228
2023-03-11 21:56:47 +01:00
kolaente e7b89ae44f
fix(docker): add cap_net_bind to the nginx binary in the docker container 2023-03-11 21:16:31 +01:00
renovate 72a1aaa654 chore(deps): update dependency eslint to v8.36.0 2023-03-11 06:04:52 +00:00
renovate c70c3b6080 chore(deps): update dependency sass to v1.59.2 2023-03-11 02:05:52 +00:00
kolaente 401f2cdd7e
chore: 0.20.4 release preperations 2023-03-10 14:51:04 +01:00
konrad 013472e899 fix(i18n): load language files before doing anything else (#3218)
Resolves #3214

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#3218
2023-03-10 13:46:23 +00:00
renovate e9d48c442d chore(deps): update dependency rollup to v3.19.1 2023-03-10 13:05:34 +00:00
renovate 6837038922 chore(deps): update dependency autoprefixer to v10.4.14 2023-03-10 07:05:55 +00:00
renovate 6cf7c75954 chore(deps): update dependency @types/node to v18.15.0 2023-03-10 01:06:32 +00:00
Frederick [Bot] 52d6677d93 [skip ci] Updated translations via Crowdin 2023-03-10 00:06:22 +00:00
renovate f8f8c8ac6e chore(deps): update dependency vite-plugin-inject-preload to v1.3.1 2023-03-09 20:53:21 +00:00
renovate 97bd5d77b6 chore(deps): update dependency rollup to v3.19.0 2023-03-09 20:05:23 +00:00
renovate 5278bcbac2 fix(deps): update sentry-javascript monorepo to v7.42.0 2023-03-09 14:05:28 +00:00
renovate 194fef0dab fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.9.1 2023-03-09 11:42:49 +00:00
renovate 97b6ba06dd chore(deps): update dependency @vue/test-utils to v2.3.1 2023-03-09 05:04:25 +00:00
Frederick [Bot] 559cfde8da [skip ci] Updated translations via Crowdin 2023-03-09 00:06:07 +00:00
WofWca 9db3aedde9 chore: remove an unused duplicate key
Introduced in 172d353df, feels like it was by error
2023-03-08 16:06:56 +00:00
WofWca 0eb78e32f9 chore: improve `@/message` `action` type (#3209)
Reviewed-on: vikunja/frontend#3209
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-08 09:51:55 +00:00
WofWca b4dd23b85d fix: i18ze a string (#3210)
Reviewed-on: vikunja/frontend#3210
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-08 09:43:46 +00:00
renovate 2262b49aaf chore(deps): update pnpm to v7.29.1 2023-03-08 06:56:34 +00:00
renovate c887990bad fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.9.0 2023-03-08 06:05:02 +00:00
renovate 37c5a88744 chore(deps): update node.js to v18.15.0 2023-03-07 20:03:33 +00:00
Dominik Pschenitschni 9b7770ade4 fix(keyboard-shortcuts): use card prop 2023-03-07 17:19:21 +00:00
Dominik Pschenitschni af4a039502
chore: histoire add logo link 2023-03-07 18:07:12 +01:00
Dan Stewart 1b06112db4 fix: collapse menu on mobile when path changes 2023-03-07 15:56:09 +00:00
renovate 0952f059c0 fix(deps): update dependency pinia to v2.0.33 2023-03-07 15:08:55 +00:00
WofWca 0f97ba6ec9 fix: sync sidebar transition with `<main>` (#3200)
Reviewed-on: vikunja/frontend#3200
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-06 18:56:13 +00:00
renovate d4c9edb55d chore(deps): update typescript-eslint monorepo to v5.54.1 2023-03-06 18:04:27 +00:00
renovate 394f056cf4 fix(deps): update sentry-javascript monorepo to v7.41.0 2023-03-06 13:04:12 +00:00
renovate 7672676b6e chore(deps): update pnpm to v7.29.0 2023-03-06 07:30:39 +00:00
renovate 51b33fd67e chore(deps): update dependency caniuse-lite to v1.0.30001460 2023-03-06 07:30:06 +00:00
renovate 0ed3ebda94 chore(deps): update dependency netlify-cli to v13.0.1 2023-03-06 00:06:16 +00:00
danstewart 7b6f76d1b4 fix: stop revealing elements on hover if hover is not supported (#3191)
Resolves #3162

Co-authored-by: Dan Stewart <git@mail.danstewart.dev>
Reviewed-on: vikunja/frontend#3191
Reviewed-by: konrad <k@knt.li>
Co-authored-by: danstewart <danstewart@noreply.kolaente.de>
Co-committed-by: danstewart <danstewart@noreply.kolaente.de>
2023-03-04 16:13:31 +00:00
renovate ad0029789d chore(deps): update dependency esbuild to v0.17.11 2023-03-03 23:04:25 +00:00
renovate e13f57c30a chore(deps): update dependency @types/node to v18.14.6 2023-03-03 22:04:16 +00:00
WofWca 6a3518dace chore(refactor): improve `stores/config` types (#3190)
Reviewed-on: vikunja/frontend#3190
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-03 14:36:59 +00:00
renovate f1ec554d09 chore(deps): update dependency @types/node to v18.14.5 2023-03-03 06:04:12 +00:00
WofWca 6aa02e29b1
chore(services): let `getAll`: always return `Model[]` 2023-03-02 16:44:01 +01:00
WofWca 5f9485414b
chore(services): add examples for some functions 2023-03-02 16:43:46 +01:00
WofWca 149ceaf2e5 fix(quick-actions): nothing happening on team click (#3186)
Reviewed-on: vikunja/frontend#3186
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-02 15:28:43 +00:00
renovate 9b3e185dd4 chore(deps): update dependency @types/node to v18.14.4 2023-03-02 09:04:10 +00:00
renovate 779fe3e323 fix(deps): update sentry-javascript monorepo to v7.40.0 2023-03-02 08:02:54 +00:00
renovate a27b77f24e fix(deps): update dependency dompurify to v3.0.1 2023-03-02 08:02:13 +00:00
renovate 41f22a1035 chore(deps): update dependency rollup to v3.18.0 2023-03-01 23:04:23 +00:00
renovate 28d01c5ba0 chore(deps): update dependency vitest to v0.29.2 2023-03-01 19:04:08 +00:00
Frederick [Bot] e272dd8e64 [skip ci] Updated translations via Crowdin 2023-03-01 00:06:13 +00:00
kolaente c002275e7f
fix(table view): correctly load sort order from local storage
Resolves https://community.vikunja.io/t/table-view-sort-by-due-date-doesnt-persist-after-page-refresh/1198
2023-02-28 11:56:05 +01:00
renovate 1392d7f101 fix(deps): update dependency ufo to v1.1.1 2023-02-27 21:43:42 +00:00
renovate e5758e21c7 chore(deps): update typescript-eslint monorepo to v5.54.0 2023-02-27 18:04:10 +00:00
Dominik Pschenitschni e0f06999be
feat: improve recommended vscode settings 2023-02-27 16:52:48 +01:00
renovate 3b72acff27 fix(deps): update sentry-javascript monorepo to v7.39.0 2023-02-27 15:49:34 +00:00
renovate df1c44aabe chore(deps): update dependency @types/node to v18.14.2 2023-02-27 15:04:16 +00:00
kolaente fe764a46e9
fix(task): allow clicking on the whole task to open the task detail view
Resolves #3172
2023-02-27 16:00:08 +01:00
renovate 000e3080a5 chore(deps): update dependency start-server-and-test to v2 2023-02-27 13:05:45 +00:00
renovate f4c568e961 chore(deps): update dependency start-server-and-test to v1.15.5 2023-02-27 08:55:40 +00:00
renovate ef70ead3f0 chore(deps): update dependency caniuse-lite to v1.0.30001458 2023-02-27 08:55:03 +00:00
renovate 7a326d6e03 chore(deps): update dependency happy-dom to v8.9.0 2023-02-27 08:54:32 +00:00
renovate afb6383a85 chore(deps): update dependency netlify-cli to v13 2023-02-27 01:06:23 +00:00
renovate e49969dcad chore(deps): update dependency rollup to v3.17.3 2023-02-26 13:26:09 +00:00
renovate 81e1d70847 chore(deps): update dependency eslint to v8.35.0 2023-02-26 10:04:19 +00:00
renovate 5226517954 chore(deps): update pnpm to v7.28.0 2023-02-25 17:03:54 +00:00
renovate d5d0f9a8e2 chore(deps): update dependency vitest to v0.29.1 2023-02-25 11:04:09 +00:00
renovate 2337b6c9f3 chore(deps): update dependency vue-tsc to v1.2.0 2023-02-25 10:18:01 +00:00
renovate 289802b13d chore(deps): update dependency cypress to v12.7.0 2023-02-25 03:04:10 +00:00
renovate 5de9a2880f chore(deps): update dependency @cypress/vite-dev-server to v5.0.4 2023-02-24 04:04:04 +00:00
Frederick [Bot] 62f6895950 [skip ci] Updated translations via Crowdin 2023-02-24 00:06:10 +00:00
renovate c198b9a164 chore(deps): update dependency @types/node to v18.14.1 2023-02-23 12:04:39 +00:00
renovate d5f5e2a412 fix(deps): update dependency axios to v1.3.4 2023-02-22 22:04:25 +00:00
kolaente cabee68bbb
fix(docker): make sure the service worker and webmanifest are never cached 2023-02-22 12:18:46 +01:00
kolaente 2fd2214a2e
fix(menu): don't show drag handle for not draggable menu items 2023-02-22 12:17:33 +01:00
kolaente 64735e0c3d
fix(filter): don't allow marking a filter as favorite
Resolves https://community.vikunja.io/t/error-favouriting-filters-lists/1161/1
2023-02-22 12:13:48 +01:00
kolaente 1f40b68108
fix(filter): validate title before creating or editing a filter
Resolves #3152
2023-02-22 11:04:31 +01:00
renovate 4033c28a67 chore(deps): update dependency vue-tsc to v1.1.7 2023-02-22 04:04:05 +00:00
renovate be20a01dd6 chore(deps): update dependency vite to v4.1.4 2023-02-21 20:04:08 +00:00
renovate 8f5a628e54 chore(deps): update node.js to v18.14.2 2023-02-21 19:03:24 +00:00
renovate e0c00b306e fix(deps): update dependency pinia to v2.0.32 2023-02-21 08:04:09 +00:00
renovate 10eaacc552 chore(deps): update dependency vue-tsc to v1.1.5 2023-02-20 20:04:05 +00:00
renovate 4b1465955a chore(deps): update typescript-eslint monorepo to v5.53.0 2023-02-20 19:16:38 +00:00
renovate 1711318212 chore(deps): update dependency esbuild to v0.17.10 2023-02-20 18:04:33 +00:00
renovate b042547aaa chore(deps): update dependency netlify-cli to v12.13.2 2023-02-20 12:29:07 +00:00
renovate ed0db956eb chore(deps): update dependency happy-dom to v8.6.0 2023-02-20 12:04:07 +00:00
renovate a66f8a6484 chore(deps): update dependency rollup to v3.17.2 2023-02-20 11:34:43 +00:00
renovate 47e895149e chore(deps): update dependency vue-tsc to v1.1.4 2023-02-20 11:34:01 +00:00
renovate 8e00014feb fix(deps): update dependency pinia to v2.0.31 2023-02-20 11:33:30 +00:00
renovate 6146340034 fix(deps): update dependency codemirror to v5.65.12 2023-02-20 11:04:16 +00:00
renovate c7b761b0eb chore(deps): update dependency caniuse-lite to v1.0.30001457 2023-02-20 09:32:51 +00:00
renovate a1e84b3460 chore(deps): update dependency @vue/test-utils to v2.3.0 2023-02-20 09:32:12 +00:00
renovate 038debaa22 chore(deps): update dependency vite to v4.1.3 2023-02-20 09:04:17 +00:00
renovate 88faf04251 chore(deps): update dependency esbuild to v0.17.9 2023-02-19 18:04:07 +00:00
renovate 04be2b9745 chore(deps): update dependency rollup to v3.17.1 2023-02-18 20:04:05 +00:00
renovate 815e8cce0e chore(deps): update dependency sass to v1.58.3 2023-02-18 13:04:11 +00:00
renovate d12f9247ff chore(deps): update dependency vue-tsc to v1.1.3 2023-02-18 12:38:10 +00:00
renovate 85e7a17934 chore(deps): update pnpm to v7.27.1 2023-02-18 12:37:43 +00:00
renovate 59c5d43348 chore(deps): update dependency rollup to v3.17.0 2023-02-18 12:37:10 +00:00
renovate c011f9aa52 fix(deps): update dependency @vueuse/core to v9.13.0 2023-02-18 12:36:29 +00:00
renovate b9f5319a4f chore(deps): update histoire to v0.15.8 2023-02-18 12:04:22 +00:00
renovate f120ba4169 chore(deps): update dependency @types/node to v18.14.0 2023-02-17 21:04:08 +00:00
renovate b2b70f4a9d chore(deps): update dependency @cypress/vite-dev-server to v5.0.3 2023-02-17 15:04:01 +00:00
renovate 9facffe3e9 fix(deps): update dependency blurhash to v2.0.5 2023-02-17 14:38:30 +00:00
renovate c31aff1d88 chore(deps): update histoire to v0.15.7 2023-02-17 14:23:27 +00:00
renovate 60dea80462 chore(deps): update dependency rollup to v3.16.0 2023-02-17 14:20:21 +00:00
renovate cd10ccfbc0 fix(deps): update sentry-javascript monorepo to v7.38.0 2023-02-17 14:04:40 +00:00
renovate 8647402038 chore(deps): update dependency vite to v4.1.2 2023-02-17 11:04:08 +00:00
renovate 990fd46302 chore(deps): update node.js to v18.14.1 2023-02-17 10:17:25 +00:00
renovate cf0aafd9e6 fix(deps): update dependency ufo to v1.1.0 2023-02-17 09:15:10 +00:00
renovate 70d2535e93 chore(deps): update dependency sass to v1.58.2 2023-02-17 02:04:07 +00:00
219 changed files with 12391 additions and 10024 deletions

View File

@ -15,6 +15,7 @@ trigger:
services:
- name: api
image: vikunja/api:unstable
pull: always
environment:
VIKUNJA_SERVICE_TESTINGTOKEN: averyLongSecretToSe33dtheDB
VIKUNJA_LOG_LEVEL: DEBUG
@ -521,6 +522,6 @@ steps:
from_secret: crowdin_key
---
kind: signature
hmac: 971875b90c7bb1649d1b00d022d0b594ba9b68f927bf8f0dbe840190816d676b
hmac: 303afeb09b75a57ba88720b45dc06c8bf2c7320e19d738d8299f325438246f75
...

14
.npmrc
View File

@ -1,2 +1,14 @@
fetch-timeout=100000
# pnpm settings
# The following settings prepare for the new default value of pnpm 8
# they can be removed directly after having moved to pnpm 8
auto-install-peers=true
fetch-timeout=100000
dedupe-peer-dependents=true
resolve-peers-from-workspace-root=true
save-workspace-protocol=rolling
resolution-mode=lowest-direct
publishConfig.linkDirectory=true
# remove some time after having moved to pnpm 8
use-lockfile-v6=true

2
.nvmrc
View File

@ -1 +1 @@
18.14.0
18.15.0

View File

@ -18,6 +18,12 @@
"javascriptreact",
"vue"
],
"volar.completion.preferredTagNameCase": "pascal",
// disable vetur in case it is installed
"vetur.validation.template": false,
// i18n ally
"i18n-ally.localesPaths": [
"src/i18n/lang"

View File

@ -9,6 +9,279 @@ All releases can be found on https://code.vikunja.io/frontend/releases.
The releases aim at the api versions which is why there are missing versions.
## [0.20.5] - 2023-03-12
### Bug Fixes
* *(docker)* Add cap_net_bind to the nginx binary in the docker container
* *(docker)* Revert unprivileged user
### Dependencies
* *(deps)* Update dependency sass to v1.59.2
* *(deps)* Update dependency eslint to v8.36.0
## [0.20.4] - 2023-03-10
### Bug Fixes
* *(base)* Use Build Time Base Path
* *(docker)* Cross compilation with buildx
* *(docker)* Default api url
* *(docker)* Make sure the service worker and webmanifest are never cached
* *(filter)* Validate title before creating or editing a filter
* *(filter)* Don't allow marking a filter as favorite
* *(i18n)* Load language files before doing anything else (#3218)
* *(keyboard-shortcuts)* Use card prop
* *(list)* Make sure favorite lists are not duplicated in the menu when renaming them
* *(menu)* Don't show drag handle for not draggable menu items
* *(postcss-preset-env)* Client side polyfills (#3051)
* *(quick actions)* Don't throw an error message when selecting the last items with the arrow keys
* *(quick actions)* Hide edges of last entry on hover
* *(quick add magic)* Correctly parse "next {weekday}" on the beginning of the text
* *(quick-actions)* Nothing happening on team click (#3186)
* *(table view)* Correctly load sort order from local storage
* *(task)* Allow clicking on the whole task to open the task detail view
* *(tests)* Only look in src for tests
* Make sure global error handler handles unrejected promises correctly ([4576da0](4576da0dd394ee68801b1dc424c9550896d63737))
* Use Build Time Base Path (#2964) ([6572f75](6572f75e5d111f7f2dd06e8c2ad0e0d16091fca6))
* Always show update popup on top ([7cbf0ac](7cbf0acac503c508a44e0491ae51e6d5749dfa04))
* Button styles ([d40729c](d40729cbe70b760bcc64d56130a410b05ef9d3dc))
* Stop revealing elements on hover if hover is not supported (#3191) ([7b6f76d](7b6f76d1b4698d0d6c6889aaab3f1cdad80469f8))
* Sync sidebar transition with `<main>` (#3200) ([0f97ba6](0f97ba6ec904226ed91cd3ade8223e2959e9207a))
* Collapse menu on mobile when path changes ([1b06112](1b06112db4ba5ad4144b5868dd04e954be1d77f7))
* I18ze a string (#3210) ([b4dd23b](b4dd23b85d909f7e629e953f1d8543ccbf963a1c))
### Dependencies
* *(deps)* Update sentry-javascript monorepo to v7.33.0 (#3004)
* *(deps)* Update dependency axios to v1.2.4 (#3005)
* *(deps)* Update pnpm to v7.26.0 (#3002)
* *(deps)* Update dependency cypress to v12.4.0 (#3006)
* *(deps)* Update dependency @infectoone/vue-ganttastic to v2.1.4 (#3009)
* *(deps)* Update dependency vitest to v0.28.2 (#3008)
* *(deps)* Update dependency rollup to v3.11.0 (#3013)
* *(deps)* Update dependency @vitejs/plugin-legacy to v3.0.2 (#3012)
* *(deps)* Update dependency axios to v1.2.5
* *(deps)* Update sentry-javascript monorepo to v7.34.0
* *(deps)* Update pnpm to v7.26.1
* *(deps)* Update dependency @vue/test-utils to v2.2.8
* *(deps)* Update dependency vitest to v0.28.3 (#3019)
* *(deps)* Update dependency cypress to v12.4.1
* *(deps)* Update dependency rollup to v3.12.0
* *(deps)* Update dependency esbuild to v0.17.5
* *(deps)* Update dependency axios to v1.2.6
* *(deps)* Update dependency @vueuse/core to v9.12.0
* *(deps)* Update pnpm to v7.26.2
* *(deps)* Update dependency eslint to v8.33.0
* *(deps)* Update dependency netlify-cli to v12.10.0
* *(deps)* Update dependency happy-dom to v8.2.0
* *(deps)* Update dependency caniuse-lite to v1.0.30001449
* *(deps)* Update dependency typescript to v4.9.5
* *(deps)* Update typescript-eslint monorepo to v5.50.0
* *(deps)* Update dependency axios to v1.3.0 (#3036)
* *(deps)* Update dependency sass to v1.58.0
* *(deps)* Update dependency cypress to v12.5.0
* *(deps)* Update pnpm to v7.26.3
* *(deps)* Update dependency rollup to v3.12.1
* *(deps)* Update sentry-javascript monorepo to v7.35.0 (#3041)
* *(deps)* Update dependency pinia to v2.0.30 (#3042)
* *(deps)* Update dependency @vue/test-utils to v2.2.9
* *(deps)* Update dependency axios to v1.3.1
* *(deps)* Update dependency vue to v3.2.47
* *(deps)* Update dependency vite to v4.1.0
* *(deps)* Update dependency postcss-preset-env to v8 (#3000)
* *(deps)* Update dependency @vitejs/plugin-legacy to v4
* *(deps)* Update dependency @vitejs/plugin-legacy to v4.0.1
* *(deps)* Update sentry-javascript monorepo to v7.36.0
* *(deps)* Update dependency vite to v4.1.1
* *(deps)* Update dependency cypress to v12.5.1
* *(deps)* Update dependency @vue/test-utils to v2.2.10
* *(deps)* Update dependency vitest to v0.28.4
* *(deps)* Update dependency rollup to v3.13.0
* *(deps)* Update dependency axios to v1.3.2
* *(deps)* Update dependency rollup to v3.14.0
* *(deps)* Update dependency @types/node to v18.11.19
* *(deps)* Update dependency @histoire/plugin-screenshot to v0.13.0
* *(deps)* Update dependency histoire to v0.13.0
* *(deps)* Update caniuse-and-related
* *(deps)* Update dependency @histoire/plugin-vue to v0.13.0
* *(deps)* Update dependency happy-dom to v8.2.6
* *(deps)* Update typescript-eslint monorepo to v5.51.0
* *(deps)* Update dependency esbuild to v0.17.6
* *(deps)* Update dependency @cypress/vue to v5.0.4
* *(deps)* Update dependency @types/node to v18.13.0
* *(deps)* Update dependency vite-plugin-pwa to v0.14.2
* *(deps)* Update font awesome to v6.3.0
* *(deps)* Update pnpm to v7.27.0
* *(deps)* Update dependency @histoire/plugin-screenshot to v0.13.1
* *(deps)* Update dependency @histoire/plugin-vue to v0.13.1
* *(deps)* Update dependency vite-plugin-pwa to v0.14.3
* *(deps)* Update dependency histoire to v0.13.1
* *(deps)* Update dependency @histoire/plugin-screenshot to v0.13.2
* *(deps)* Update dependency @histoire/plugin-vue to v0.13.2
* *(deps)* Update dependency histoire to v0.13.2
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v0.8.2
* *(deps)* Update sentry-javascript monorepo to v7.37.0
* *(deps)* Update dependency esbuild to v0.17.7
* *(deps)* Update dependency rollup to v3.15.0
* *(deps)* Create a group for all histoire dependencies
* *(deps)* Update dependency @histoire/plugin-vue to v0.14.0
* *(deps)* Update dependency @histoire/plugin-screenshot to v0.14.0
* *(deps)* Update dependency @histoire/plugin-vue to v0.14.0
* *(deps)* Update dependency histoire to v0.14.0
* *(deps)* Update sentry-javascript monorepo to v7.37.1
* *(deps)* Update dependency histoire to v0.14.2
* *(deps)* Include histoire main package in histoire renovate group
* *(deps)* Histoire renovate group
* *(deps)* Update dependency eslint to v8.34.0
* *(deps)* Update histoire to v0.14.2
* *(deps)* Update dependency vite-plugin-pwa to v0.14.4
* *(deps)* Update dependency esbuild to v0.17.8
* *(deps)* Update dependency netlify-cli to v12.12.0
* *(deps)* Update dependency caniuse-lite to v1.0.30001451
* *(deps)* Update dependency vite-plugin-inject-preload to v1.3.0
* *(deps)* Update dependency vitest to v0.28.5
* *(deps)* Update sentry-javascript monorepo to v7.37.2
* *(deps)* Update dependency dompurify to v3 (#3107)
* *(deps)* Update typescript-eslint monorepo to v5.52.0
* *(deps)* Update dependency axios to v1.3.3
* *(deps)* Update dependency start-server-and-test to v1.15.4 (#3109)
* *(deps)* Update dependency sass to v1.58.1
* *(deps)* Update dependency vue-flatpickr-component to v11.0.2 (#3112)
* *(deps)* Update dependency @kyvg/vue3-notification to v2.9.0 (#3113)
* *(deps)* Update histoire to v0.15.1
* *(deps)* Update histoire to v0.15.3
* *(deps)* Update dependency vue-tsc to v1.1.0
* *(deps)* Pin node.js to 18.14.0
* *(deps)* Update dependency cypress to v12.6.0 (#3115)
* *(deps)* Update histoire to v0.15.4
* *(deps)* Update dependency vue-tsc to v1.1.2
* *(deps)* Update dependency sass to v1.58.2
* *(deps)* Update dependency ufo to v1.1.0
* *(deps)* Update node.js to v18.14.1
* *(deps)* Update dependency vite to v4.1.2
* *(deps)* Update sentry-javascript monorepo to v7.38.0
* *(deps)* Update dependency rollup to v3.16.0
* *(deps)* Update histoire to v0.15.7
* *(deps)* Update dependency blurhash to v2.0.5
* *(deps)* Update dependency @cypress/vite-dev-server to v5.0.3
* *(deps)* Update dependency @types/node to v18.14.0
* *(deps)* Update histoire to v0.15.8
* *(deps)* Update dependency @vueuse/core to v9.13.0
* *(deps)* Update dependency rollup to v3.17.0
* *(deps)* Update pnpm to v7.27.1
* *(deps)* Update dependency vue-tsc to v1.1.3
* *(deps)* Update dependency sass to v1.58.3
* *(deps)* Update dependency rollup to v3.17.1
* *(deps)* Update dependency esbuild to v0.17.9
* *(deps)* Update dependency vite to v4.1.3
* *(deps)* Update dependency @vue/test-utils to v2.3.0
* *(deps)* Update dependency caniuse-lite to v1.0.30001457
* *(deps)* Update dependency codemirror to v5.65.12
* *(deps)* Update dependency pinia to v2.0.31
* *(deps)* Update dependency vue-tsc to v1.1.4
* *(deps)* Update dependency rollup to v3.17.2
* *(deps)* Update dependency happy-dom to v8.6.0
* *(deps)* Update dependency netlify-cli to v12.13.2
* *(deps)* Update dependency esbuild to v0.17.10
* *(deps)* Update typescript-eslint monorepo to v5.53.0
* *(deps)* Update dependency vue-tsc to v1.1.5
* *(deps)* Update dependency pinia to v2.0.32
* *(deps)* Update node.js to v18.14.2
* *(deps)* Update dependency vite to v4.1.4
* *(deps)* Update dependency vue-tsc to v1.1.7
* *(deps)* Update dependency axios to v1.3.4
* *(deps)* Update dependency @types/node to v18.14.1
* *(deps)* Update dependency @cypress/vite-dev-server to v5.0.4
* *(deps)* Update dependency cypress to v12.7.0
* *(deps)* Update dependency vue-tsc to v1.2.0
* *(deps)* Update dependency vitest to v0.29.1
* *(deps)* Update pnpm to v7.28.0
* *(deps)* Update dependency eslint to v8.35.0
* *(deps)* Update dependency rollup to v3.17.3
* *(deps)* Update dependency netlify-cli to v13
* *(deps)* Update dependency happy-dom to v8.9.0
* *(deps)* Update dependency caniuse-lite to v1.0.30001458
* *(deps)* Update dependency start-server-and-test to v1.15.5
* *(deps)* Update dependency start-server-and-test to v2
* *(deps)* Update dependency @types/node to v18.14.2
* *(deps)* Update sentry-javascript monorepo to v7.39.0
* *(deps)* Update typescript-eslint monorepo to v5.54.0
* *(deps)* Update dependency ufo to v1.1.1
* *(deps)* Update dependency vitest to v0.29.2
* *(deps)* Update dependency rollup to v3.18.0
* *(deps)* Update dependency dompurify to v3.0.1
* *(deps)* Update sentry-javascript monorepo to v7.40.0
* *(deps)* Update dependency @types/node to v18.14.4
* *(deps)* Update dependency @types/node to v18.14.5
* *(deps)* Update dependency @types/node to v18.14.6
* *(deps)* Update dependency esbuild to v0.17.11
* *(deps)* Update dependency netlify-cli to v13.0.1
* *(deps)* Update dependency caniuse-lite to v1.0.30001460
* *(deps)* Update pnpm to v7.29.0
* *(deps)* Update sentry-javascript monorepo to v7.41.0
* *(deps)* Update typescript-eslint monorepo to v5.54.1
* *(deps)* Update dependency pinia to v2.0.33
* *(deps)* Update node.js to v18.15.0
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v0.9.0
* *(deps)* Update pnpm to v7.29.1
* *(deps)* Update dependency @vue/test-utils to v2.3.1
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v0.9.1
* *(deps)* Update sentry-javascript monorepo to v7.42.0
* *(deps)* Update dependency rollup to v3.19.0
* *(deps)* Update dependency vite-plugin-inject-preload to v1.3.1
* *(deps)* Update dependency @types/node to v18.15.0
* *(deps)* Update dependency autoprefixer to v10.4.14
* *(deps)* Update dependency rollup to v3.19.1
### Features
* *(config)* Support Setting Base Path in .env
* Use v-show for navigation buttons ([7ed1a37](7ed1a37de53cb8c15994e9524a52080170db5950))
* Unindent settings page (#2996) ([13a39be](13a39be3de4d0f7e0f6be9c20e0464e86b87c676))
* Small content auth improvements (#2998) ([2be7847](2be784766f54810f8969e48291ce9181f2854a5b))
* Move update from navigation to app ([3db5ea4](3db5ea45d768d10458eaab0f5ee9dad0df2996e4))
* Improve naming and styles ([eaeddda](eaeddda4e468c2040862d18c9b2d37a1c0ba099e))
* Use klona instead of lodash.clonedeep (#3073) ([7b96397](7b96397e3bfa43a393ca84439069290bc4c8a5c8))
* Refactor to composable ([c502f9b](c502f9b840ee2d65193aa4ef29c7f260b49db0d2))
* Header improvements ([e8db2c2](e8db2c2b458bcae592609d5a5bc3f1b333651b25))
* Persistent menuActive state with Local Storage (#3011) ([e3dd4ef](e3dd4ef78ac818add138d0323bf65abe8a4caa29))
* Fix calculation of token invalidation (#3077) ([d6b55c7](d6b55c757067413bbc34acd48af9fb553f36db8a))
* Use renovate js-app as preset (#3087) ([97c8970](97c8970dd60b2ba1e894ca0039524c8f6a5cd5df))
* Improve recommended vscode settings ([e0f0699](e0f06999beb0a9fb5da817323744307401e85e47))
### Miscellaneous Tasks
* *(refactor)* Improve `stores/config` types (#3190)
* *(services)* Add examples for some functions
* *(services)* Let `getAll`: always return `Model[]`
* Move class name to top ([c6ed925](c6ed9254247efeb43e0763e095b145d6ec1965e1))
* Simplify error handling for login and OpenId Auth ([e67088f](e67088fdb7bd3b24cea6ee37851ef45f1fb7bdad))
* Simplify getting the error text from an exception ([9adf1ab](9adf1aba895a02f416148ddf8b6925689d6e2687))
* Typo ([81a4f2d](81a4f2d9775716bc0056348664fc24185af040d4))
* Update funding links ([7cb0cd2](7cb0cd293d6d277172eccf2558a62427bc86dfe6))
* Update funding links ([b26ea45](b26ea45fe0d1d6f5f070ef42a5d68aa6db8e6b70))
* Remove minimist dependency (not used anywhere) ([f697640](f697640636466e8f035c7d31597ee589379fa017))
* Remove sponsor ([fa0e46a](fa0e46a3991ab423c9364b65439d9e8e5a28cb7b))
* Histoire add logo link ([af4a039](af4a039502b29e9e7e21cf30d44715c7af056c15))
* Improve `@/message` `action` type (#3209) ([0eb78e3](0eb78e32f994e7032725e38d564320a5a04cbf2a))
* Remove an unused duplicate key ([9db3aed](9db3aedde9566fb94717e1dd66a21abdbda6e84a))
### Other
* *(other)* Add Ipv6 support to nginx (#100)
* *(other)* Added ipv6 control script
* *(other)* Disable listening on IPv6 ports when IPv6 is not supported (#102)
* *(other)* Docker refactoring (#3018)
* *(other)* Persist menuActive state in Local Storage
* *(other)* Refactor to only used local storage value when on desktop viewport widths
* *(other)* Solve for resize()
* *(other)* [skip ci] Updated translations via Crowdin
## [0.20.3] - 2023-01-24
### Bug Fixes

View File

@ -66,5 +66,3 @@ RUN chmod 0755 /docker-entrypoint.d/*.sh /etc/nginx/templates && \
chmod -R 0644 /etc/nginx/nginx.conf && \
chown -R nginx:nginx ./ /etc/nginx/conf.d /etc/nginx/templates && \
rm -f /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
# unprivileged user
USER nginx

View File

@ -4,7 +4,7 @@
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/frontend/status.svg)](https://drone.kolaente.de/vikunja/frontend)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.20.3-brightgreen.svg)](https://dl.vikunja.io)
[![Download](https://img.shields.io/badge/download-v0.20.5-brightgreen.svg)](https://dl.vikunja.io)
[![Translation](https://badges.crowdin.net/vikunja/localized.svg)](https://crowdin.com/project/vikunja)
This is the web frontend for Vikunja, written in Vue.js.

View File

@ -1,57 +0,0 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {ListFactory} from '../../factories/list'
import {prepareLists} from './prepareLists'
describe('List History', () => {
createFakeUserAndLogin()
prepareLists()
it('should show a list history on the home page', () => {
cy.intercept(Cypress.env('API_URL') + '/namespaces*').as('loadNamespaces')
cy.intercept(Cypress.env('API_URL') + '/lists/*').as('loadList')
const lists = ListFactory.create(6)
cy.visit('/')
cy.wait('@loadNamespaces')
cy.get('body')
.should('not.contain', 'Last viewed')
cy.visit(`/lists/${lists[0].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[1].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[2].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[3].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[4].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[5].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
// cy.visit('/')
// cy.wait('@loadNamespaces')
// Not using cy.visit here to work around the redirect issue fixed in #1337
cy.get('nav.menu.top-menu a')
.contains('Overview')
.click()
cy.get('body')
.should('contain', 'Last viewed')
cy.get('[data-cy="listCardGrid"]')
.should('not.contain', lists[0].title)
.should('contain', lists[1].title)
.should('contain', lists[2].title)
.should('contain', lists[3].title)
.should('contain', lists[4].title)
.should('contain', lists[5].title)
})
})

View File

@ -1,19 +0,0 @@
import {ListFactory} from '../../factories/list'
import {NamespaceFactory} from '../../factories/namespace'
import {TaskFactory} from '../../factories/task'
export function createLists() {
NamespaceFactory.create(1)
const lists = ListFactory.create(1, {
title: 'First List'
})
TaskFactory.truncate()
return lists
}
export function prepareLists(setLists = (...args: any[]) => {}) {
beforeEach(() => {
const lists = createLists()
setLists(lists)
})
}

View File

@ -1,18 +1,20 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
import {ListFactory} from '../../factories/list'
import {ProjectFactory} from '../../factories/project'
import {NamespaceFactory} from '../../factories/namespace'
import {UserListFactory} from '../../factories/users_list'
import {UserProjectFactory} from '../../factories/users_project'
import {BucketFactory} from '../../factories/bucket'
describe('Editor', () => {
createFakeUserAndLogin()
beforeEach(() => {
NamespaceFactory.create(1)
ListFactory.create(1)
ProjectFactory.create(1)
BucketFactory.create(1)
TaskFactory.truncate()
UserListFactory.truncate()
UserProjectFactory.truncate()
})
it('Has a preview with checkable checkboxes', () => {
@ -24,6 +26,7 @@ describe('Editor', () => {
* [ ] Checklist
* [x] Checklist checked
`,
bucket_id: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)

View File

@ -1,6 +1,6 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {ListFactory} from '../../factories/list'
import {ProjectFactory} from '../../factories/project'
import {NamespaceFactory} from '../../factories/namespace'
describe('Namepaces', () => {
@ -10,7 +10,7 @@ describe('Namepaces', () => {
beforeEach(() => {
namespaces = NamespaceFactory.create(1)
ListFactory.create(1)
ProjectFactory.create(1)
})
it('Should be all there', () => {
@ -99,17 +99,17 @@ describe('Namepaces', () => {
.should('not.contain', newNamespaces[0].title)
})
it('Should not show archived lists & namespaces if the filter is not checked', () => {
it('Should not show archived projects & namespaces if the filter is not checked', () => {
const n = NamespaceFactory.create(1, {
id: 2,
is_archived: true,
}, false)
ListFactory.create(1, {
ProjectFactory.create(1, {
id: 2,
namespace_id: n[0].id,
}, false)
ListFactory.create(1, {
ProjectFactory.create(1, {
id: 3,
is_archived: true,
}, false)
@ -120,7 +120,7 @@ describe('Namepaces', () => {
.should('not.contain', 'Archived')
// Show archived
cy.get('[data-cy="show-archived-check"] label.check span')
cy.get('[data-cy="show-archived-check"] .fancycheckbox__content')
.should('be.visible')
.click()
cy.get('[data-cy="show-archived-check"] input')
@ -129,7 +129,7 @@ describe('Namepaces', () => {
.should('contain', 'Archived')
// Don't show archived
cy.get('[data-cy="show-archived-check"] label.check span')
cy.get('[data-cy="show-archived-check"] .fancycheckbox__content')
.should('be.visible')
.click()
cy.get('[data-cy="show-archived-check"] input')

View File

@ -0,0 +1,19 @@
import {ProjectFactory} from '../../factories/project'
import {NamespaceFactory} from '../../factories/namespace'
import {TaskFactory} from '../../factories/task'
export function createProjects() {
NamespaceFactory.create(1)
const projects = ProjectFactory.create(1, {
title: 'First Project'
})
TaskFactory.truncate()
return projects
}
export function prepareProjects(setProjects = (...args: any[]) => {}) {
beforeEach(() => {
const projects = createProjects()
setProjects(projects)
})
}

View File

@ -0,0 +1,57 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {ProjectFactory} from '../../factories/project'
import {prepareProjects} from './prepareProjects'
describe('Project History', () => {
createFakeUserAndLogin()
prepareProjects()
it('should show a project history on the home page', () => {
cy.intercept(Cypress.env('API_URL') + '/namespaces*').as('loadNamespaces')
cy.intercept(Cypress.env('API_URL') + '/projects/*').as('loadProject')
const projects = ProjectFactory.create(6)
cy.visit('/')
cy.wait('@loadNamespaces')
cy.get('body')
.should('not.contain', 'Last viewed')
cy.visit(`/projects/${projects[0].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadProject')
cy.visit(`/projects/${projects[1].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadProject')
cy.visit(`/projects/${projects[2].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadProject')
cy.visit(`/projects/${projects[3].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadProject')
cy.visit(`/projects/${projects[4].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadProject')
cy.visit(`/projects/${projects[5].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadProject')
// cy.visit('/')
// cy.wait('@loadNamespaces')
// Not using cy.visit here to work around the redirect issue fixed in #1337
cy.get('nav.menu.top-menu a')
.contains('Overview')
.click()
cy.get('body')
.should('contain', 'Last viewed')
cy.get('[data-cy="projectCardGrid"]')
.should('not.contain', projects[0].title)
.should('contain', projects[1].title)
.should('contain', projects[2].title)
.should('contain', projects[3].title)
.should('contain', projects[4].title)
.should('contain', projects[5].title)
})
})

View File

@ -3,15 +3,15 @@ import {formatISO, format} from 'date-fns'
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
import {prepareLists} from './prepareLists'
import {prepareProjects} from './prepareProjects'
describe('List View Gantt', () => {
describe('Project View Gantt', () => {
createFakeUserAndLogin()
prepareLists()
prepareProjects()
it('Hides tasks with no dates', () => {
const tasks = TaskFactory.create(1)
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.g-gantt-rows-container')
.should('not.contain', tasks[0].title)
@ -25,7 +25,7 @@ describe('List View Gantt', () => {
nextMonth.setDate(1)
nextMonth.setMonth(9)
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.g-timeunits-container')
.should('contain', format(now, 'MMMM'))
@ -38,7 +38,7 @@ describe('List View Gantt', () => {
start_date: now.toISOString(),
end_date: new Date(new Date(now).setDate(now.getDate() + 4)).toISOString(),
})
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.g-gantt-rows-container')
.should('not.be.empty')
@ -50,7 +50,7 @@ describe('List View Gantt', () => {
start_date: null,
end_date: null,
})
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.gantt-options .fancycheckbox')
.contains('Show tasks which don\'t have dates set')
@ -69,7 +69,7 @@ describe('List View Gantt', () => {
start_date: now.toISOString(),
end_date: new Date(new Date(now).setDate(now.getDate() + 4)).toISOString(),
})
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.g-gantt-rows-container .g-gantt-row .g-gantt-row-bars-container div .g-gantt-bar')
.first()
@ -83,9 +83,9 @@ describe('List View Gantt', () => {
const now = Date.UTC(2022, 10, 9)
cy.clock(now, ['Date'])
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.list-gantt .gantt-options .field .control input.input.form-control')
cy.get('.project-gantt .gantt-options .field .control input.input.form-control')
.click()
cy.get('.flatpickr-calendar .flatpickr-innerContainer .dayContainer .flatpickr-day')
.first()
@ -99,13 +99,13 @@ describe('List View Gantt', () => {
})
it('Should change the date range based on date query parameters', () => {
cy.visit('/lists/1/gantt?dateFrom=2022-09-25&dateTo=2022-11-05')
cy.visit('/projects/1/gantt?dateFrom=2022-09-25&dateTo=2022-11-05')
cy.get('.g-timeunits-container')
.should('contain', 'September 2022')
.should('contain', 'October 2022')
.should('contain', 'November 2022')
cy.get('.list-gantt .gantt-options .field .control input.input.form-control')
cy.get('.project-gantt .gantt-options .field .control input.input.form-control')
.should('have.value', '25 Sep 2022 to 5 Nov 2022')
})
@ -115,7 +115,7 @@ describe('List View Gantt', () => {
start_date: formatISO(now),
end_date: formatISO(now.setDate(now.getDate() + 4)),
})
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.gantt-container .g-gantt-chart .g-gantt-row-bars-container .g-gantt-bar')
.dblclick()

View File

@ -1,13 +1,13 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {BucketFactory} from '../../factories/bucket'
import {ListFactory} from '../../factories/list'
import {ProjectFactory} from '../../factories/project'
import {TaskFactory} from '../../factories/task'
import {prepareLists} from './prepareLists'
import {prepareProjects} from './prepareProjects'
describe('List View Kanban', () => {
describe('Project View Kanban', () => {
createFakeUserAndLogin()
prepareLists()
prepareProjects()
let buckets
beforeEach(() => {
@ -16,10 +16,10 @@ describe('List View Kanban', () => {
it('Shows all buckets with their tasks', () => {
const data = TaskFactory.create(10, {
list_id: 1,
project_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .title')
.contains(buckets[0].title)
@ -34,10 +34,10 @@ describe('List View Kanban', () => {
it('Can add a new task to a bucket', () => {
TaskFactory.create(2, {
list_id: 1,
project_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket')
.contains(buckets[0].title)
@ -55,7 +55,7 @@ describe('List View Kanban', () => {
})
it('Can create a new bucket', () => {
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket.new-bucket .button')
.click()
@ -69,7 +69,7 @@ describe('List View Kanban', () => {
})
it('Can set a bucket limit', () => {
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
.first()
@ -90,7 +90,7 @@ describe('List View Kanban', () => {
})
it('Can rename a bucket', () => {
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .bucket-header .title')
.first()
@ -101,7 +101,7 @@ describe('List View Kanban', () => {
})
it('Can delete a bucket', () => {
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
.first()
@ -125,10 +125,10 @@ describe('List View Kanban', () => {
it('Can drag tasks around', () => {
const tasks = TaskFactory.create(2, {
list_id: 1,
project_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
@ -144,10 +144,10 @@ describe('List View Kanban', () => {
it('Should navigate to the task when the task card is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
project_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
@ -158,18 +158,18 @@ describe('List View Kanban', () => {
.should('contain', `/tasks/${tasks[0].id}`, { timeout: 1000 })
})
it('Should remove a task from the kanban board when moving it to another list', () => {
const lists = ListFactory.create(2)
it('Should remove a task from the kanban board when moving it to another project', () => {
const projects = ProjectFactory.create(2)
BucketFactory.create(2, {
list_id: '{increment}',
project_id: '{increment}',
})
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
project_id: 1,
bucket_id: 1,
})
const task = tasks[0]
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .tasks .task')
.contains(task.title)
@ -180,7 +180,7 @@ describe('List View Kanban', () => {
.contains('Move')
.click()
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
.type(`${lists[1].title}{enter}`)
.type(`${projects[1].title}{enter}`)
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
// presses enter and we can't simulate pressing on enter to select the item.
cy.get('.task-view .content.details .field .multiselect.control .search-results')
@ -197,26 +197,26 @@ describe('List View Kanban', () => {
it('Shows a button to filter the kanban board', () => {
const data = TaskFactory.create(10, {
list_id: 1,
project_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.list-kanban .filter-container .base-button')
cy.get('.project-kanban .filter-container .base-button')
.should('exist')
})
it('Should remove a task from the board when deleting it', () => {
const lists = ListFactory.create(1)
const projects = ProjectFactory.create(1)
const buckets = BucketFactory.create(2, {
list_id: lists[0].id,
project_id: projects[0].id,
})
const tasks = TaskFactory.create(5, {
list_id: 1,
project_id: 1,
bucket_id: buckets[0].id,
})
const task = tasks[0]
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .tasks .task')
.contains(task.title)

View File

@ -1,32 +1,32 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {UserListFactory} from '../../factories/users_list'
import {UserProjectFactory} from '../../factories/users_project'
import {TaskFactory} from '../../factories/task'
import {UserFactory} from '../../factories/user'
import {ListFactory} from '../../factories/list'
import {prepareLists} from './prepareLists'
import {ProjectFactory} from '../../factories/project'
import {prepareProjects} from './prepareProjects'
describe('List View List', () => {
describe('Project View Project', () => {
createFakeUserAndLogin()
prepareLists()
prepareProjects()
it('Should be an empty list', () => {
cy.visit('/lists/1')
it('Should be an empty project', () => {
cy.visit('/projects/1')
cy.url()
.should('contain', '/lists/1/list')
cy.get('.list-title')
.should('contain', 'First List')
cy.get('.list-title-dropdown')
.should('contain', '/projects/1/list')
cy.get('.project-title')
.should('contain', 'First Project')
cy.get('.project-title-dropdown')
.should('exist')
cy.get('p')
.contains('This list is currently empty.')
.contains('This project is currently empty.')
.should('exist')
})
it('Should create a new task', () => {
const newTaskTitle = 'New task'
cy.visit('/lists/1')
cy.visit('/projects/1')
cy.get('.task-add textarea')
.type(newTaskTitle+'{enter}')
cy.get('.tasks')
@ -36,9 +36,9 @@ describe('List View List', () => {
it('Should navigate to the task when the title is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
project_id: 1,
})
cy.visit('/lists/1/list')
cy.visit('/projects/1/list')
cy.get('.tasks .task .tasktext')
.contains(tasks[0].title)
@ -49,33 +49,33 @@ describe('List View List', () => {
.should('contain', `/tasks/${tasks[0].id}`)
})
it('Should not see any elements for a list which is shared read only', () => {
it('Should not see any elements for a project which is shared read only', () => {
UserFactory.create(2)
UserListFactory.create(1, {
list_id: 2,
UserProjectFactory.create(1, {
project_id: 2,
user_id: 1,
right: 0,
})
const lists = ListFactory.create(2, {
const projects = ProjectFactory.create(2, {
owner_id: '{increment}',
namespace_id: '{increment}',
})
cy.visit(`/lists/${lists[1].id}/`)
cy.visit(`/projects/${projects[1].id}/`)
cy.get('.list-title-wrapper .icon')
cy.get('.project-title-wrapper .icon')
.should('not.exist')
cy.get('input.input[placeholder="Add a new task..."')
.should('not.exist')
})
it('Should only show the color of a list in the navigation and not in the list view', () => {
const lists = ListFactory.create(1, {
it('Should only show the color of a project in the navigation and not in the list view', () => {
const projects = ProjectFactory.create(1, {
hex_color: '00db60',
})
TaskFactory.create(10, {
list_id: lists[0].id,
project_id: projects[0].id,
})
cy.visit(`/lists/${lists[0].id}/`)
cy.visit(`/projects/${projects[0].id}/`)
cy.get('.menu-list li .list-menu-link .color-bubble')
.should('have.css', 'background-color', 'rgb(0, 219, 96)')
@ -87,9 +87,9 @@ describe('List View List', () => {
const tasks = TaskFactory.create(100, {
id: '{increment}',
title: i => `task${i}`,
list_id: 1,
project_id: 1,
})
cy.visit('/lists/1/list')
cy.visit('/projects/1/list')
cy.get('.tasks')
.should('contain', tasks[1].title)

View File

@ -2,37 +2,37 @@ import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
describe('List View Table', () => {
describe('Project View Table', () => {
createFakeUserAndLogin()
it('Should show a table with tasks', () => {
const tasks = TaskFactory.create(1)
cy.visit('/lists/1/table')
cy.visit('/projects/1/table')
cy.get('.list-table table.table')
cy.get('.project-table table.table')
.should('exist')
cy.get('.list-table table.table')
cy.get('.project-table table.table')
.should('contain', tasks[0].title)
})
it('Should have working column switches', () => {
TaskFactory.create(1)
cy.visit('/lists/1/table')
cy.visit('/projects/1/table')
cy.get('.list-table .filter-container .items .button')
cy.get('.project-table .filter-container .items .button')
.contains('Columns')
.click()
cy.get('.list-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox')
.contains('Priority')
.click()
cy.get('.list-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox')
.contains('Done')
.click()
cy.get('.list-table table.table th')
cy.get('.project-table table.table th')
.contains('Priority')
.should('exist')
cy.get('.list-table table.table th')
cy.get('.project-table table.table th')
.contains('Done')
.should('not.exist')
})
@ -40,11 +40,11 @@ describe('List View Table', () => {
it('Should navigate to the task when the title is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
project_id: 1,
})
cy.visit('/lists/1/table')
cy.visit('/projects/1/table')
cy.get('.list-table table.table')
cy.get('.project-table table.table')
.contains(tasks[0].title)
.click()

View File

@ -1,58 +1,60 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
import {prepareLists} from './prepareLists'
import {prepareProjects} from './prepareProjects'
describe('Lists', () => {
describe('Projects', () => {
createFakeUserAndLogin()
let lists
prepareLists((newLists) => (lists = newLists))
let projects
prepareProjects((newProjects) => (projects = newProjects))
it('Should create a new list', () => {
it('Should create a new project', () => {
cy.visit('/')
cy.get('.namespace-title .dropdown-trigger')
.click()
cy.get('.namespace-title .dropdown .dropdown-item')
.contains('New list')
.contains('New project')
.click()
cy.url()
.should('contain', '/lists/new/1')
.should('contain', '/projects/new/1')
cy.get('.card-header-title')
.contains('New list')
.contains('New project')
cy.get('input.input')
.type('New List')
.type('New Project')
cy.get('.button')
.contains('Create')
.click()
cy.get('.global-notification', { timeout: 1000 }) // Waiting until the request to create the new list is done
cy.get('.global-notification', { timeout: 1000 }) // Waiting until the request to create the new project is done
.should('contain', 'Success')
cy.url()
.should('contain', '/lists/')
cy.get('.list-title')
.should('contain', 'New List')
.should('contain', '/projects/')
cy.get('.project-title')
.should('contain', 'New Project')
})
it('Should redirect to a specific list view after visited', () => {
cy.visit('/lists/1/kanban')
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.url()
.should('contain', '/lists/1/kanban')
cy.visit('/lists/1')
.should('contain', '/projects/1/kanban')
cy.wait('@loadBuckets')
cy.visit('/projects/1')
cy.url()
.should('contain', '/lists/1/kanban')
.should('contain', '/projects/1/kanban')
})
it('Should rename the list in all places', () => {
it('Should rename the project in all places', () => {
TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
project_id: 1,
})
const newListName = 'New list name'
const newProjectName = 'New project name'
cy.visit('/lists/1')
cy.get('.list-title')
.should('contain', 'First List')
cy.visit('/projects/1')
cy.get('.project-title')
.should('contain', 'First Project')
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .menu-list-dropdown-trigger')
.click()
@ -60,27 +62,27 @@ describe('Lists', () => {
.contains('Edit')
.click()
cy.get('#title')
.type(`{selectall}${newListName}`)
.type(`{selectall}${newProjectName}`)
cy.get('footer.card-footer .button')
.contains('Save')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.list-title')
.should('contain', newListName)
.should('not.contain', lists[0].title)
cy.get('.project-title')
.should('contain', newProjectName)
.should('not.contain', projects[0].title)
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child')
.should('contain', newListName)
.should('not.contain', lists[0].title)
.should('contain', newProjectName)
.should('not.contain', projects[0].title)
cy.visit('/')
cy.get('.card-content')
.should('contain', newListName)
.should('not.contain', lists[0].title)
.should('contain', newProjectName)
.should('not.contain', projects[0].title)
})
it('Should remove a list', () => {
cy.visit(`/lists/${lists[0].id}`)
it('Should remove a project', () => {
cy.visit(`/projects/${projects[0].id}`)
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .menu-list-dropdown-trigger')
.click()
@ -96,27 +98,27 @@ describe('Lists', () => {
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.namespace-container .menu.namespaces-lists .menu-list')
.should('not.contain', lists[0].title)
.should('not.contain', projects[0].title)
cy.location('pathname')
.should('equal', '/')
})
it('Should archive a list', () => {
cy.visit(`/lists/${lists[0].id}`)
it('Should archive a project', () => {
cy.visit(`/projects/${projects[0].id}`)
cy.get('.list-title-dropdown')
cy.get('.project-title-dropdown')
.click()
cy.get('.list-title-dropdown .dropdown-menu .dropdown-item')
cy.get('.project-title-dropdown .dropdown-menu .dropdown-item')
.contains('Archive')
.click()
cy.get('.modal-content')
.should('contain.text', 'Archive this list')
.should('contain.text', 'Archive this project')
cy.get('.modal-content [data-cy=modalPrimary]')
.click()
cy.get('.namespace-container .menu.namespaces-lists .menu-list')
.should('not.contain', lists[0].title)
.should('not.contain', projects[0].title)
cy.get('main.app-content')
.should('contain.text', 'This list is archived. It is not possible to create new or edit tasks for it.')
.should('contain.text', 'This project is archived. It is not possible to create new or edit tasks for it.')
})
})

View File

@ -1,22 +1,22 @@
import {LinkShareFactory} from '../../factories/link_sharing'
import {ListFactory} from '../../factories/list'
import {ProjectFactory} from '../../factories/project'
import {TaskFactory} from '../../factories/task'
describe('Link shares', () => {
it('Can view a link share', () => {
const lists = ListFactory.create(1)
const projects = ProjectFactory.create(1)
const tasks = TaskFactory.create(10, {
list_id: lists[0].id
project_id: projects[0].id
})
const linkShares = LinkShareFactory.create(1, {
list_id: lists[0].id,
project_id: projects[0].id,
right: 0,
})
cy.visit(`/share/${linkShares[0].hash}/auth`)
cy.get('h1.title')
.should('contain', lists[0].title)
.should('contain', projects[0].title)
cy.get('input.input[placeholder="Add a new task..."')
.should('not.exist')
cy.get('.tasks')

View File

@ -1,6 +1,6 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {ListFactory} from '../../factories/list'
import {ProjectFactory} from '../../factories/project'
import {seed} from '../../support/seed'
import {TaskFactory} from '../../factories/task'
import {NamespaceFactory} from '../../factories/namespace'
@ -9,9 +9,9 @@ import {updateUserSettings} from '../../support/updateUserSettings'
function seedTasks(numberOfTasks = 50, startDueDate = new Date()) {
NamespaceFactory.create(1)
const list = ListFactory.create()[0]
const project = ProjectFactory.create()[0]
BucketFactory.create(1, {
list_id: list.id,
project_id: project.id,
})
const tasks = []
let dueDate = startDueDate
@ -20,7 +20,7 @@ function seedTasks(numberOfTasks = 50, startDueDate = new Date()) {
dueDate = new Date(new Date(dueDate).setDate(dueDate.getDate() + 2))
tasks.push({
id: i + 1,
list_id: list.id,
project_id: project.id,
done: false,
created_by_id: 1,
title: 'Test Task ' + i,
@ -31,7 +31,7 @@ function seedTasks(numberOfTasks = 50, startDueDate = new Date()) {
})
}
seed(TaskFactory.table, tasks)
return {tasks, list}
return {tasks, project}
}
describe('Home Page Task Overview', () => {
@ -73,7 +73,7 @@ describe('Home Page Task Overview', () => {
due_date: new Date().toISOString(),
}, false)
cy.visit(`/lists/${tasks[0].list_id}/list`)
cy.visit(`/projects/${tasks[0].project_id}/list`)
cy.get('.tasks .task')
.first()
.should('contain.text', newTaskTitle)
@ -90,7 +90,7 @@ describe('Home Page Task Overview', () => {
cy.visit('/')
cy.visit(`/lists/${tasks[0].list_id}/list`)
cy.visit(`/projects/${tasks[0].project_id}/list`)
cy.get('.task-add textarea')
.type(newTaskTitle+'{enter}')
cy.visit('/')
@ -113,10 +113,10 @@ describe('Home Page Task Overview', () => {
.should('contain.text', newTaskTitle)
})
it('Should show a task without a due date added via default list at the bottom', () => {
const {list} = seedTasks(40)
it('Should show a task without a due date added via default project at the bottom', () => {
const {project} = seedTasks(40)
updateUserSettings({
default_list_id: list.id,
default_project_id: project.id,
overdue_tasks_reminders_time: '9:00',
})
@ -131,23 +131,23 @@ describe('Home Page Task Overview', () => {
.should('contain.text', newTaskTitle)
})
it('Should show the cta buttons for new list when there are no tasks', () => {
it('Should show the cta buttons for new project when there are no tasks', () => {
TaskFactory.truncate()
cy.visit('/')
cy.get('.home.app-content .content')
.should('contain.text', 'You can create a new list for your new tasks:')
.should('contain.text', 'Or import your lists and tasks from other services into Vikunja:')
.should('contain.text', 'You can create a new project for your new tasks:')
.should('contain.text', 'Or import your projects and tasks from other services into Vikunja:')
})
it('Should not show the cta buttons for new list when there are tasks', () => {
it('Should not show the cta buttons for new project when there are tasks', () => {
seedTasks()
cy.visit('/')
cy.get('.home.app-content .content')
.should('not.contain.text', 'You can create a new list for your new tasks:')
.should('not.contain.text', 'Or import your lists and tasks from other services into Vikunja:')
.should('not.contain.text', 'You can create a new project for your new tasks:')
.should('not.contain.text', 'Or import your projects and tasks from other services into Vikunja:')
})
})

View File

@ -1,11 +1,11 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
import {ListFactory} from '../../factories/list'
import {ProjectFactory} from '../../factories/project'
import {TaskCommentFactory} from '../../factories/task_comment'
import {UserFactory} from '../../factories/user'
import {NamespaceFactory} from '../../factories/namespace'
import {UserListFactory} from '../../factories/users_list'
import {UserProjectFactory} from '../../factories/users_project'
import {TaskAssigneeFactory} from '../../factories/task_assignee'
import {LabelFactory} from '../../factories/labels'
import {LabelTaskFactory} from '../../factories/label_task'
@ -48,22 +48,22 @@ describe('Task', () => {
createFakeUserAndLogin()
let namespaces
let lists
let projects
let buckets
beforeEach(() => {
// UserFactory.create(1)
namespaces = NamespaceFactory.create(1)
lists = ListFactory.create(1)
projects = ProjectFactory.create(1)
buckets = BucketFactory.create(1, {
list_id: lists[0].id,
project_id: projects[0].id,
})
TaskFactory.truncate()
UserListFactory.truncate()
UserProjectFactory.truncate()
})
it('Should be created new', () => {
cy.visit('/lists/1/list')
cy.visit('/projects/1/list')
cy.get('.input[placeholder="Add a new task…"')
.type('New Task')
cy.get('.button')
@ -74,11 +74,11 @@ describe('Task', () => {
.should('contain', 'New Task')
})
it('Inserts new tasks at the top of the list', () => {
it('Inserts new tasks at the top of the project', () => {
TaskFactory.create(1)
cy.visit('/lists/1/list')
cy.get('.list-is-empty-notice')
cy.visit('/projects/1/list')
cy.get('.project-is-empty-notice')
.should('not.exist')
cy.get('.input[placeholder="Add a new task…"')
.type('New Task')
@ -95,8 +95,8 @@ describe('Task', () => {
it('Marks a task as done', () => {
TaskFactory.create(1)
cy.visit('/lists/1/list')
cy.get('.tasks .task .fancycheckbox label.check')
cy.visit('/projects/1/list')
cy.get('.tasks .task .fancycheckbox')
.first()
.click()
cy.get('.global-notification')
@ -106,7 +106,7 @@ describe('Task', () => {
it('Can add a task to favorites', () => {
TaskFactory.create(1)
cy.visit('/lists/1/list')
cy.visit('/projects/1/list')
cy.get('.tasks .task .favorite')
.first()
.click()
@ -134,7 +134,7 @@ describe('Task', () => {
.should('contain', '#1')
cy.get('.task-view h6.subtitle')
.should('contain', namespaces[0].title)
.should('contain', lists[0].title)
.should('contain', projects[0].title)
cy.get('.task-view .details.content.description')
.should('contain', tasks[0].description)
cy.get('.task-view .action-buttons p.created')
@ -179,21 +179,21 @@ describe('Task', () => {
.should('contain', 'Mark as undone')
})
it('Shows a task identifier since the list has one', () => {
const lists = ListFactory.create(1, {
it('Shows a task identifier since the project has one', () => {
const projects = ProjectFactory.create(1, {
id: 1,
identifier: 'TEST',
})
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
project_id: projects[0].id,
index: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view h1.title.task-id')
.should('contain', `${lists[0].identifier}-${tasks[0].index}`)
.should('contain', `${projects[0].identifier}-${tasks[0].index}`)
})
it('Can edit the description', () => {
@ -236,14 +236,14 @@ describe('Task', () => {
.should('contain', 'Success')
})
it('Can move a task to another list', () => {
const lists = ListFactory.create(2)
it('Can move a task to another project', () => {
const projects = ProjectFactory.create(2)
BucketFactory.create(2, {
list_id: '{increment}'
project_id: '{increment}'
})
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
project_id: projects[0].id,
})
cy.visit(`/tasks/${tasks[0].id}`)
@ -251,7 +251,7 @@ describe('Task', () => {
.contains('Move')
.click()
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
.type(`${lists[1].title}{enter}`)
.type(`${projects[1].title}{enter}`)
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
// presses enter and we can't simulate pressing on enter to select the item.
cy.get('.task-view .content.details .field .multiselect.control .search-results')
@ -261,7 +261,7 @@ describe('Task', () => {
cy.get('.task-view h6.subtitle')
.should('contain', namespaces[0].title)
.should('contain', lists[1].title)
.should('contain', projects[1].title)
cy.get('.global-notification')
.should('contain', 'Success')
})
@ -269,7 +269,7 @@ describe('Task', () => {
it('Can delete a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
project_id: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
@ -286,17 +286,17 @@ describe('Task', () => {
cy.get('.global-notification')
.should('contain', 'Success')
cy.url()
.should('contain', `/lists/${tasks[0].list_id}/`)
.should('contain', `/projects/${tasks[0].project_id}/`)
})
it('Can add an assignee to a task', () => {
const users = UserFactory.create(5)
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
project_id: 1,
})
UserListFactory.create(5, {
list_id: 1,
UserProjectFactory.create(5, {
project_id: 1,
user_id: '{increment}',
})
@ -321,10 +321,10 @@ describe('Task', () => {
const users = UserFactory.create(2)
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
project_id: 1,
})
UserListFactory.create(5, {
list_id: 1,
UserProjectFactory.create(5, {
project_id: 1,
user_id: '{increment}',
})
TaskAssigneeFactory.create(1, {
@ -347,7 +347,7 @@ describe('Task', () => {
it('Can add a new label to a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
project_id: 1,
})
LabelFactory.truncate()
const newLabelText = 'some new label'
@ -375,7 +375,7 @@ describe('Task', () => {
it('Can add an existing label to a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
project_id: 1,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
@ -388,13 +388,13 @@ describe('Task', () => {
it('Can add a label to a task and it shows up on the kanban board afterwards', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
project_id: projects[0].id,
bucket_id: buckets[0].id,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
cy.visit(`/lists/${lists[0].id}/kanban`)
cy.visit(`/projects/${projects[0].id}/kanban`)
cy.get('.bucket .task')
.contains(tasks[0].title)
@ -412,7 +412,7 @@ describe('Task', () => {
it('Can remove a label from a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
project_id: 1,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.create(1, {
@ -527,13 +527,13 @@ describe('Task', () => {
TaskAttachmentFactory.truncate()
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
project_id: projects[0].id,
bucket_id: buckets[0].id,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
cy.visit(`/lists/${lists[0].id}/kanban`)
cy.visit(`/projects/${projects[0].id}/kanban`)
cy.get('.bucket .task')
.contains(tasks[0].title)

View File

@ -6,6 +6,7 @@
"isolatedModules": false,
"target": "ES2015",
"lib": ["ESNext", "dom"],
"types": ["cypress"]
"types": ["cypress"],
"ignoreDeprecations": "5.0"
}
}

View File

@ -1,12 +1,14 @@
import {UserFactory} from '../../factories/user'
const testAndAssertFailed = fixture => {
cy.intercept(Cypress.env('API_URL') + '/login*').as('login')
cy.visit('/login')
cy.get('input[id=username]').type(fixture.username)
cy.get('input[id=password]').type(fixture.password)
cy.get('.button').contains('Login').click()
cy.wait(5000) // It can take waaaayy too long to log the user in
cy.wait('@login')
cy.url().should('include', '/')
cy.get('div.message.danger').contains('Wrong username or password.')
}

View File

@ -1,5 +1,5 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {createLists} from '../list/prepareLists'
import {createProjects} from '../project/prepareProjects'
function logout() {
cy.get('.navbar .username-dropdown-trigger')
@ -26,21 +26,21 @@ describe('Log out', () => {
})
})
it.skip('Should clear the list history after logging the user out', () => {
const lists = createLists()
cy.visit(`/lists/${lists[0].id}`)
it.skip('Should clear the project history after logging the user out', () => {
const projects = createProjects()
cy.visit(`/projects/${projects[0].id}`)
.then(() => {
expect(localStorage.getItem('listHistory')).to.not.eq(null)
expect(localStorage.getItem('projectHistory')).to.not.eq(null)
})
logout()
cy.wait(1000) // This makes re-loading of the list and associated entities (and the resulting error) visible
cy.wait(1000) // This makes re-loading of the project and associated entities (and the resulting error) visible
cy.url()
.should('contain', '/login')
.then(() => {
expect(localStorage.getItem('listHistory')).to.eq(null)
expect(localStorage.getItem('projectHistory')).to.eq(null)
})
})
})

View File

@ -10,7 +10,7 @@ export class BucketFactory extends Factory {
return {
id: '{increment}',
title: faker.lorem.words(3),
list_id: 1,
project_id: 1,
created_by_id: 1,
created: now.toISOString(),
updated: now.toISOString(),

View File

@ -10,7 +10,7 @@ export class LinkShareFactory extends Factory {
return {
id: '{increment}',
hash: faker.random.word(32),
list_id: 1,
project_id: 1,
right: 0,
sharing_type: 0,
shared_by_id: 1,

View File

@ -1,8 +1,8 @@
import {Factory} from '../support/factory'
import {faker} from '@faker-js/faker'
export class ListFactory extends Factory {
static table = 'lists'
export class ProjectFactory extends Factory {
static table = 'projects'
static factory() {
const now = new Date()

View File

@ -11,7 +11,7 @@ export class TaskFactory extends Factory {
id: '{increment}',
title: faker.lorem.words(3),
done: false,
list_id: 1,
project_id: 1,
created_by_id: 1,
index: '{increment}',
position: '{increment}',

View File

@ -1,14 +1,14 @@
import {Factory} from '../support/factory'
export class UserListFactory extends Factory {
static table = 'users_lists'
export class UserProjectFactory extends Factory {
static table = 'users_projects'
static factory() {
const now = new Date()
return {
id: '{increment}',
list_id: 1,
project_id: 1,
user_id: 1,
right: 0,
created: now.toISOString(),

View File

@ -28,6 +28,20 @@ server {
add_header Cache-Control "public, max-age=0, s-maxage=0, must-revalidate" always;
try_files $uri /index.html =404;
}
# Disable caching for sw
location = /sw.js {
autoindex off;
expires off;
add_header Cache-Control "public, max-age=0, s-maxage=0, must-revalidate" always;
}
# Disable caching for webmanifest
location = /manifest.webmanifest {
autoindex off;
expires off;
add_header Cache-Control "public, max-age=0, s-maxage=0, must-revalidate" always;
}
# favicon.ico
location = /favicon.ico {

View File

@ -30,21 +30,21 @@ A basic service can look like this:
```javascript
import AbstractService from './abstractService'
import ListModel from '../models/list'
import ProjectModel from '../models/project'
export default class ListService extends AbstractService {
export default class ProjectService extends AbstractService {
constructor() {
super({
getAll: '/lists',
get: '/lists/{id}',
create: '/namespaces/{namespaceID}/lists',
update: '/lists/{id}',
delete: '/lists/{id}',
getAll: '/projects',
get: '/projects/{id}',
create: '/namespaces/{namespaceID}/projects',
update: '/projects/{id}',
delete: '/projects/{id}',
})
}
modelFactory(data) {
return new ListModel(data)
return new ProjectModel(data)
}
}
```
@ -132,7 +132,7 @@ import AbstractModel from './abstractModel'
import TaskModel from './task'
import UserModel from './user'
export default class ListModel extends AbstractModel {
export default class ProjectModel extends AbstractModel {
constructor(data) {
// The constructor of AbstractModel handles all the default parsing.

10
env.d.ts vendored
View File

@ -3,6 +3,16 @@
/// <reference types="cypress" />
/// <reference types="@histoire/plugin-vue/components" />
declare module 'postcss-focus-within/browser' {
import focusWithinInit from 'postcss-focus-within/browser'
export default focusWithinInit
}
declare module 'css-has-pseudo/browser' {
import cssHasPseudo from 'css-has-pseudo/browser'
export default cssHasPseudo
}
interface ImportMetaEnv {
readonly VITE_IS_ONLINE: boolean
}

View File

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1664753041,
"narHash": "sha256-0ogaD8PaGHluARFeupofvk1Nq9gpVeZdlFM0Kcwguys=",
"lastModified": 1680030621,
"narHash": "sha256-qQa1NeS5Rvk2lgK5lSk986PC6I72yIHejzM8PFu+dHs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a62844b302507c7531ad68a86cb7aa54704c9cb4",
"rev": "402cc3633cc60dfc50378197305c984518b30773",
"type": "github"
},
"original": {

View File

@ -28,7 +28,7 @@ export default defineConfig({
// light: './img/light.png',
// dark: './img/dark.png',
// },
// logoHref: 'https://acme.com',
logoHref: 'https://vikunja.io',
// favicon: './favicon.ico',
},
})

Binary file not shown.

Binary file not shown.

View File

@ -13,7 +13,7 @@
},
"homepage": "https://vikunja.io/",
"funding": "https://opencollective.com/vikunja",
"packageManager": "pnpm@7.27.0",
"packageManager": "pnpm@7.32.0",
"keywords": [
"todo",
"productivity",
@ -45,47 +45,43 @@
"story:preview": "histoire preview"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "6.3.0",
"@fortawesome/free-regular-svg-icons": "6.3.0",
"@fortawesome/free-solid-svg-icons": "6.3.0",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-regular-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/vue-fontawesome": "3.0.3",
"@github/hotkey": "2.0.1",
"@infectoone/vue-ganttastic": "2.1.4",
"@intlify/unplugin-vue-i18n": "0.8.2",
"@intlify/unplugin-vue-i18n": "0.10.0",
"@kyvg/vue3-notification": "2.9.0",
"@sentry/tracing": "7.37.2",
"@sentry/vue": "7.37.2",
"@types/is-touch-device": "1.0.0",
"@types/lodash.clonedeep": "4.5.7",
"@types/sortablejs": "1.15.0",
"@vueuse/core": "9.12.0",
"axios": "1.3.3",
"blurhash": "2.0.4",
"@sentry/tracing": "7.46.0",
"@sentry/vue": "7.46.0",
"@vueuse/core": "9.13.0",
"axios": "1.3.5",
"blurhash": "2.0.5",
"bulma-css-variables": "0.9.33",
"camel-case": "4.1.2",
"codemirror": "5.65.11",
"codemirror": "5.65.12",
"date-fns": "2.29.3",
"dayjs": "1.11.7",
"dompurify": "3.0.0",
"dompurify": "3.0.1",
"easymde": "2.18.0",
"fast-deep-equal": "3.1.3",
"flatpickr": "4.6.13",
"flexsearch": "0.7.21",
"flexsearch": "0.7.31",
"floating-vue": "2.0.0-beta.20",
"focus-within": "3.0.2",
"highlight.js": "11.7.0",
"is-touch-device": "1.0.1",
"klona": "2.0.6",
"lodash.debounce": "4.0.8",
"marked": "4.2.12",
"pinia": "2.0.30",
"marked": "4.3.0",
"pinia": "2.0.34",
"register-service-worker": "1.7.2",
"snake-case": "3.0.4",
"sortablejs": "1.15.0",
"ufo": "1.0.1",
"ufo": "1.1.1",
"vue": "3.2.47",
"vue-advanced-cropper": "2.8.8",
"vue-flatpickr-component": "11.0.2",
"vue-flatpickr-component": "11.0.3",
"vue-i18n": "9.2.2",
"vue-router": "4.1.6",
"workbox-precaching": "6.5.4",
@ -93,54 +89,62 @@
},
"devDependencies": {
"@4tw/cypress-drag-drop": "2.2.3",
"@cypress/vite-dev-server": "5.0.2",
"@cypress/vue": "5.0.4",
"@cypress/vite-dev-server": "5.0.5",
"@cypress/vue": "5.0.5",
"@faker-js/faker": "7.6.0",
"@histoire/plugin-screenshot": "0.15.4",
"@histoire/plugin-vue": "0.15.4",
"@histoire/plugin-screenshot": "0.15.9",
"@histoire/plugin-vue": "0.15.8",
"@rushstack/eslint-patch": "1.2.0",
"@types/codemirror": "5.60.7",
"@types/dompurify": "2.4.0",
"@types/dompurify": "3.0.1",
"@types/flexsearch": "0.7.3",
"@types/focus-within": "1.0.1",
"@types/is-touch-device": "1.0.0",
"@types/lodash.debounce": "4.0.7",
"@types/marked": "4.0.8",
"@types/node": "18.13.0",
"@types/node": "18.15.11",
"@types/postcss-preset-env": "7.7.0",
"@typescript-eslint/eslint-plugin": "5.52.0",
"@typescript-eslint/parser": "5.52.0",
"@vitejs/plugin-legacy": "4.0.1",
"@vitejs/plugin-vue": "4.0.0",
"@types/sortablejs": "1.15.1",
"@typescript-eslint/eslint-plugin": "5.57.1",
"@typescript-eslint/parser": "5.57.1",
"@vitejs/plugin-legacy": "4.0.2",
"@vitejs/plugin-vue": "4.1.0",
"@vue/eslint-config-typescript": "11.0.2",
"@vue/test-utils": "2.2.10",
"@vue/test-utils": "2.3.2",
"@vue/tsconfig": "0.1.3",
"autoprefixer": "10.4.13",
"autoprefixer": "10.4.14",
"browserslist": "4.21.5",
"caniuse-lite": "1.0.30001451",
"csstype": "3.1.1",
"cypress": "12.6.0",
"esbuild": "0.17.8",
"eslint": "8.34.0",
"eslint-plugin-vue": "9.9.0",
"happy-dom": "8.2.6",
"histoire": "0.15.4",
"netlify-cli": "12.12.0",
"caniuse-lite": "1.0.30001477",
"css-has-pseudo": "5.0.2",
"csstype": "3.1.2",
"cypress": "12.9.0",
"esbuild": "0.17.16",
"eslint": "8.38.0",
"eslint-plugin-vue": "9.10.0",
"happy-dom": "9.1.9",
"histoire": "0.15.9",
"netlify-cli": "13.2.2",
"postcss": "8.4.21",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "3.0.1",
"postcss-preset-env": "8.0.1",
"rollup": "3.15.0",
"postcss-focus-within": "7.0.2",
"postcss-preset-env": "8.3.1",
"rollup": "3.20.2",
"rollup-plugin-visualizer": "5.9.0",
"sass": "1.58.1",
"start-server-and-test": "1.15.4",
"typescript": "4.9.5",
"vite": "4.1.1",
"vite-plugin-inject-preload": "1.3.0",
"vite-plugin-pwa": "0.14.4",
"sass": "1.61.0",
"start-server-and-test": "2.0.0",
"typescript": "5.0.4",
"vite": "4.2.1",
"vite-plugin-inject-preload": "1.3.1",
"vite-plugin-pwa": "0.14.7",
"vite-svg-loader": "4.0.0",
"vitest": "0.28.5",
"vue-tsc": "1.1.2",
"vitest": "0.30.0",
"vue-tsc": "1.2.0",
"wait-on": "7.0.1",
"workbox-cli": "6.5.4"
},
"pnpm": {
"patchedDependencies": {
"flexsearch@0.7.31": "patches/flexsearch@0.7.31.patch"
}
}
}

View File

@ -0,0 +1,16 @@
diff --git a/index.d.ts b/index.d.ts
deleted file mode 100644
index 9f39f41073864b83968bdaa242ac4e3c3149685a..0000000000000000000000000000000000000000
diff --git a/package.json b/package.json
index 8968f5bf8010ff194240591c8b83299f7328e79d..6d84b6f590a841b129ed8b3860cb786df5a185c0 100644
--- a/package.json
+++ b/package.json
@@ -22,8 +22,6 @@
},
"main": "dist/flexsearch.bundle.js",
"browser": "dist/flexsearch.bundle.js",
- "module": "dist/module/index.js",
- "types": "./index.d.ts",
"preferGlobal": false,
"repository": {
"type": "git",

File diff suppressed because it is too large Load Diff

Binary file not shown.

4
src/assets/checkbox.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 18 18" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
<path d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z" stroke-dasharray="60"></path>
<polyline points="1 9 7 14 15 4" stroke-dasharray="22" stroke-dashoffset="66"></polyline>
</svg>

After

Width:  |  Height:  |  Size: 420 B

View File

@ -0,0 +1,54 @@
<template>
<div class="base-checkbox" v-cy="'checkbox'">
<input
type="checkbox"
:id="checkboxId"
class="is-sr-only"
:checked="modelValue"
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
:disabled="disabled || undefined"
/>
<slot name="label" :checkboxId="checkboxId">
<label :for="checkboxId" class="base-checkbox__label">
<slot/>
</label>
</slot>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
import {createRandomID} from '@/helpers/randomId'
defineProps({
modelValue: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
})
const emit = defineEmits<{
(event: 'update:modelValue', value: boolean): void
}>()
const checkboxId = ref(`fancycheckbox_${createRandomID()}`)
</script>
<style lang="scss" scoped>
.base-checkbox__label {
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
display: inline-flex;
}
.base-checkbox:has(input:disabled) .base-checkbox__label {
cursor:not-allowed;
pointer-events: none;
}
</style>

View File

@ -32,7 +32,7 @@ import {computed, ref} from 'vue'
import {getInheritedBackgroundColor} from '@/helpers/getInheritedBackgroundColor'
const props = defineProps({
/** Wheather the Expandable is open or not */
/** Whether the Expandable is open or not */
open: {
type: Boolean,
default: false,

View File

@ -4,8 +4,11 @@ import { useNow } from '@vueuse/core'
import LogoFull from '@/assets/logo-full.svg?component'
import LogoFullPride from '@/assets/logo-full-pride.svg?component'
import {MILLISECONDS_A_HOUR} from '@/constants/date'
const now = useNow()
const now = useNow({
interval: MILLISECONDS_A_HOUR,
})
const Logo = computed(() => now.value.getMonth() === 5 ? LogoFullPride : LogoFull)
</script>

View File

@ -1,67 +1,50 @@
<template>
<header
:class="{'has-background': background, 'menu-active': menuActive}"
aria-label="main navigation"
class="navbar d-print-none"
>
<router-link :to="{name: 'home'}" class="logo-link">
<Logo width="164" height="48"/>
<header :class="{ 'has-background': background, 'menu-active': menuActive }" aria-label="main navigation"
class="navbar d-print-none">
<router-link :to="{ name: 'home' }" class="logo-link">
<Logo width="164" height="48" />
</router-link>
<MenuButton class="menu-button"/>
<MenuButton class="menu-button" />
<div
v-if="currentList.id"
class="list-title-wrapper"
>
<h1 class="list-title">{{ currentList.title === '' ? $t('misc.loading') : getListTitle(currentList) }}</h1>
<BaseButton :to="{name: 'list.info', params: {listId: currentList.id}}" class="list-title-button">
<icon icon="circle-info"/>
<div v-if="currentProject.id" class="project-title-wrapper">
<h1 class="project-title">{{ currentProject.title === '' ? $t('misc.loading') : getProjectTitle(currentProject) }}
</h1>
<BaseButton :to="{ name: 'project.info', params: { projectId: currentProject.id } }" class="project-title-button">
<icon icon="circle-info" />
</BaseButton>
<list-settings-dropdown
v-if="canWriteCurrentList && currentList.id !== -1"
class="list-title-dropdown"
:list="currentList"
>
<template #trigger="{toggleOpen}">
<BaseButton class="list-title-button" @click="toggleOpen">
<icon icon="ellipsis-h" class="icon"/>
<project-settings-dropdown v-if="canWriteCurrentProject && currentProject.id !== -1"
class="project-title-dropdown" :project="currentProject">
<template #trigger="{ toggleOpen }">
<BaseButton class="project-title-button" @click="toggleOpen">
<icon icon="ellipsis-h" class="icon" />
</BaseButton>
</template>
</list-settings-dropdown>
</project-settings-dropdown>
</div>
<div class="navbar-end">
<BaseButton
@click="openQuickActions"
class="trigger-button"
v-shortcut="'Control+k'"
:title="$t('keyboardShortcuts.quickSearch')"
>
<icon icon="search"/>
<BaseButton @click="openQuickActions" class="trigger-button" v-shortcut="'Control+k'"
:title="$t('keyboardShortcuts.quickSearch')">
<icon icon="search" />
</BaseButton>
<Notifications />
<dropdown>
<template #trigger="{toggleOpen, open}">
<BaseButton
class="username-dropdown-trigger"
@click="toggleOpen"
variant="secondary"
:shadow="false"
>
<img :src="authStore.avatarUrl" alt="" class="avatar" width="40" height="40"/>
<template #trigger="{ toggleOpen, open }">
<BaseButton class="username-dropdown-trigger" @click="toggleOpen" variant="secondary" :shadow="false">
<img :src="authStore.avatarUrl" alt="" class="avatar" width="40" height="40" />
<span class="username">{{ authStore.userDisplayName }}</span>
<span class="icon is-small" :style="{
transform: open ? 'rotate(180deg)' : 'rotate(0)',
}">
<icon icon="chevron-down"/>
<icon icon="chevron-down" />
</span>
</BaseButton>
</template>
<dropdown-item :to="{name: 'user.settings'}">
<dropdown-item :to="{ name: 'user.settings' }">
{{ $t('user.settings.title') }}
</dropdown-item>
<dropdown-item v-if="imprintUrl" :href="imprintUrl">
@ -73,7 +56,7 @@
<dropdown-item @click="baseStore.setKeyboardShortcutsActive(true)">
{{ $t('keyboardShortcuts.title') }}
</dropdown-item>
<dropdown-item :to="{name: 'about'}">
<dropdown-item :to="{ name: 'about' }">
{{ $t('about.title') }}
</dropdown-item>
<dropdown-item @click="authStore.logout()">
@ -85,11 +68,11 @@
</template>
<script setup lang="ts">
import {computed} from 'vue'
import { computed } from 'vue'
import {RIGHTS as Rights} from '@/constants/rights'
import { RIGHTS as Rights } from '@/constants/rights'
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
import ProjectSettingsDropdown from '@/components/project/project-settings-dropdown.vue'
import Dropdown from '@/components/misc/dropdown.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue'
import Notifications from '@/components/notifications/notifications.vue'
@ -97,16 +80,16 @@ import Logo from '@/components/home/Logo.vue'
import BaseButton from '@/components/base/BaseButton.vue'
import MenuButton from '@/components/home/MenuButton.vue'
import {getListTitle} from '@/helpers/getListTitle'
import { getProjectTitle } from '@/helpers/getProjectTitle'
import {useBaseStore} from '@/stores/base'
import {useConfigStore} from '@/stores/config'
import {useAuthStore} from '@/stores/auth'
import { useBaseStore } from '@/stores/base'
import { useConfigStore } from '@/stores/config'
import { useAuthStore } from '@/stores/auth'
const baseStore = useBaseStore()
const currentList = computed(() => baseStore.currentList)
const currentProject = computed(() => baseStore.currentProject)
const background = computed(() => baseStore.background)
const canWriteCurrentList = computed(() => baseStore.currentList.maxRight > Rights.READ)
const canWriteCurrentProject = computed(() => baseStore.currentProject.maxRight > Rights.READ)
const menuActive = computed(() => baseStore.menuActive)
const authStore = useAuthStore()
@ -166,7 +149,7 @@ $user-dropdown-width-mobile: 5rem;
.logo-link {
display: none;
@media screen and (min-width: $tablet) {
align-self: stretch;
display: flex;
@ -185,12 +168,12 @@ $user-dropdown-width-mobile: 5rem;
}
}
.list-title-wrapper {
.project-title-wrapper {
margin-inline: auto;
display: flex;
align-items: center;
// this makes the truncated text of the list title work
// this makes the truncated text of the project title work
// inside the flexbox parent
min-width: 0;
@ -199,7 +182,7 @@ $user-dropdown-width-mobile: 5rem;
}
}
.list-title {
.project-title {
font-size: 1rem;
// We need the following for overflowing ellipsis to work
text-overflow: ellipsis;
@ -211,15 +194,15 @@ $user-dropdown-width-mobile: 5rem;
}
}
.list-title-dropdown {
.project-title-dropdown {
align-self: stretch;
.list-title-button {
.project-title-button {
flex-grow: 1;
}
}
.list-title-button {
.project-title-button {
align-self: stretch;
min-width: var(--navbar-button-min-width);
display: flex;
@ -235,7 +218,7 @@ $user-dropdown-width-mobile: 5rem;
display: flex;
align-items: stretch;
> * {
>* {
min-width: var(--navbar-button-min-width);
}
}

View File

@ -33,7 +33,7 @@
<quick-actions/>
<router-view :route="routeWithModal" v-slot="{ Component }">
<keep-alive :include="['list.list', 'list.gantt', 'list.table', 'list.kanban']">
<keep-alive :include="['project.list', 'project.gantt', 'project.table', 'project.kanban']">
<component :is="Component"/>
</keep-alive>
</router-view>
@ -87,7 +87,7 @@ function showKeyboardShortcuts() {
const route = useRoute()
// FIXME: this is really error prone
// Reset the current list highlight in menu if the current route is not list related.
// Reset the current project highlight in menu if the current route is not project related.
watch(() => route.name as string, (routeName) => {
if (
routeName &&
@ -106,7 +106,7 @@ watch(() => route.name as string, (routeName) => {
routeName.startsWith('user.settings')
)
) {
baseStore.handleSetCurrentList({list: null})
baseStore.handleSetCurrentProject({project: null})
}
})
@ -156,6 +156,8 @@ labelStore.loadAllLabels()
z-index: 10;
position: relative;
padding: 1.5rem 0.5rem 1rem;
// TODO refactor: DRY `transition-timing-function` with `./navigation.vue`.
transition: margin-left $transition-duration;
@media screen and (max-width: $tablet) {
margin-left: 0;

View File

@ -9,9 +9,9 @@
<Logo class="logo" v-if="logoVisible"/>
<h1
:class="{'m-0': !logoVisible}"
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
:style="{ 'opacity': currentProject.title === '' ? '0': '1' }"
class="title">
{{ currentList.title === '' ? $t('misc.loading') : currentList.title }}
{{ currentProject.title === '' ? $t('misc.loading') : currentProject.title }}
</h1>
<div class="box has-text-left view">
<router-view/>
@ -31,7 +31,7 @@ import Logo from '@/components/home/Logo.vue'
import PoweredByLink from './PoweredByLink.vue'
const baseStore = useBaseStore()
const currentList = computed(() => baseStore.currentList)
const currentProject = computed(() => baseStore.currentProject)
const background = computed(() => baseStore.background)
const logoVisible = computed(() => baseStore.logoVisible)
</script>

View File

@ -52,37 +52,37 @@
<template v-for="(n, nk) in namespaces" :key="n.id">
<div class="namespace-title" :class="{'has-menu': n.id > 0}">
<BaseButton
@click="toggleLists(n.id)"
@click="toggleProjects(n.id)"
class="menu-label"
v-tooltip="namespaceTitles[nk]"
>
<ColorBubble
v-if="n.hexColor !== ''"
:color="n.hexColor"
class="mr-1"
v-if="n.hexColor !== ''"
:color="n.hexColor"
class="mr-1"
/>
<span class="name">{{ namespaceTitles[nk] }}</span>
<div
class="icon menu-item-icon is-small toggle-lists-icon pl-2"
:class="{'active': typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true}"
class="icon menu-item-icon is-small toggle-lists-icon pl-2"
:class="{'active': typeof projectsVisible[n.id] !== 'undefined' ? projectsVisible[n.id] : true}"
>
<icon icon="chevron-down"/>
</div>
<span class="count" :class="{'ml-2 mr-0': n.id > 0}">
({{ namespaceListsCount[nk] }})
({{ namespaceProjectsCount[nk] }})
</span>
</BaseButton>
<namespace-settings-dropdown class="menu-list-dropdown" :namespace="n" v-if="n.id > 0"/>
</div>
<!--
NOTE: a v-model / computed setter is not possible, since the updateActiveLists function
triggered by the change needs to have access to the current namespace
-->
<draggable
v-if="listsVisible[n.id] ?? true"
<!--
NOTE: a v-model / computed setter is not possible, since the updateActiveProjects function
triggered by the change needs to have access to the current namespace
-->
<draggable
v-if="projectsVisible[n.id] ?? true"
v-bind="dragOptions"
:modelValue="activeLists[nk]"
@update:modelValue="(lists) => updateActiveLists(n, lists)"
:modelValue="activeProjects[nk]"
@update:modelValue="(projects) => updateActiveProjects(n, projects)"
group="namespace-lists"
@start="() => drag = true"
@end="saveListPosition"
@ -100,45 +100,46 @@
{ 'dragging-disabled': n.id < 0 }
]
}"
>
<template #item="{element: l}">
<li
class="list-menu loader-container is-loading-small"
:class="{'is-loading': listUpdating[l.id]}"
>
<template #item="{element: l}">
<li
class="list-menu loader-container is-loading-small"
:class="{'is-loading': projectUpdating[l.id]}"
>
<BaseButton
:to="{ name: 'project.index', params: { projectId: l.id} }"
class="list-menu-link"
:class="{'router-link-exact-active': currentProject.id === l.id}"
>
<BaseButton
:to="{ name: 'list.index', params: { listId: l.id} }"
class="list-menu-link"
:class="{'router-link-exact-active': currentList.id === l.id}"
>
<span class="icon menu-item-icon handle">
<icon icon="grip-lines"/>
</span>
<ColorBubble
<span class="icon menu-item-icon handle">
<icon icon="grip-lines"/>
</span>
<ColorBubble
v-if="l.hexColor !== ''"
:color="l.hexColor"
class="mr-1"
/>
<span class="list-menu-title">{{ getListTitle(l) }}</span>
</BaseButton>
<BaseButton
class="favorite"
:class="{'is-favorite': l.isFavorite}"
@click="listStore.toggleListFavorite(l)"
>
<icon :icon="l.isFavorite ? 'star' : ['far', 'star']"/>
</BaseButton>
<list-settings-dropdown class="menu-list-dropdown" :list="l" v-if="l.id > 0">
<template #trigger="{toggleOpen}">
<BaseButton class="menu-list-dropdown-trigger" @click="toggleOpen">
<icon icon="ellipsis-h" class="icon"/>
</BaseButton>
</template>
</list-settings-dropdown>
<span class="list-setting-spacer" v-else></span>
</li>
</template>
</draggable>
/>
<span class="list-menu-title">{{ getProjectTitle(l) }}</span>
</BaseButton>
<BaseButton
v-if="l.id > 0"
class="favorite"
:class="{'is-favorite': l.isFavorite}"
@click="projectStore.toggleProjectFavorite(l)"
>
<icon :icon="l.isFavorite ? 'star' : ['far', 'star']"/>
</BaseButton>
<ProjectSettingsDropdown class="menu-list-dropdown" :project="l" v-if="l.id > 0">
<template #trigger="{toggleOpen}">
<BaseButton class="menu-list-dropdown-trigger" @click="toggleOpen">
<icon icon="ellipsis-h" class="icon"/>
</BaseButton>
</template>
</ProjectSettingsDropdown>
<span class="list-setting-spacer" v-else></span>
</li>
</template>
</draggable>
</template>
</nav>
<PoweredByLink/>
@ -151,20 +152,20 @@ import draggable from 'zhyswan-vuedraggable'
import type {SortableEvent} from 'sortablejs'
import BaseButton from '@/components/base/BaseButton.vue'
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
import ProjectSettingsDropdown from '@/components/project/project-settings-dropdown.vue'
import NamespaceSettingsDropdown from '@/components/namespace/namespace-settings-dropdown.vue'
import PoweredByLink from '@/components/home/PoweredByLink.vue'
import Logo from '@/components/home/Logo.vue'
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
import {getListTitle} from '@/helpers/getListTitle'
import type {IList} from '@/modelTypes/IList'
import {getProjectTitle} from '@/helpers/getProjectTitle'
import type {IProject} from '@/modelTypes/IProject'
import type {INamespace} from '@/modelTypes/INamespace'
import ColorBubble from '@/components/misc/colorBubble.vue'
import {useBaseStore} from '@/stores/base'
import {useListStore} from '@/stores/lists'
import {useProjectStore} from '@/stores/projects'
import {useNamespaceStore} from '@/stores/namespaces'
const drag = ref(false)
@ -175,7 +176,7 @@ const dragOptions = {
const baseStore = useBaseStore()
const namespaceStore = useNamespaceStore()
const currentList = computed(() => baseStore.currentList)
const currentProject = computed(() => baseStore.currentProject)
const menuActive = computed(() => baseStore.menuActive)
const loading = computed(() => namespaceStore.isLoading)
@ -183,9 +184,9 @@ const loading = computed(() => namespaceStore.isLoading)
const namespaces = computed(() => {
return namespaceStore.namespaces.filter(n => !n.isArchived)
})
const activeLists = computed(() => {
return namespaces.value.map(({lists}) => {
return lists?.filter(item => {
const activeProjects = computed(() => {
return namespaces.value.map(({projects}) => {
return projects?.filter(item => {
return typeof item !== 'undefined' && !item.isArchived
})
})
@ -195,45 +196,45 @@ const namespaceTitles = computed(() => {
return namespaces.value.map((namespace) => getNamespaceTitle(namespace))
})
const namespaceListsCount = computed(() => {
return namespaces.value.map((_, index) => activeLists.value[index]?.length ?? 0)
const namespaceProjectsCount = computed(() => {
return namespaces.value.map((_, index) => activeProjects.value[index]?.length ?? 0)
})
const listStore = useListStore()
const projectStore = useProjectStore()
function toggleLists(namespaceId: INamespace['id']) {
listsVisible.value[namespaceId] = !listsVisible.value[namespaceId]
function toggleProjects(namespaceId: INamespace['id']) {
projectsVisible.value[namespaceId] = !projectsVisible.value[namespaceId]
}
const listsVisible = ref<{ [id: INamespace['id']]: boolean }>({})
const projectsVisible = ref<{ [id: INamespace['id']]: boolean }>({})
// FIXME: async action will be unfinished when component mounts
onBeforeMount(async () => {
const namespaces = await namespaceStore.loadNamespaces()
namespaces.forEach(n => {
if (typeof listsVisible.value[n.id] === 'undefined') {
listsVisible.value[n.id] = true
if (typeof projectsVisible.value[n.id] === 'undefined') {
projectsVisible.value[n.id] = true
}
})
})
function updateActiveLists(namespace: INamespace, activeLists: IList[]) {
function updateActiveProjects(namespace: INamespace, activeProjects: IProject[]) {
// This is a bit hacky: since we do have to filter out the archived items from the list
// for vue draggable updating it is not as simple as replacing it.
// To work around this, we merge the active lists with the archived ones. Doing so breaks the order
// because now all archived lists are sorted after the active ones. This is fine because they are sorted
// To work around this, we merge the active projects with the archived ones. Doing so breaks the order
// because now all archived projects are sorted after the active ones. This is fine because they are sorted
// later when showing them anyway, and it makes the merging happening here a lot easier.
const lists = [
...activeLists,
...namespace.lists.filter(l => l.isArchived),
const projects = [
...activeProjects,
...namespace.projects.filter(l => l.isArchived),
]
namespaceStore.setNamespaceById({
...namespace,
lists,
projects,
})
}
const listUpdating = ref<{ [id: INamespace['id']]: boolean }>({})
const projectUpdating = ref<{ [id: INamespace['id']]: boolean }>({})
async function saveListPosition(e: SortableEvent) {
if (!e.newIndex && e.newIndex !== 0) return
@ -241,31 +242,31 @@ async function saveListPosition(e: SortableEvent) {
const namespaceId = parseInt(e.to.dataset.namespaceId as string)
const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex as string)
const listsActive = activeLists.value[newNamespaceIndex]
// If the list was dragged to the last position, Safari will report e.newIndex as the size of the listsActive
// array instead of using the position. Because the index is wrong in that case, dragging the list will fail.
const projectsActive = activeProjects.value[newNamespaceIndex]
// If the project was dragged to the last position, Safari will report e.newIndex as the size of the projectsActive
// array instead of using the position. Because the index is wrong in that case, dragging the project will fail.
// To work around that we're explicitly checking that case here and decrease the index.
const newIndex = e.newIndex === listsActive.length ? e.newIndex - 1 : e.newIndex
const list = listsActive[newIndex]
const listBefore = listsActive[newIndex - 1] ?? null
const listAfter = listsActive[newIndex + 1] ?? null
listUpdating.value[list.id] = true
const newIndex = e.newIndex === projectsActive.length ? e.newIndex - 1 : e.newIndex
const project = projectsActive[newIndex]
const projectBefore = projectsActive[newIndex - 1] ?? null
const projectAfter = projectsActive[newIndex + 1] ?? null
projectUpdating.value[project.id] = true
const position = calculateItemPosition(
listBefore !== null ? listBefore.position : null,
listAfter !== null ? listAfter.position : null,
projectBefore !== null ? projectBefore.position : null,
projectAfter !== null ? projectAfter.position : null,
)
try {
// create a copy of the list in order to not violate pinia manipulation
await listStore.updateList({
...list,
// create a copy of the project in order to not violate pinia manipulation
await projectStore.updateProject({
...project,
position,
namespaceId,
})
} finally {
listUpdating.value[list.id] = false
projectUpdating.value[project.id] = false
}
}
</script>
@ -341,13 +342,20 @@ $vikunja-nav-selected-width: 0.4rem;
}
.menu-list-dropdown {
opacity: 0;
opacity: 1;
transition: $transition;
}
&:hover .menu-list-dropdown {
opacity: 1;
@media(hover: hover) and (pointer: fine) {
.menu-list-dropdown {
opacity: 0;
}
&:hover .menu-list-dropdown {
opacity: 1;
}
}
}
.menu-item-icon {
@ -411,18 +419,21 @@ $vikunja-nav-selected-width: 0.4rem;
opacity: 0;
transition: opacity $transition;
margin-right: .25rem;
cursor: grab;
}
&:hover .handle {
opacity: 1;
}
}
&:not(.dragging-disabled) .handle {
cursor: grab;
}
}
}
.top-menu {
margin-top: math.div($navbar-padding, 2);
.menu-list {
li {
font-weight: 600;
@ -477,17 +488,24 @@ $vikunja-nav-selected-width: 0.4rem;
.favorite {
margin-left: .25rem;
transition: opacity $transition, color $transition;
opacity: 0;
opacity: 1;
&:hover,
&.is-favorite {
color: var(--warning);
opacity: 1;
}
}
.favorite.is-favorite,
.list-menu:hover .favorite {
opacity: 1;
@media(hover: hover) and (pointer: fine) {
.list-menu .favorite {
opacity: 0;
}
.list-menu:hover .favorite,
.favorite.is-favorite {
opacity: 1;
}
}
.list-menu-title {

View File

@ -1,63 +0,0 @@
<template>
<multiselect
v-model="selectedLists"
:search-results="foundLists"
:loading="listService.loading"
:multiple="true"
:placeholder="$t('list.search')"
label="title"
@search="findLists"
/>
</template>
<script setup lang="ts">
import {computed, ref, shallowReactive, watchEffect, type PropType} from 'vue'
import Multiselect from '@/components/input/multiselect.vue'
import type {IList} from '@/modelTypes/IList'
import ListService from '@/services/list'
import {includesById} from '@/helpers/utils'
const props = defineProps({
modelValue: {
type: Array as PropType<IList[]>,
default: () => [],
},
})
const emit = defineEmits<{
(e: 'update:modelValue', value: IList[]): void
}>()
const lists = ref<IList[]>([])
watchEffect(() => {
lists.value = props.modelValue
})
const selectedLists = computed({
get() {
return lists.value
},
set: (value) => {
lists.value = value
emit('update:modelValue', value)
},
})
const listService = shallowReactive(new ListService())
const foundLists = ref<IList[]>([])
async function findLists(query: string) {
if (query === '') {
foundLists.value = []
return
}
const response = await listService.getAll({}, {s: query}) as IList[]
// Filter selected items from the results
foundLists.value = response.filter(({id}) => !includesById(lists.value, id))
}
</script>

View File

@ -0,0 +1,63 @@
<template>
<multiselect
v-model="selectedProjects"
:search-results="foundProjects"
:loading="projectService.loading"
:multiple="true"
:placeholder="$t('project.search')"
label="title"
@search="findProjects"
/>
</template>
<script setup lang="ts">
import {computed, ref, shallowReactive, watchEffect, type PropType} from 'vue'
import Multiselect from '@/components/input/multiselect.vue'
import type {IProject} from '@/modelTypes/IProject'
import ProjectService from '@/services/project'
import {includesById} from '@/helpers/utils'
const props = defineProps({
modelValue: {
type: Array as PropType<IProject[]>,
default: () => [],
},
})
const emit = defineEmits<{
(e: 'update:modelValue', value: IProject[]): void
}>()
const projects = ref<IProject[]>([])
watchEffect(() => {
projects.value = props.modelValue
})
const selectedProjects = computed({
get() {
return projects.value
},
set: (value) => {
projects.value = value
emit('update:modelValue', value)
},
})
const projectService = shallowReactive(new ProjectService())
const foundProjects = ref<IProject[]>([])
async function findProjects(query: string) {
if (query === '') {
foundProjects.value = []
return
}
const response = await projectService.getAll({}, {s: query}) as IProject[]
// Filter selected items from the results
foundProjects.value = response.filter(({id}) => !includesById(projects.value, id))
}
</script>

View File

@ -286,11 +286,11 @@ function handleCheckboxClick(e: Event) {
console.debug('no index found')
return
}
const listPrefix = text.value.substring(index, index + 1)
const projectPrefix = text.value.substring(index, index + 1)
console.debug({index, listPrefix, checked, text: text.value})
console.debug({index, projectPrefix, checked, text: text.value})
text.value = replaceAt(text.value, index, `${listPrefix} ${checked ? '[x]' : '[ ]'} `)
text.value = replaceAt(text.value, index, `${projectPrefix} ${checked ? '[x]' : '[ ]'} `)
bubble()
renderPreview()
}

View File

@ -0,0 +1,71 @@
<script lang="ts" setup>
import {ref} from 'vue'
import {logEvent} from 'histoire/client'
import FancyCheckbox from './fancycheckbox.vue'
const isDisabled = ref<boolean | undefined>()
const isChecked = ref(false)
const isCheckedInitiallyEnabled = ref(true)
const isCheckedDisabled = ref(false)
const withoutInitialState = ref<boolean | undefined>()
</script>
<template>
<Story :layout="{ type: 'grid', width: '200px' }">
<Variant title="Default">
<FancyCheckbox
v-model="isChecked"
:disabled="isDisabled"
>
This is probably not important
</FancyCheckbox>
Visualisation
<input type="checkbox" v-model="isChecked">
{{ isChecked }}
</Variant>
<Variant title="Enabled Initially">
<FancyCheckbox
:disabled="isDisabled"
v-model="isCheckedInitiallyEnabled"
>
We want you to use this option
</FancyCheckbox>
Visualisation
<input type="checkbox" v-model="isCheckedInitiallyEnabled">
{{ isCheckedInitiallyEnabled }}
</Variant>
<Variant title="Disabled">
<FancyCheckbox
disabled
:modelValue="isCheckedDisabled"
@update:model-value="logEvent('Setting disabled: This should never happen', $event)"
>
You can't change this
</FancyCheckbox>
Visualisation
<input type="checkbox" v-model="isCheckedDisabled" disabled>
{{ isCheckedDisabled }}
</Variant>
<Variant title="Undefined initial State">
<FancyCheckbox
v-model="withoutInitialState"
:disabled="isDisabled"
>
Not sure what the value should be
</FancyCheckbox>
Visualisation
<input type="checkbox" v-model="withoutInitialState" disabled>
{{ withoutInitialState }}
</Variant>
</Story>
</template>

View File

@ -1,61 +1,42 @@
<template>
<div :class="{'is-disabled': disabled}" class="fancycheckbox">
<input
:checked="checked"
:disabled="disabled || undefined"
:id="checkBoxId"
@change="(event: Event) => updateData((event.target as HTMLInputElement).checked)"
type="checkbox"
/>
<label :for="checkBoxId" class="check">
<svg height="18px" viewBox="0 0 18 18" width="18px">
<path
d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z"></path>
<polyline points="1 9 7 14 15 4"></polyline>
</svg>
<span>
<slot></slot>
</span>
</label>
</div>
<BaseCheckbox
class="fancycheckbox"
:class="{
'is-disabled': disabled,
'is-block': isBlock,
}"
:disabled="disabled"
:model-value="modelValue"
@update:model-value="value => emit('update:modelValue', value)"
>
<CheckboxIcon class="fancycheckbox__icon" />
<span v-if="$slots.default" class="fancycheckbox__content">
<slot/>
</span>
</BaseCheckbox>
</template>
<script setup lang="ts">
import {ref, toRef, watch} from 'vue'
import CheckboxIcon from '@/assets/checkbox.svg?component'
import {createRandomID} from '@/helpers/randomId'
import BaseCheckbox from '@/components/base/BaseCheckbox.vue'
const checked = ref(false)
const checkBoxId = `fancycheckbox_${createRandomID()}`
const props = defineProps({
defineProps({
modelValue: {
type: Boolean,
required: false,
},
disabled: {
type: Boolean,
required: false,
},
isBlock: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update:modelValue', 'change'])
const modelValue = toRef(props, 'modelValue')
watch(
modelValue,
newValue => {
checked.value = newValue
},
{immediate: true},
)
function updateData(newChecked: boolean) {
checked.value = newChecked
emit('update:modelValue', newChecked)
emit('change', newChecked)
}
const emit = defineEmits<{
(event: 'update:modelValue', value: boolean): void
}>()
</script>
@ -65,75 +46,54 @@ function updateData(newChecked: boolean) {
padding-right: 5px;
padding-top: 3px;
// FIXME: should be a prop
&.is-block {
display: block;
margin: .5rem .2rem;
}
}
input[type=checkbox] {
display: none;
}
.check {
cursor: pointer;
position: relative;
margin: auto;
width: 18px;
height: 18px;
-webkit-tap-highlight-color: transparent;
transform: translate3d(0, 0, 0);
}
span {
.fancycheckbox__content {
font-size: 0.8rem;
vertical-align: top;
padding-left: .5rem;
}
svg {
.fancycheckbox__icon:deep() {
position: relative;
z-index: 1;
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
stroke: #c8ccd4;
stroke-width: 1.5;
stroke: var(--stroke-color, #c8ccd4);
transform: translate3d(0, 0, 0);
transition: all 0.2s ease;
}
.check:hover svg {
stroke: var(--primary);
}
.is-disabled .check:hover svg {
stroke: #c8ccd4;
}
path {
stroke-dasharray: 60;
stroke-dashoffset: 0;
}
polyline {
stroke-dasharray: 22;
stroke-dashoffset: 66;
}
input[type=checkbox]:checked + .check {
svg {
stroke: var(--primary);
path,
polyline {
transition: all 0.2s linear, color 0.2s ease;
}
}
.fancycheckbox:not(:has(input:disabled)):hover .fancycheckbox__icon,
.fancycheckbox:has(input:checked) .fancycheckbox__icon {
--stroke-color: var(--primary);
}
</style>
<style lang="scss">
// Since css-has-pseudo doesn't work with deep classes,
// the following rules can't be scoped
.fancycheckbox:has(:not(input:checked)) .fancycheckbox__icon {
path {
transition-delay: 0.05s;
}
}
.fancycheckbox:has(input:checked) .fancycheckbox__icon {
path {
stroke-dashoffset: 60;
transition: all 0.3s linear;
}
polyline {
stroke-dashoffset: 42;
transition: all 0.2s linear;
transition-delay: 0.15s;
}
}

View File

@ -1,200 +0,0 @@
<template>
<div
:class="{ 'is-loading': listService.loading, 'is-archived': currentList.isArchived}"
class="loader-container"
>
<div class="switch-view-container">
<div class="switch-view">
<BaseButton
v-shortcut="'g l'"
:title="$t('keyboardShortcuts.list.switchToListView')"
class="switch-view-button"
:class="{'is-active': viewName === 'list'}"
:to="{ name: 'list.list', params: { listId } }"
>
{{ $t('list.list.title') }}
</BaseButton>
<BaseButton
v-shortcut="'g g'"
:title="$t('keyboardShortcuts.list.switchToGanttView')"
class="switch-view-button"
:class="{'is-active': viewName === 'gantt'}"
:to="{ name: 'list.gantt', params: { listId } }"
>
{{ $t('list.gantt.title') }}
</BaseButton>
<BaseButton
v-shortcut="'g t'"
:title="$t('keyboardShortcuts.list.switchToTableView')"
class="switch-view-button"
:class="{'is-active': viewName === 'table'}"
:to="{ name: 'list.table', params: { listId } }"
>
{{ $t('list.table.title') }}
</BaseButton>
<BaseButton
v-shortcut="'g k'"
:title="$t('keyboardShortcuts.list.switchToKanbanView')"
class="switch-view-button"
:class="{'is-active': viewName === 'kanban'}"
:to="{ name: 'list.kanban', params: { listId } }"
>
{{ $t('list.kanban.title') }}
</BaseButton>
</div>
<slot name="header" />
</div>
<CustomTransition name="fade">
<Message variant="warning" v-if="currentList.isArchived" class="mb-4">
{{ $t('list.archived') }}
</Message>
</CustomTransition>
<slot v-if="loadedListId"/>
</div>
</template>
<script setup lang="ts">
import {ref, computed, watch} from 'vue'
import {useRoute} from 'vue-router'
import BaseButton from '@/components/base/BaseButton.vue'
import Message from '@/components/misc/message.vue'
import CustomTransition from '@/components/misc/CustomTransition.vue'
import ListModel from '@/models/list'
import ListService from '@/services/list'
import {getListTitle} from '@/helpers/getListTitle'
import {saveListToHistory} from '@/modules/listHistory'
import {useTitle} from '@/composables/useTitle'
import {useBaseStore} from '@/stores/base'
import {useListStore} from '@/stores/lists'
const props = defineProps({
listId: {
type: Number,
required: true,
},
viewName: {
type: String,
required: true,
},
})
const route = useRoute()
const baseStore = useBaseStore()
const listStore = useListStore()
const listService = ref(new ListService())
const loadedListId = ref(0)
const currentList = computed(() => {
return typeof baseStore.currentList === 'undefined' ? {
id: 0,
title: '',
isArchived: false,
maxRight: null,
} : baseStore.currentList
})
useTitle(() => currentList.value.id ? getListTitle(currentList.value) : '')
// 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 list multiple times, even when navigating away from it.
// This caused wired bugs where the list background would be set on the home page but only right after setting a new
// list background and then navigating to home. It also highlighted the list in the menu and didn't allow changing any
// of it, most likely due to the rights not being properly populated.
watch(
() => props.listId,
// loadList
async (listIdToLoad: number) => {
const listData = {id: listIdToLoad}
saveListToHistory(listData)
// Don't load the list if we either already loaded it or aren't dealing with a list at all currently and
// the currently loaded list has the right set.
if (
(
listIdToLoad === loadedListId.value ||
typeof listIdToLoad === 'undefined' ||
listIdToLoad === currentList.value.id
)
&& typeof currentList.value !== 'undefined' && currentList.value.maxRight !== null
) {
loadedListId.value = props.listId
return
}
console.debug(`Loading list, props.viewName = ${props.viewName}, $route.params =`, route.params, `, loadedListId = ${loadedListId.value}, currentList = `, currentList.value)
// Set the current list to the one we're about to load so that the title is already shown at the top
loadedListId.value = 0
const listFromStore = listStore.getListById(listData.id)
if (listFromStore !== null) {
baseStore.setBackground(null)
baseStore.setBlurHash(null)
baseStore.handleSetCurrentList({list: listFromStore})
}
// We create an extra list object instead of creating it in list.value because that would trigger a ui update which would result in bad ux.
const list = new ListModel(listData)
try {
const loadedList = await listService.value.get(list)
baseStore.handleSetCurrentList({list: loadedList})
} finally {
loadedListId.value = props.listId
}
},
{immediate: true},
)
</script>
<style lang="scss" scoped>
.switch-view-container {
@media screen and (max-width: $tablet) {
display: flex;
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;
}
.switch-view-button {
padding: .25rem .5rem;
display: block;
border-radius: $radius;
transition: all 100ms;
&:not(:last-child) {
margin-right: .5rem;
}
&:hover {
color: var(--switch-view-color);
background: var(--primary);
}
&.is-active {
color: var(--switch-view-color);
background: var(--primary);
font-weight: bold;
box-shadow: var(--shadow-xs);
}
}
// FIXME: this should be in notification and set via a prop
.is-archived .notification.is-warning {
margin-bottom: 1rem;
}
</style>

View File

@ -1,77 +0,0 @@
<template>
<ul class="list-grid">
<li
v-for="(item, index) in filteredLists"
:key="`list_${item.id}_${index}`"
class="list-grid-item"
>
<ListCard :list="item" />
</li>
</ul>
</template>
<script lang="ts" setup>
import {computed, type PropType} from 'vue'
import type {IList} from '@/modelTypes/IList'
import ListCard from './ListCard.vue'
const props = defineProps({
lists: {
type: Array as PropType<IList[]>,
default: () => [],
},
showArchived: {
default: false,
type: Boolean,
},
itemLimit: {
type: Boolean,
default: false,
},
})
const filteredLists = computed(() => {
return props.showArchived
? props.lists
: props.lists.filter(l => !l.isArchived)
})
</script>
<style lang="scss" scoped>
$list-height: 150px;
$list-spacing: 1rem;
.list-grid {
margin: 0; // reset li
list-style-type: none;
display: grid;
grid-template-columns: repeat(var(--list-columns), 1fr);
grid-auto-rows: $list-height;
gap: $list-spacing;
@media screen and (min-width: $mobile) {
--list-rows: 4;
--list-columns: 1;
}
@media screen and (min-width: $mobile) and (max-width: $tablet) {
--list-columns: 2;
}
@media screen and (min-width: $tablet) and (max-width: $widescreen) {
--list-columns: 3;
--list-rows: 3;
}
@media screen and (min-width: $widescreen) {
--list-columns: 5;
--list-rows: 2;
}
}
.list-grid-item {
display: grid;
margin-top: 0; // remove padding coming form .content li + li
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<modal @close="close()">
<card class="has-background-white has-no-shadow keyboard-shortcuts" :title="$t('keyboardShortcuts.title')">
<card class="has-background-white keyboard-shortcuts" :shadow="false" :title="$t('keyboardShortcuts.title')">
<template v-for="(s, i) in shortcuts" :key="i">
<h3>{{ $t(s.title) }}</h3>

View File

@ -61,8 +61,8 @@ export const KEYBOARD_SHORTCUTS : ShortcutGroup[] = [
],
},
{
title: 'list.kanban.title',
available: (route) => route.name === 'list.kanban',
title: 'project.kanban.title',
available: (route) => route.name === 'project.kanban',
shortcuts: [
{
title: 'keyboardShortcuts.task.done',
@ -71,26 +71,26 @@ export const KEYBOARD_SHORTCUTS : ShortcutGroup[] = [
],
},
{
title: 'keyboardShortcuts.list.title',
available: (route) => (route.name as string)?.startsWith('list.'),
title: 'keyboardShortcuts.project.title',
available: (route) => (route.name as string)?.startsWith('project.'),
shortcuts: [
{
title: 'keyboardShortcuts.list.switchToListView',
title: 'keyboardShortcuts.project.switchToListView',
keys: ['g', 'l'],
combination: 'then',
},
{
title: 'keyboardShortcuts.list.switchToGanttView',
title: 'keyboardShortcuts.project.switchToGanttView',
keys: ['g', 'g'],
combination: 'then',
},
{
title: 'keyboardShortcuts.list.switchToTableView',
title: 'keyboardShortcuts.project.switchToTableView',
keys: ['g', 't'],
combination: 'then',
},
{
title: 'keyboardShortcuts.list.switchToKanbanView',
title: 'keyboardShortcuts.project.switchToKanbanView',
keys: ['g', 'k'],
combination: 'then',
},

View File

@ -73,14 +73,14 @@ const {t} = useI18n({useScope: 'global'})
const tooltipText = computed(() => {
if (disabled.value) {
if (props.entity === 'list' && subscriptionEntity.value === 'namespace') {
return t('task.subscription.subscribedListThroughParentNamespace')
if (props.entity === 'project' && subscriptionEntity.value === 'namespace') {
return t('task.subscription.subscribedProjectThroughParentNamespace')
}
if (props.entity === 'task' && subscriptionEntity.value === 'namespace') {
return t('task.subscription.subscribedTaskThroughParentNamespace')
}
if (props.entity === 'task' && subscriptionEntity.value === 'list') {
return t('task.subscription.subscribedTaskThroughParentList')
if (props.entity === 'task' && subscriptionEntity.value === 'project') {
return t('task.subscription.subscribedTaskThroughParentProject')
}
return ''
@ -91,10 +91,10 @@ const tooltipText = computed(() => {
return props.modelValue !== null ?
t('task.subscription.subscribedNamespace') :
t('task.subscription.notSubscribedNamespace')
case 'list':
case 'project':
return props.modelValue !== null ?
t('task.subscription.subscribedList') :
t('task.subscription.notSubscribedList')
t('task.subscription.subscribedProject') :
t('task.subscription.notSubscribedProject')
case 'task':
return props.modelValue !== null ?
t('task.subscription.subscribedTask') :
@ -133,8 +133,8 @@ async function subscribe() {
case 'namespace':
message = t('task.subscription.subscribeSuccessNamespace')
break
case 'list':
message = t('task.subscription.subscribeSuccessList')
case 'project':
message = t('task.subscription.subscribeSuccessProject')
break
case 'task':
message = t('task.subscription.subscribeSuccessTask')
@ -156,8 +156,8 @@ async function unsubscribe() {
case 'namespace':
message = t('task.subscription.unsubscribeSuccessNamespace')
break
case 'list':
message = t('task.subscription.unsubscribeSuccessList')
case 'project':
message = t('task.subscription.unsubscribeSuccessProject')
break
case 'task':
message = t('task.subscription.unsubscribeSuccessTask')

View File

@ -30,10 +30,10 @@
{{ $t('menu.share') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'list.create', params: { namespaceId: namespace.id } }"
:to="{ name: 'project.create', params: { namespaceId: namespace.id } }"
icon="plus"
>
{{ $t('menu.newList') }}
{{ $t('menu.newProject') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id } }"

View File

@ -117,9 +117,9 @@ function to(n, index) {
case names.TASK_DELETED:
// Nothing
break
case names.LIST_CREATED:
case names.PROJECT_CREATED:
to.name = 'task.index'
to.params.listId = n.notification.list.id
to.params.projectId = n.notification.project.id
break
case names.TEAM_MEMBER_ADDED:
to.name = 'teams.edit'
@ -145,12 +145,13 @@ function to(n, index) {
.trigger-button {
width: 100%;
position: relative;
}
.unread-indicator {
position: absolute;
top: .75rem;
right: 1.15rem;
top: 1rem;
right: .5rem;
width: .75rem;
height: .75rem;

View File

@ -0,0 +1,200 @@
<template>
<div
:class="{ 'is-loading': projectService.loading, 'is-archived': currentProject.isArchived}"
class="loader-container"
>
<div class="switch-view-container">
<div class="switch-view">
<BaseButton
v-shortcut="'g l'"
:title="$t('keyboardShortcuts.project.switchToListView')"
class="switch-view-button"
:class="{'is-active': viewName === 'project'}"
:to="{ name: 'project.list', params: { projectId } }"
>
{{ $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') }}
</BaseButton>
</div>
<slot name="header" />
</div>
<CustomTransition name="fade">
<Message variant="warning" v-if="currentProject.isArchived" class="mb-4">
{{ $t('project.archived') }}
</Message>
</CustomTransition>
<slot v-if="loadedProjectId"/>
</div>
</template>
<script setup lang="ts">
import {ref, computed, watch} from 'vue'
import {useRoute} from 'vue-router'
import BaseButton from '@/components/base/BaseButton.vue'
import Message from '@/components/misc/message.vue'
import CustomTransition from '@/components/misc/CustomTransition.vue'
import ProjectModel from '@/models/project'
import ProjectService from '@/services/project'
import {getProjectTitle} from '@/helpers/getProjectTitle'
import {saveProjectToHistory} from '@/modules/projectHistory'
import {useTitle} from '@/composables/useTitle'
import {useBaseStore} from '@/stores/base'
import {useProjectStore} from '@/stores/projects'
const props = defineProps({
projectId: {
type: Number,
required: true,
},
viewName: {
type: String,
required: true,
},
})
const route = useRoute()
const baseStore = useBaseStore()
const projectStore = useProjectStore()
const projectService = ref(new ProjectService())
const loadedProjectId = ref(0)
const currentProject = computed(() => {
return typeof baseStore.currentProject === 'undefined' ? {
id: 0,
title: '',
isArchived: false,
maxRight: null,
} : baseStore.currentProject
})
useTitle(() => currentProject.value.id ? getProjectTitle(currentProject.value) : '')
// 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,
// loadProject
async (projectIdToLoad: number) => {
const projectData = {id: projectIdToLoad}
saveProjectToHistory(projectData)
// Don't load the project if we either already loaded it or aren't dealing with a project at all currently and
// the currently loaded project has the right set.
if (
(
projectIdToLoad === loadedProjectId.value ||
typeof projectIdToLoad === 'undefined' ||
projectIdToLoad === currentProject.value.id
)
&& typeof currentProject.value !== 'undefined' && currentProject.value.maxRight !== null
) {
loadedProjectId.value = props.projectId
return
}
console.debug(`Loading project, props.viewName = ${props.viewName}, $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
const projectFromStore = projectStore.getProjectById(projectData.id)
if (projectFromStore !== null) {
baseStore.setBackground(null)
baseStore.setBlurHash(null)
baseStore.handleSetCurrentProject({project: projectFromStore})
}
// We create an extra project object instead of creating it in project.value because that would trigger a ui update which would result in bad ux.
const project = new ProjectModel(projectData)
try {
const loadedProject = await projectService.value.get(project)
baseStore.handleSetCurrentProject({project: loadedProject})
} finally {
loadedProjectId.value = props.projectId
}
},
{immediate: true},
)
</script>
<style lang="scss" scoped>
.switch-view-container {
@media screen and (max-width: $tablet) {
display: flex;
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;
}
.switch-view-button {
padding: .25rem .5rem;
display: block;
border-radius: $radius;
transition: all 100ms;
&:not(:last-child) {
margin-right: .5rem;
}
&:hover {
color: var(--switch-view-color);
background: var(--primary);
}
&.is-active {
color: var(--switch-view-color);
background: var(--primary);
font-weight: bold;
box-shadow: var(--shadow-xs);
}
}
// FIXME: this should be in notification and set via a prop
.is-archived .notification.is-warning {
margin-bottom: 1rem;
}
</style>

View File

@ -1,39 +1,39 @@
<template>
<div
class="list-card"
class="project-card"
:class="{
'has-light-text': background !== null,
'has-background': blurHashUrl !== '' || background !== null
}"
:style="{
'border-left': list.hexColor ? `0.25rem solid ${list.hexColor}` : undefined,
'border-left': project.hexColor ? `0.25rem solid ${project.hexColor}` : undefined,
'background-image': blurHashUrl !== '' ? `url(${blurHashUrl})` : undefined,
}"
>
<div
class="list-background background-fade-in"
class="project-background background-fade-in"
:class="{'is-visible': background}"
:style="{'background-image': background !== null ? `url(${background})` : undefined}"
/>
<span v-if="list.isArchived" class="is-archived" >{{ $t('namespace.archived') }}</span>
<span v-if="project.isArchived" class="is-archived" >{{ $t('namespace.archived') }}</span>
<div class="list-title" aria-hidden="true">{{ list.title }}</div>
<div class="project-title" aria-hidden="true">{{ project.title }}</div>
<BaseButton
class="list-button"
:aria-label="list.title"
:title="list.description"
class="project-button"
:aria-label="project.title"
:title="project.description"
:to="{
name: 'list.index',
params: { listId: list.id}
name: 'project.index',
params: { projectId: project.id}
}"
/>
<BaseButton
v-if="!list.isArchived"
v-if="!project.isArchived"
class="favorite"
:class="{'is-favorite': list.isFavorite}"
@click.prevent.stop="listStore.toggleListFavorite(list)"
:class="{'is-favorite': project.isFavorite}"
@click.prevent.stop="projectStore.toggleProjectFavorite(project)"
>
<icon :icon="list.isFavorite ? 'star' : ['far', 'star']" />
<icon :icon="project.isFavorite ? 'star' : ['far', 'star']" />
</BaseButton>
</div>
</template>
@ -41,30 +41,30 @@
<script lang="ts" setup>
import {toRef, type PropType} from 'vue'
import type {IList} from '@/modelTypes/IList'
import type {IProject} from '@/modelTypes/IProject'
import BaseButton from '@/components/base/BaseButton.vue'
import {useListBackground} from './useListBackground'
import {useListStore} from '@/stores/lists'
import {useProjectBackground} from './useProjectBackground'
import {useProjectStore} from '@/stores/projects'
const props = defineProps({
list: {
type: Object as PropType<IList>,
project: {
type: Object as PropType<IProject>,
required: true,
},
})
const {background, blurHashUrl} = useListBackground(toRef(props, 'list'))
const {background, blurHashUrl} = useProjectBackground(toRef(props, 'project'))
const listStore = useListStore()
const projectStore = useProjectStore()
</script>
<style lang="scss" scoped>
.list-card {
--list-card-padding: 1rem;
.project-card {
--project-card-padding: 1rem;
background: var(--white);
padding: var(--list-card-padding);
padding: var(--project-card-padding);
border-radius: $radius;
box-shadow: var(--shadow-sm);
transition: box-shadow $transition;
@ -91,14 +91,14 @@ const listStore = useListStore()
}
.has-background,
.list-background {
.project-background {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
}
.list-background,
.list-button {
.project-background,
.project-button {
position: absolute;
top: 0;
right: 0;
@ -111,7 +111,7 @@ const listStore = useListStore()
float: left;
}
.list-title {
.project-title {
align-self: flex-end;
font-family: $vikunja-font;
font-weight: 400;
@ -120,7 +120,7 @@ const listStore = useListStore()
color: var(--text);
width: 100%;
margin-bottom: 0;
max-height: calc(100% - (var(--list-card-padding) + 1rem)); // padding & height of the "is archived" badge
max-height: calc(100% - (var(--project-card-padding) + 1rem)); // padding & height of the "is archived" badge
overflow: hidden;
text-overflow: ellipsis;
word-break: break-word;
@ -130,11 +130,11 @@ const listStore = useListStore()
-webkit-box-orient: vertical;
}
.has-light-text .list-title {
.has-light-text .project-title {
color: var(--grey-100);
}
.has-background .list-title {
.has-background .project-title {
text-shadow:
0 0 10px var(--black),
1px 1px 5px var(--grey-700),
@ -144,10 +144,10 @@ const listStore = useListStore()
.favorite {
position: absolute;
top: var(--list-card-padding);
right: var(--list-card-padding);
top: var(--project-card-padding);
right: var(--project-card-padding);
transition: opacity $transition, color $transition;
opacity: 0;
opacity: 1;
&:hover {
color: var(--warning);
@ -160,8 +160,14 @@ const listStore = useListStore()
}
}
.list-card:hover .favorite {
opacity: 1;
@media(hover: hover) and (pointer: fine) {
.project-card .favorite {
opacity: 0;
}
.project-card:hover .favorite {
opacity: 1;
}
}
.background-fade-in {
@ -173,4 +179,4 @@ const listStore = useListStore()
opacity: 1;
}
}
</style>
</style>

View File

@ -0,0 +1,73 @@
<template>
<ul class="project-grid">
<li
v-for="(item, index) in filteredProjects"
:key="`project_${item.id}_${index}`"
class="project-grid-item"
>
<ProjectCard :project="item" />
</li>
</ul>
</template>
<script lang="ts" setup>
import {computed, type PropType} from 'vue'
import type {IProject} from '@/modelTypes/IProject'
import ProjectCard from './ProjectCard.vue'
const props = defineProps({
projects: {
type: Array as PropType<IProject[]>,
default: () => [],
},
showArchived: {
default: false,
type: Boolean,
},
itemLimit: {
type: Boolean,
default: false,
},
})
const filteredProjects = computed(() => {
return props.showArchived
? props.projects
: props.projects.filter(l => !l.isArchived)
})
</script>
<style lang="scss" scoped>
.project-grid {
--project-grid-item-height: 150px;
--project-grid-gap: 1rem;
margin: 0; // reset li
list-style-type: none;
display: grid;
grid-template-columns: repeat(var(--project-grid-columns), 1fr);
grid-auto-rows: var(--project-grid-item-height);
gap: var(--project-grid-gap);
@media screen and (min-width: $mobile) {
--project-grid-columns: 1;
}
@media screen and (min-width: $mobile) and (max-width: $tablet) {
--project-grid-columns: 2;
}
@media screen and (min-width: $tablet) and (max-width: $widescreen) {
--project-grid-columns: 3;
}
@media screen and (min-width: $widescreen) {
--project-grid-columns: 5;
}
}
.project-grid-item {
display: grid;
margin-top: 0; // remove padding coming form .content li + li
}
</style>

View File

@ -32,7 +32,7 @@
<script setup lang="ts">
import {computed, ref, watch} from 'vue'
import Filters from '@/components/list/partials/filters.vue'
import Filters from '@/components/project/partials/filters.vue'
import {getDefaultParams} from '@/composables/useTaskList'

View File

@ -20,7 +20,7 @@
{{ $t('filters.attributes.showDoneTasks') }}
</fancycheckbox>
<fancycheckbox
v-if="!['list.kanban', 'list.table'].includes($route.name as string)"
v-if="!['project.kanban', 'project.table'].includes($route.name as string)"
v-model="sortAlphabetically"
@update:model-value="change()"
>
@ -154,14 +154,14 @@
</div>
<template
v-if="['filters.create', 'list.edit', 'filter.settings.edit'].includes($route.name as string)">
v-if="['filters.create', 'project.edit', 'filter.settings.edit'].includes($route.name as string)">
<div class="field">
<label class="label">{{ $t('list.lists') }}</label>
<label class="label">{{ $t('project.lists') }}</label>
<div class="control">
<SelectList
v-model="entities.lists"
@select="changeMultiselectFilter('lists', 'list_id')"
@remove="changeMultiselectFilter('lists', 'list_id')"
<SelectProject
v-model="entities.projects"
@select="changeMultiselectFilter('projects', 'project_id')"
@remove="changeMultiselectFilter('projects', 'project_id')"
/>
</div>
</div>
@ -190,7 +190,7 @@ import {camelCase} from 'camel-case'
import type {ILabel} from '@/modelTypes/ILabel'
import type {IUser} from '@/modelTypes/IUser'
import type {INamespace} from '@/modelTypes/INamespace'
import type {IList} from '@/modelTypes/IList'
import type {IProject} from '@/modelTypes/IProject'
import {useLabelStore} from '@/stores/labels'
@ -200,7 +200,7 @@ import PercentDoneSelect from '@/components/tasks/partials/percentDoneSelect.vue
import EditLabels from '@/components/tasks/partials/editLabels.vue'
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
import SelectUser from '@/components/input/SelectUser.vue'
import SelectList from '@/components/input/SelectList.vue'
import SelectProject from '@/components/input/SelectProject.vue'
import SelectNamespace from '@/components/input/SelectNamespace.vue'
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
@ -208,13 +208,13 @@ import {dateIsValid, formatISO} from '@/helpers/time/formatDate'
import {objectToSnakeCase} from '@/helpers/case'
import UserService from '@/services/user'
import ListService from '@/services/list'
import ProjectService from '@/services/project'
import NamespaceService from '@/services/namespace'
// FIXME: do not use this here for now. instead create new version from DEFAULT_PARAMS
import {getDefaultParams} from '@/composables/useTaskList'
// FIXME: merge with DEFAULT_PARAMS in taskList.js
// FIXME: merge with DEFAULT_PARAMS in taskProject.js
const DEFAULT_PARAMS = {
sort_by: [],
order_by: [],
@ -239,7 +239,7 @@ const DEFAULT_FILTERS = {
reminders: '',
assignees: '',
labels: '',
list_id: '',
project_id: '',
namespace: '',
} as const
@ -264,23 +264,23 @@ const filters = ref({...DEFAULT_FILTERS})
const services = {
users: shallowReactive(new UserService()),
lists: shallowReactive(new ListService()),
projects: shallowReactive(new ProjectService()),
namespace: shallowReactive(new NamespaceService()),
}
interface Entities {
users: IUser[]
labels: ILabel[]
lists: IList[]
projects: IProject[]
namespace: INamespace[]
}
type EntityType = 'users' | 'labels' | 'lists' | 'namespace'
type EntityType = 'users' | 'labels' | 'projects' | 'namespace'
const entities: Entities = reactive({
users: [],
labels: [],
lists: [],
projects: [],
namespace: [],
})
@ -327,7 +327,7 @@ function prepareFilters() {
prepareSingleValue('percent_done', 'percentDone', 'usePercentDone', true)
prepareDate('reminders')
prepareRelatedObjectFilter('users', 'assignees')
prepareRelatedObjectFilter('lists', 'list_id')
prepareRelatedObjectFilter('projects', 'project_id')
prepareRelatedObjectFilter('namespace')
prepareSingleValue('labels')

View File

@ -1,30 +1,30 @@
import {ref, watch, type Ref} from 'vue'
import ListService from '@/services/list'
import type {IList} from '@/modelTypes/IList'
import ProjectService from '@/services/project'
import type {IProject} from '@/modelTypes/IProject'
import {getBlobFromBlurHash} from '@/helpers/getBlobFromBlurHash'
export function useListBackground(list: Ref<IList>) {
export function useProjectBackground(project: Ref<IProject>) {
const background = ref<string | null>(null)
const backgroundLoading = ref(false)
const blurHashUrl = ref('')
watch(
() => [list.value.id, list.value.backgroundBlurHash] as [IList['id'], IList['backgroundBlurHash']],
async ([listId, blurHash], oldValue) => {
() => [project.value.id, project.value.backgroundBlurHash] as [IProject['id'], IProject['backgroundBlurHash']],
async ([projectId, blurHash], oldValue) => {
if (
list.value === null ||
!list.value.backgroundInformation ||
project.value === null ||
!project.value.backgroundInformation ||
backgroundLoading.value
) {
return
}
const [oldListId, oldBlurHash] = oldValue || []
const [oldProjectId, oldBlurHash] = oldValue || []
if (
oldValue !== undefined &&
listId === oldListId && blurHash === oldBlurHash
oldValue !== undefined &&
projectId === oldProjectId && blurHash === oldBlurHash
) {
// list hasn't changed
// project hasn't changed
return
}
@ -35,8 +35,8 @@ export function useListBackground(list: Ref<IList>) {
blurHashUrl.value = blurHash ? window.URL.createObjectURL(blurHash) : ''
})
const listService = new ListService()
const backgroundPromise = listService.background(list.value).then((result) => {
const projectService = new ProjectService()
const backgroundPromise = projectService.background(project.value).then((result) => {
background.value = result
})
await Promise.all([blurHashPromise, backgroundPromise])
@ -44,7 +44,7 @@ export function useListBackground(list: Ref<IList>) {
backgroundLoading.value = false
}
},
{ immediate: true },
{immediate: true},
)
return {
@ -52,4 +52,4 @@ export function useListBackground(list: Ref<IList>) {
blurHashUrl,
backgroundLoading,
}
}
}

View File

@ -8,24 +8,24 @@
</slot>
</template>
<template v-if="isSavedFilter(list)">
<template v-if="isSavedFilter(project)">
<dropdown-item
:to="{ name: 'filter.settings.edit', params: { listId: list.id } }"
:to="{ name: 'filter.settings.edit', params: { projectId: project.id } }"
icon="pen"
>
{{ $t('menu.edit') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'filter.settings.delete', params: { listId: list.id } }"
:to="{ name: 'filter.settings.delete', params: { projectId: project.id } }"
icon="trash-alt"
>
{{ $t('misc.delete') }}
</dropdown-item>
</template>
<template v-else-if="list.isArchived">
<template v-else-if="project.isArchived">
<dropdown-item
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
:to="{ name: 'project.settings.archive', params: { projectId: project.id } }"
icon="archive"
>
{{ $t('menu.unarchive') }}
@ -33,32 +33,32 @@
</template>
<template v-else>
<dropdown-item
:to="{ name: 'list.settings.edit', params: { listId: list.id } }"
:to="{ name: 'project.settings.edit', params: { projectId: project.id } }"
icon="pen"
>
{{ $t('menu.edit') }}
</dropdown-item>
<dropdown-item
v-if="backgroundsEnabled"
:to="{ name: 'list.settings.background', params: { listId: list.id } }"
:to="{ name: 'project.settings.background', params: { projectId: project.id } }"
icon="image"
>
{{ $t('menu.setBackground') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'list.settings.share', params: { listId: list.id } }"
:to="{ name: 'project.settings.share', params: { projectId: project.id } }"
icon="share-alt"
>
{{ $t('menu.share') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'list.settings.duplicate', params: { listId: list.id } }"
:to="{ name: 'project.settings.duplicate', params: { projectId: project.id } }"
icon="paste"
>
{{ $t('menu.duplicate') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
:to="{ name: 'project.settings.archive', params: { projectId: project.id } }"
icon="archive"
>
{{ $t('menu.archive') }}
@ -66,14 +66,14 @@
<Subscription
class="has-no-shadow"
:is-button="false"
entity="list"
:entity-id="list.id"
:model-value="list.subscription"
entity="project"
:entity-id="project.id"
:model-value="project.subscription"
@update:model-value="setSubscriptionInStore"
type="dropdown"
/>
<dropdown-item
:to="{ name: 'list.settings.delete', params: { listId: list.id } }"
:to="{ name: 'project.settings.delete', params: { projectId: project.id } }"
icon="trash-alt"
class="has-text-danger"
>
@ -90,26 +90,26 @@ import BaseButton from '@/components/base/BaseButton.vue'
import Dropdown from '@/components/misc/dropdown.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue'
import Subscription from '@/components/misc/subscription.vue'
import type {IList} from '@/modelTypes/IList'
import type {IProject} from '@/modelTypes/IProject'
import type {ISubscription} from '@/modelTypes/ISubscription'
import {isSavedFilter} from '@/services/savedFilter'
import {useConfigStore} from '@/stores/config'
import {useListStore} from '@/stores/lists'
import {useProjectStore} from '@/stores/projects'
import {useNamespaceStore} from '@/stores/namespaces'
const props = defineProps({
list: {
type: Object as PropType<IList>,
project: {
type: Object as PropType<IProject>,
required: true,
},
})
const listStore = useListStore()
const projectStore = useProjectStore()
const namespaceStore = useNamespaceStore()
const subscription = ref<ISubscription | null>(null)
watchEffect(() => {
subscription.value = props.list.subscription ?? null
subscription.value = props.project.subscription ?? null
})
const configStore = useConfigStore()
@ -117,11 +117,11 @@ const backgroundsEnabled = computed(() => configStore.enabledBackgroundProviders
function setSubscriptionInStore(sub: ISubscription) {
subscription.value = sub
const updatedList = {
...props.list,
const updatedProject = {
...props.project,
subscription: sub,
}
listStore.setList(updatedList)
namespaceStore.setListInNamespaceById(updatedList)
projectStore.setProject(updatedProject)
namespaceStore.setProjectInNamespaceById(updatedProject)
}
</script>

View File

@ -63,18 +63,18 @@ import TeamService from '@/services/team'
import NamespaceModel from '@/models/namespace'
import TeamModel from '@/models/team'
import ListModel from '@/models/list'
import ProjectModel from '@/models/project'
import BaseButton from '@/components/base/BaseButton.vue'
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
import {useBaseStore} from '@/stores/base'
import {useListStore} from '@/stores/lists'
import {useProjectStore} from '@/stores/projects'
import {useNamespaceStore} from '@/stores/namespaces'
import {useLabelStore} from '@/stores/labels'
import {useTaskStore} from '@/stores/tasks'
import {getHistory} from '@/modules/listHistory'
import {getHistory} from '@/modules/projectHistory'
import {parseTaskText, PrefixMode, PREFIXES} from '@/modules/parseTaskText'
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
import {success} from '@/message'
@ -82,13 +82,13 @@ import {success} from '@/message'
import type {ITeam} from '@/modelTypes/ITeam'
import type {ITask} from '@/modelTypes/ITask'
import type {INamespace} from '@/modelTypes/INamespace'
import type {IList} from '@/modelTypes/IList'
import type {IProject} from '@/modelTypes/IProject'
const {t} = useI18n({useScope: 'global'})
const router = useRouter()
const baseStore = useBaseStore()
const listStore = useListStore()
const projectStore = useProjectStore()
const namespaceStore = useNamespaceStore()
const labelStore = useLabelStore()
const taskStore = useTaskStore()
@ -98,13 +98,13 @@ type DoAction<Type = any> = { type: ACTION_TYPE } & Type
enum ACTION_TYPE {
CMD = 'cmd',
TASK = 'task',
LIST = 'list',
PROJECT = 'project',
TEAM = 'team',
}
enum COMMAND_TYPE {
NEW_TASK = 'newTask',
NEW_LIST = 'newList',
NEW_PROJECT = 'newProject',
NEW_NAMESPACE = 'newNamespace',
NEW_TEAM = 'newTeam',
}
@ -112,7 +112,7 @@ enum COMMAND_TYPE {
enum SEARCH_MODE {
ALL = 'all',
TASKS = 'tasks',
LISTS = 'lists',
PROJECTS = 'projects',
TEAMS = 'teams',
}
@ -137,26 +137,26 @@ function closeQuickActions() {
baseStore.setQuickActionsActive(false)
}
const foundLists = computed(() => {
const { list } = parsedQuery.value
const foundProjects = computed(() => {
const { project } = parsedQuery.value
if (
searchMode.value === SEARCH_MODE.ALL ||
searchMode.value === SEARCH_MODE.LISTS ||
list === null
searchMode.value === SEARCH_MODE.PROJECTS ||
project === null
) {
return []
}
const ncache: { [id: ListModel['id']]: INamespace } = {}
const ncache: { [id: ProjectModel['id']]: INamespace } = {}
const history = getHistory()
const allLists = [
const allProjects = [
...new Set([
...history.map((l) => listStore.getListById(l.id)),
...listStore.searchList(list),
...history.map((l) => projectStore.getProjectById(l.id)),
...projectStore.searchProject(project),
]),
]
return allLists.filter((l) => {
return allProjects.filter((l) => {
if (typeof l === 'undefined' || l === null) {
return false
}
@ -191,9 +191,9 @@ const results = computed<Result[]>(() => {
items: foundTasks.value,
},
{
type: ACTION_TYPE.LIST,
title: t('quickActions.lists'),
items: foundLists.value,
type: ACTION_TYPE.PROJECT,
title: t('quickActions.projects'),
items: foundProjects.value,
},
{
type: ACTION_TYPE.TEAM,
@ -206,7 +206,7 @@ const results = computed<Result[]>(() => {
const loading = computed(() =>
taskService.loading ||
namespaceStore.isLoading ||
listStore.isLoading ||
projectStore.isLoading ||
teamService.loading,
)
@ -224,11 +224,11 @@ const commands = computed<{ [key in COMMAND_TYPE]: Command }>(() => ({
placeholder: t('quickActions.newTask'),
action: newTask,
},
newList: {
type: COMMAND_TYPE.NEW_LIST,
title: t('quickActions.cmds.newList'),
placeholder: t('quickActions.newList'),
action: newList,
newProject: {
type: COMMAND_TYPE.NEW_PROJECT,
title: t('quickActions.cmds.newProject'),
placeholder: t('quickActions.newProject'),
action: newProject,
},
newNamespace: {
type: COMMAND_TYPE.NEW_NAMESPACE,
@ -246,24 +246,24 @@ const commands = computed<{ [key in COMMAND_TYPE]: Command }>(() => ({
const placeholder = computed(() => selectedCmd.value?.placeholder || t('quickActions.placeholder'))
const currentList = computed(() => Object.keys(baseStore.currentList).length === 0
const currentProject = computed(() => Object.keys(baseStore.currentProject).length === 0
? null
: baseStore.currentList,
: baseStore.currentProject,
)
const hintText = computed(() => {
let namespace
if (selectedCmd.value !== null && currentList.value !== null) {
if (selectedCmd.value !== null && currentProject.value !== null) {
switch (selectedCmd.value.type) {
case COMMAND_TYPE.NEW_TASK:
return t('quickActions.createTask', {
title: currentList.value.title,
title: currentProject.value.title,
})
case COMMAND_TYPE.NEW_LIST:
case COMMAND_TYPE.NEW_PROJECT:
namespace = namespaceStore.getNamespaceById(
currentList.value.namespaceId,
currentProject.value.namespaceId,
)
return t('quickActions.createList', {
return t('quickActions.createProject', {
title: namespace?.title,
})
}
@ -275,8 +275,8 @@ const hintText = computed(() => {
const availableCmds = computed(() => {
const cmds = []
if (currentList.value !== null) {
cmds.push(commands.value.newTask, commands.value.newList)
if (currentProject.value !== null) {
cmds.push(commands.value.newTask, commands.value.newProject)
}
cmds.push(commands.value.newNamespace, commands.value.newTeam)
return cmds
@ -288,21 +288,21 @@ const searchMode = computed(() => {
if (query.value === '') {
return SEARCH_MODE.ALL
}
const { text, list, labels, assignees } = parsedQuery.value
const { text, project, labels, assignees } = parsedQuery.value
if (assignees.length === 0 && text !== '') {
return SEARCH_MODE.TASKS
}
if (
assignees.length === 0 &&
list !== null &&
project !== null &&
text === '' &&
labels.length === 0
) {
return SEARCH_MODE.LISTS
return SEARCH_MODE.PROJECTS
}
if (
assignees.length > 0 &&
list === null &&
project === null &&
text === '' &&
labels.length === 0
) {
@ -356,7 +356,7 @@ function searchTasks() {
taskSearchTimeout.value = null
}
const { text, list: listName, labels } = parsedQuery.value
const { text, project: projectName, labels } = parsedQuery.value
const filters: Filter[] = []
@ -373,10 +373,10 @@ function searchTasks() {
})
}
if (listName !== null) {
const list = listStore.findListByExactname(listName)
if (list !== null) {
addFilter('listId', list.id, 'equals')
if (projectName !== null) {
const project = projectStore.findProjectByExactname(projectName)
if (project !== null) {
addFilter('projectId', project.id, 'equals')
}
}
@ -396,9 +396,9 @@ function searchTasks() {
const r = await taskService.getAll({}, params) as DoAction<ITask>[]
foundTasks.value = r.map((t) => {
t.type = ACTION_TYPE.TASK
const list = listStore.getListById(t.listId)
if (list !== null) {
t.title = `${t.title} (${list.title})`
const project = projectStore.getProjectById(t.projectId)
if (project !== null) {
t.title = `${t.title} (${project.title})`
}
return t
})
@ -428,7 +428,7 @@ function searchTeams() {
teamService.getAll({}, { s: t }),
)
const teamsResult = await Promise.all(teamSearchPromises)
foundTeams.value = teamsResult.flatMap((team) => {
foundTeams.value = teamsResult.flat().map((team) => {
team.title = team.name
return team
})
@ -444,11 +444,11 @@ const searchInput = ref<HTMLElement | null>(null)
async function doAction(type: ACTION_TYPE, item: DoAction) {
switch (type) {
case ACTION_TYPE.LIST:
case ACTION_TYPE.PROJECT:
closeQuickActions()
await router.push({
name: 'list.index',
params: { listId: (item as DoAction<IList>).id },
name: 'project.index',
params: { projectId: (item as DoAction<IProject>).id },
})
break
case ACTION_TYPE.TASK:
@ -458,6 +458,13 @@ async function doAction(type: ACTION_TYPE, item: DoAction) {
params: { id: (item as DoAction<ITask>).id },
})
break
case ACTION_TYPE.TEAM:
closeQuickActions()
await router.push({
name: 'teams.edit',
params: { id: (item as DoAction<ITeam>).id },
})
break
case ACTION_TYPE.CMD:
query.value = ''
selectedCmd.value = item as DoAction<Command>
@ -482,29 +489,29 @@ async function doCmd() {
}
async function newTask() {
if (currentList.value === null) {
if (currentProject.value === null) {
return
}
const task = await taskStore.createNewTask({
title: query.value,
listId: currentList.value.id,
projectId: currentProject.value.id,
})
success({ message: t('task.createSuccess') })
await router.push({ name: 'task.detail', params: { id: task.id } })
}
async function newList() {
if (currentList.value === null) {
async function newProject() {
if (currentProject.value === null) {
return
}
const newList = await listStore.createList(new ListModel({
const newProject = await projectStore.createProject(new ProjectModel({
title: query.value,
namespaceId: currentList.value.namespaceId,
namespaceId: currentProject.value.namespaceId,
}))
success({ message: t('list.create.createdSuccess')})
success({ message: t('project.create.createdSuccess')})
await router.push({
name: 'list.index',
params: { listId: newList.id },
name: 'project.index',
params: { projectId: newProject.id },
})
}

View File

@ -1,39 +1,39 @@
<template>
<div>
<p class="has-text-weight-bold">
{{ $t('list.share.links.title') }}
{{ $t('project.share.links.title') }}
<span
class="is-size-7 has-text-grey is-italic ml-3"
v-tooltip="$t('list.share.links.explanation')">
{{ $t('list.share.links.what') }}
v-tooltip="$t('project.share.links.explanation')">
{{ $t('project.share.links.what') }}
</span>
</p>
<div class="sharables-list">
<div class="sharables-project">
<x-button
v-if="!(linkShares.length === 0 || showNewForm)"
@click="showNewForm = true"
icon="plus"
class="mb-4">
{{ $t('list.share.links.create') }}
{{ $t('project.share.links.create') }}
</x-button>
<div class="p-4" v-if="linkShares.length === 0 || showNewForm">
<div class="field">
<label class="label" for="linkShareRight">
{{ $t('list.share.right.title') }}
{{ $t('project.share.right.title') }}
</label>
<div class="control">
<div class="select">
<select v-model="selectedRight" id="linkShareRight">
<option :value="RIGHTS.READ">
{{ $t('list.share.right.read') }}
{{ $t('project.share.right.read') }}
</option>
<option :value="RIGHTS.READ_WRITE">
{{ $t('list.share.right.readWrite') }}
{{ $t('project.share.right.readWrite') }}
</option>
<option :value="RIGHTS.ADMIN">
{{ $t('list.share.right.admin') }}
{{ $t('project.share.right.admin') }}
</option>
</select>
</div>
@ -41,21 +41,21 @@
</div>
<div class="field">
<label class="label" for="linkShareName">
{{ $t('list.share.links.name') }}
{{ $t('project.share.links.name') }}
</label>
<div class="control">
<input
id="linkShareName"
class="input"
:placeholder="$t('list.share.links.namePlaceholder')"
v-tooltip="$t('list.share.links.nameExplanation')"
:placeholder="$t('project.share.links.namePlaceholder')"
v-tooltip="$t('project.share.links.nameExplanation')"
v-model="name"
/>
</div>
</div>
<div class="field">
<label class="label" for="linkSharePassword">
{{ $t('list.share.links.password') }}
{{ $t('project.share.links.password') }}
</label>
<div class="control">
<input
@ -63,25 +63,25 @@
type="password"
class="input"
:placeholder="$t('user.auth.passwordPlaceholder')"
v-tooltip="$t('list.share.links.passwordExplanation')"
v-tooltip="$t('project.share.links.passwordExplanation')"
v-model="password"
/>
</div>
</div>
<x-button @click="add(listId)" icon="plus">
{{ $t('list.share.share') }}
<x-button @click="add(projectId)" icon="plus">
{{ $t('project.share.share') }}
</x-button>
</div>
<table
class="table has-actions is-striped is-hoverable is-fullwidth link-share-list"
class="table has-actions is-striped is-hoverable is-fullwidth"
v-if="linkShares.length > 0"
>
<thead>
<tr>
<th></th>
<th>{{ $t('list.share.links.view') }}</th>
<th>{{ $t('list.share.attributes.delete') }}</th>
<th>{{ $t('project.share.links.view') }}</th>
<th>{{ $t('project.share.attributes.delete') }}</th>
</tr>
</thead>
<tbody>
@ -92,7 +92,7 @@
</p>
<p class="mb-2">
<i18n-t keypath="list.share.links.sharedBy" scope="global">
<i18n-t keypath="project.share.links.sharedBy" scope="global">
<strong>{{ getDisplayName(s.sharedBy) }}</strong>
</i18n-t>
</p>
@ -102,19 +102,19 @@
<span class="icon is-small">
<icon icon="lock"/>
</span>&nbsp;
{{ $t('list.share.right.admin') }}
{{ $t('project.share.right.admin') }}
</template>
<template v-else-if="s.right === RIGHTS.READ_WRITE">
<span class="icon is-small">
<icon icon="pen"/>
</span>&nbsp;
{{ $t('list.share.right.readWrite') }}
{{ $t('project.share.right.readWrite') }}
</template>
<template v-else>
<span class="icon is-small">
<icon icon="users"/>
</span>&nbsp;
{{ $t('list.share.right.read') }}
{{ $t('project.share.right.read') }}
</template>
</p>
@ -172,14 +172,14 @@
<modal
:enabled="showDeleteModal"
@close="showDeleteModal = false"
@submit="remove(listId)"
@submit="remove(projectId)"
>
<template #header>
<span>{{ $t('list.share.links.remove') }}</span>
<span>{{ $t('project.share.links.remove') }}</span>
</template>
<template #text>
<p>{{ $t('list.share.links.removeText') }}</p>
<p>{{ $t('project.share.links.removeText') }}</p>
</template>
</modal>
</div>
@ -193,19 +193,19 @@ import {RIGHTS} from '@/constants/rights'
import LinkShareModel from '@/models/linkShare'
import type {ILinkShare} from '@/modelTypes/ILinkShare'
import type {IList} from '@/modelTypes/IList'
import type {IProject} from '@/modelTypes/IProject'
import LinkShareService from '@/services/linkShare'
import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
import {success} from '@/message'
import {getDisplayName} from '@/models/user'
import type {ListView} from '@/types/ListView'
import {LIST_VIEWS} from '@/types/ListView'
import type {ProjectView} from '@/types/ProjectView'
import {PROJECT_VIEWS} from '@/types/ProjectView'
import {useConfigStore} from '@/stores/config'
const props = defineProps({
listId: {
projectId: {
default: 0,
required: true,
},
@ -222,20 +222,20 @@ const showDeleteModal = ref(false)
const linkIdToDelete = ref(0)
const showNewForm = ref(false)
type SelectedViewMapper = Record<IList['id'], ListView>
type SelectedViewMapper = Record<IProject['id'], ProjectView>
const selectedView = ref<SelectedViewMapper>({})
const availableViews = computed<Record<ListView, string>>(() => ({
list: t('list.list.title'),
gantt: t('list.gantt.title'),
table: t('list.table.title'),
kanban: t('list.kanban.title'),
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 copy = useCopyToClipboard()
watch(
() => props.listId,
() => props.projectId,
load,
{immediate: true},
)
@ -243,23 +243,23 @@ watch(
const configStore = useConfigStore()
const frontendUrl = computed(() => configStore.frontendUrl)
async function load(listId: IList['id']) {
// If listId == 0 the list on the calling component wasn't already loaded, so we just bail out here
if (listId === 0) {
async function load(projectId: IProject['id']) {
// If projectId == 0 the project on the calling component wasn't already loaded, so we just bail out here
if (projectId === 0) {
return
}
const links = await linkShareService.getAll({listId})
const links = await linkShareService.getAll({projectId})
links.forEach((l: ILinkShare) => {
selectedView.value[l.id] = 'list'
selectedView.value[l.id] = 'project'
})
linkShares.value = links
}
async function add(listId: IList['id']) {
async function add(projectId: IProject['id']) {
const newLinkShare = new LinkShareModel({
right: selectedRight.value,
listId,
projectId,
name: name.value,
password: password.value,
})
@ -268,31 +268,31 @@ async function add(listId: IList['id']) {
name.value = ''
password.value = ''
showNewForm.value = false
success({message: t('list.share.links.createSuccess')})
await load(listId)
success({message: t('project.share.links.createSuccess')})
await load(projectId)
}
async function remove(listId: IList['id']) {
async function remove(projectId: IProject['id']) {
try {
await linkShareService.delete(new LinkShareModel({
id: linkIdToDelete.value,
listId,
projectId,
}))
success({message: t('list.share.links.deleteSuccess')})
await load(listId)
success({message: t('project.share.links.deleteSuccess')})
await load(projectId)
} finally {
showDeleteModal.value = false
}
}
function getShareLink(hash: string, view: ListView = LIST_VIEWS.LIST) {
function getShareLink(hash: string, view: ProjectView = PROJECT_VIEWS.LIST) {
return frontendUrl.value + 'share/' + hash + '/auth?view=' + view
}
</script>
<style lang="scss" scoped>
// FIXME: I think this is not needed
.sharables-list:not(.card-content) {
.sharables-project:not(.card-content) {
overflow-y: auto
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div>
<p class="has-text-weight-bold">
{{ $t('list.share.userTeam.shared', {type: shareTypeNames}) }}
{{ $t('project.share.userTeam.shared', {type: shareTypeNames}) }}
</p>
<div v-if="userIsAdmin">
<div class="field has-addons">
@ -19,7 +19,7 @@
/>
</p>
<p class="control">
<x-button @click="add()">{{ $t('list.share.share') }}</x-button>
<x-button @click="add()">{{ $t('project.share.share') }}</x-button>
</p>
</div>
</div>
@ -31,7 +31,7 @@
<td>{{ getDisplayName(s) }}</td>
<td>
<template v-if="s.id === userInfo.id">
<b class="is-success">{{ $t('list.share.userTeam.you') }}</b>
<b class="is-success">{{ $t('project.share.userTeam.you') }}</b>
</template>
</td>
</template>
@ -52,19 +52,19 @@
<span class="icon is-small">
<icon icon="lock"/>
</span>
{{ $t('list.share.right.admin') }}
{{ $t('project.share.right.admin') }}
</template>
<template v-else-if="s.right === RIGHTS.READ_WRITE">
<span class="icon is-small">
<icon icon="pen"/>
</span>
{{ $t('list.share.right.readWrite') }}
{{ $t('project.share.right.readWrite') }}
</template>
<template v-else>
<span class="icon is-small">
<icon icon="users"/>
</span>
{{ $t('list.share.right.read') }}
{{ $t('project.share.right.read') }}
</template>
</td>
<td class="actions" v-if="userIsAdmin">
@ -78,19 +78,19 @@
:selected="s.right === RIGHTS.READ"
:value="RIGHTS.READ"
>
{{ $t('list.share.right.read') }}
{{ $t('project.share.right.read') }}
</option>
<option
:selected="s.right === RIGHTS.READ_WRITE"
:value="RIGHTS.READ_WRITE"
>
{{ $t('list.share.right.readWrite') }}
{{ $t('project.share.right.readWrite') }}
</option>
<option
:selected="s.right === RIGHTS.ADMIN"
:value="RIGHTS.ADMIN"
>
{{ $t('list.share.right.admin') }}
{{ $t('project.share.right.admin') }}
</option>
</select>
</div>
@ -110,7 +110,7 @@
</table>
<nothing v-else>
{{ $t('list.share.userTeam.notShared', {type: shareTypeNames}) }}
{{ $t('project.share.userTeam.notShared', {type: shareTypeNames}) }}
</nothing>
<modal
@ -120,11 +120,11 @@
>
<template #header>
<span>{{
$t('list.share.userTeam.removeHeader', {type: shareTypeName, sharable: sharableName})
$t('project.share.userTeam.removeHeader', {type: shareTypeName, sharable: sharableName})
}}</span>
</template>
<template #text>
<p>{{ $t('list.share.userTeam.removeText', {type: shareTypeName, sharable: sharableName}) }}</p>
<p>{{ $t('project.share.userTeam.removeText', {type: shareTypeName, sharable: sharableName}) }}</p>
</template>
</modal>
</div>
@ -143,9 +143,9 @@ import UserNamespaceService from '@/services/userNamespace'
import UserNamespaceModel from '@/models/userNamespace'
import type {IUserNamespace} from '@/modelTypes/IUserNamespace'
import UserListService from '@/services/userList'
import UserListModel from '@/models/userList'
import type {IUserList} from '@/modelTypes/IUserList'
import UserProjectService from '@/services/userProject'
import UserProjectModel from '@/models/userProject'
import type {IUserProject} from '@/modelTypes/IUserProject'
import UserService from '@/services/user'
import UserModel, { getDisplayName } from '@/models/user'
@ -155,9 +155,9 @@ import TeamNamespaceService from '@/services/teamNamespace'
import TeamNamespaceModel from '@/models/teamNamespace'
import type { ITeamNamespace } from '@/modelTypes/ITeamNamespace'
import TeamListService from '@/services/teamList'
import TeamListModel from '@/models/teamList'
import type { ITeamList } from '@/modelTypes/ITeamList'
import TeamProjectService from '@/services/teamProject'
import TeamProjectModel from '@/models/teamProject'
import type { ITeamProject } from '@/modelTypes/ITeamProject'
import TeamService from '@/services/team'
import TeamModel from '@/models/team'
@ -172,7 +172,7 @@ import {useAuthStore} from '@/stores/auth'
const props = defineProps({
type: {
type: String as PropType<'list' | 'namespace'>,
type: String as PropType<'project' | 'namespace'>,
default: '',
},
shareType: {
@ -191,9 +191,9 @@ const props = defineProps({
const {t} = useI18n({useScope: 'global'})
// This user service is either a userNamespaceService or a userListService, depending on the type we are using
let stuffService: UserNamespaceService | UserListService | TeamListService | TeamNamespaceService
let stuffModel: IUserNamespace | IUserList | ITeamList | ITeamNamespace
// This user service is either a userNamespaceService or a userProjectService, depending on the type we are using
let stuffService: UserNamespaceService | UserProjectService | TeamProjectService | TeamNamespaceService
let stuffModel: IUserNamespace | IUserProject | ITeamProject | ITeamNamespace
let searchService: UserService | TeamService
let sharable: Ref<IUser | ITeam>
@ -201,7 +201,7 @@ const searchLabel = ref('')
const selectedRight = ref({})
// This holds either teams or users who this namepace or list is shared with
// This holds either teams or users who this namepace or project is shared with
const sharables = ref([])
const showDeleteModal = ref(false)
@ -212,11 +212,11 @@ const userInfo = computed(() => authStore.info)
function createShareTypeNameComputed(count: number) {
return computed(() => {
if (props.shareType === 'user') {
return t('list.share.userTeam.typeUser', count)
return t('project.share.userTeam.typeUser', count)
}
if (props.shareType === 'team') {
return t('list.share.userTeam.typeTeam', count)
return t('project.share.userTeam.typeTeam', count)
}
return ''
@ -227,8 +227,8 @@ const shareTypeNames = createShareTypeNameComputed(2)
const shareTypeName = createShareTypeNameComputed(1)
const sharableName = computed(() => {
if (props.type === 'list') {
return t('list.list.title')
if (props.type === 'project') {
return t('project.list.title')
}
if (props.shareType === 'namespace') {
@ -244,9 +244,9 @@ if (props.shareType === 'user') {
sharable = ref(new UserModel())
searchLabel.value = 'username'
if (props.type === 'list') {
stuffService = shallowReactive(new UserListService())
stuffModel = reactive(new UserListModel({listId: props.id}))
if (props.type === 'project') {
stuffService = shallowReactive(new UserProjectService())
stuffModel = reactive(new UserProjectModel({projectId: props.id}))
} else if (props.type === 'namespace') {
stuffService = shallowReactive(new UserNamespaceService())
stuffModel = reactive(new UserNamespaceModel({
@ -261,9 +261,9 @@ if (props.shareType === 'user') {
sharable = ref(new TeamModel())
searchLabel.value = 'name'
if (props.type === 'list') {
stuffService = shallowReactive(new TeamListService())
stuffModel = reactive(new TeamListModel({listId: props.id}))
if (props.type === 'project') {
stuffService = shallowReactive(new TeamProjectService())
stuffModel = reactive(new TeamProjectModel({projectId: props.id}))
} else if (props.type === 'namespace') {
stuffService = shallowReactive(new TeamNamespaceService())
stuffModel = reactive(new TeamNamespaceModel({
@ -303,7 +303,7 @@ async function deleteSharable() {
}
}
success({
message: t('list.share.userTeam.removeSuccess', {
message: t('project.share.userTeam.removeSuccess', {
type: shareTypeName.value,
sharable: sharableName.value,
}),
@ -326,7 +326,7 @@ async function add(admin) {
}
await stuffService.create(stuffModel)
success({message: t('list.share.userTeam.addedSuccess', {type: shareTypeName.value})})
success({message: t('project.share.userTeam.addedSuccess', {type: shareTypeName.value})})
await load()
}
@ -358,7 +358,7 @@ async function toggleType(sharable) {
sharables.value[i].right = r.right
}
}
success({message: t('list.share.userTeam.updatedSuccess', {type: shareTypeName.value})})
success({message: t('project.share.userTeam.updatedSuccess', {type: shareTypeName.value})})
}
const found = ref([])

View File

@ -38,9 +38,8 @@
</template>
<script setup lang="ts">
import {computed, ref, watch, toRefs} from 'vue'
import {computed, ref, watch, toRefs, onActivated} from 'vue'
import {useRouter} from 'vue-router'
import {useNow} from '@vueuse/core'
import {getHexColor} from '@/models/task'
@ -50,7 +49,7 @@ import {parseKebabDate} from '@/helpers/time/parseKebabDate'
import type {ITask, ITaskPartialWithId} from '@/modelTypes/ITask'
import type {DateISO} from '@/types/DateISO'
import type {GanttFilters} from '@/views/list/helpers/useGanttFilters'
import type {GanttFilters} from '@/views/project/helpers/useGanttFilters'
import {
extendDayjs,
@ -157,7 +156,8 @@ function openTask(e: {
const weekDayFromDate = useWeekDayFromDate()
const today = useNow()
const today = ref(new Date())
onActivated(() => today.value = new Date())
const dateIsToday = computed(() => (date: Date) => {
return (
date.getDate() === today.value.getDate() &&

View File

@ -5,7 +5,7 @@
<textarea
class="add-task-textarea input"
:class="{'textarea-empty': newTaskTitle === ''}"
:placeholder="$t('list.list.addPlaceholder')"
:placeholder="$t('project.list.addPlaceholder')"
rows="1"
v-focus
v-model="newTaskTitle"
@ -24,10 +24,10 @@
@click="addTask()"
icon="plus"
:loading="loading"
:aria-label="$t('list.list.add')"
:aria-label="$t('project.list.add')"
>
<span class="button-text">
{{ $t('list.list.add') }}
{{ $t('project.list.add') }}
</span>
</x-button>
</p>
@ -107,7 +107,7 @@ const loading = computed(() => taskStore.isLoading)
async function addTask() {
if (newTaskTitle.value === '') {
errorMessage.value = t('list.create.addTitleRequired')
errorMessage.value = t('project.create.addTitleRequired')
return
}
errorMessage.value = ''
@ -128,20 +128,20 @@ async function addTask() {
const allLabels = tasksToCreate.map(({title}) => getLabelsFromPrefix(title) ?? [])
await taskStore.ensureLabelsExist(allLabels.flat())
const newTasks = tasksToCreate.map(async ({title, list}) => {
const newTasks = tasksToCreate.map(async ({title, project}) => {
if (title === '') {
return
}
// If the task has a list specified, make sure to use it
let listId = null
if (list !== null) {
listId = await taskStore.findListId({list, listId: 0})
// If the task has a project specified, make sure to use it
let projectId = null
if (project !== null) {
projectId = await taskStore.findProjectId({project, projectId: 0})
}
const task = await taskStore.createNewTask({
title,
listId: listId || authStore.settings.defaultListId,
projectId: projectId || authStore.settings.defaultProjectId,
position: props.defaultPosition,
})
createdTasks[title] = task
@ -176,7 +176,7 @@ async function addTask() {
}))
createdTask.relatedTasks[RELATION_KIND.PARENTTASK] = [createdParentTask]
// we're only emitting here so that the relation shows up in the task list
// we're only emitting here so that the relation shows up in the project
emit('taskAdded', createdTask)
return rel
@ -184,8 +184,8 @@ async function addTask() {
await Promise.all(relations)
} catch (e: any) {
newTaskTitle.value = taskTitleBackup
if (e?.message === 'NO_LIST') {
errorMessage.value = t('list.create.addListRequired')
if (e?.message === 'NO_PROJECT') {
errorMessage.value = t('project.create.addProjectRequired')
return
}
throw e

View File

@ -1,6 +1,6 @@
<template>
<Multiselect
:loading="listUserService.loading"
:loading="projectUserService.loading"
:placeholder="$t('task.assignee.placeholder')"
:multiple="true"
@search="findUser"
@ -30,7 +30,7 @@ import Multiselect from '@/components/input/multiselect.vue'
import BaseButton from '@/components/base/BaseButton.vue'
import {includesById} from '@/helpers/utils'
import ListUserService from '@/services/listUsers'
import ProjectUserService from '@/services/projectUsers'
import {success} from '@/message'
import {useTaskStore} from '@/stores/tasks'
@ -42,7 +42,7 @@ const props = defineProps({
type: Number,
required: true,
},
listId: {
projectId: {
type: Number,
required: true,
},
@ -59,7 +59,7 @@ const emit = defineEmits(['update:modelValue'])
const taskStore = useTaskStore()
const {t} = useI18n({useScope: 'global'})
const listUserService = shallowReactive(new ListUserService())
const projectUserService = shallowReactive(new ProjectUserService())
const foundUsers = ref<IUser[]>([])
const assignees = ref<IUser[]>([])
let isAdding = false
@ -94,7 +94,7 @@ async function addAssignee(user: IUser) {
async function removeAssignee(user: IUser) {
await taskStore.removeAssignee({user: user, taskId: props.taskId})
// Remove the assignee from the list
// Remove the assignee from the project
for (const a in assignees.value) {
if (assignees.value[a].id === user.id) {
assignees.value.splice(a, 1)
@ -109,7 +109,7 @@ async function findUser(query: string) {
return
}
const response = await listUserService.getAll({listId: props.listId}, {s: query}) as IUser[]
const response = await projectUserService.getAll({projectId: props.projectId}, {s: query}) as IUser[]
// Filter the results to not include users who are already assigned
foundUsers.value = response

View File

@ -1,18 +1,18 @@
<template>
<Multiselect
class="control is-expanded"
:placeholder="$t('list.search')"
:search-results="foundLists"
:placeholder="$t('project.search')"
:search-results="foundProjects"
label="title"
:select-placeholder="$t('list.searchSelect')"
:model-value="list"
@update:model-value="Object.assign(list, $event)"
:select-placeholder="$t('project.searchSelect')"
:model-value="project"
@update:model-value="Object.assign(project, $event)"
@select="select"
@search="findLists"
@search="findProjects"
>
<template #searchResult="{option}">
<span class="list-namespace-title search-result">{{ namespace((option as IList).namespaceId) }} ></span>
{{ (option as IList).title }}
<span class="project-namespace-title search-result">{{ namespace((option as IProject).namespaceId) }} ></span>
{{ (option as IProject).title }}
</template>
</Multiselect>
</template>
@ -22,19 +22,19 @@ import {reactive, ref, watch} from 'vue'
import type {PropType} from 'vue'
import {useI18n} from 'vue-i18n'
import type {IList} from '@/modelTypes/IList'
import type {IProject} from '@/modelTypes/IProject'
import type {INamespace} from '@/modelTypes/INamespace'
import {useListStore} from '@/stores/lists'
import {useProjectStore} from '@/stores/projects'
import {useNamespaceStore} from '@/stores/namespaces'
import ListModel from '@/models/list'
import ProjectModel from '@/models/project'
import Multiselect from '@/components/input/multiselect.vue'
const props = defineProps({
modelValue: {
type: Object as PropType<IList>,
type: Object as PropType<IProject>,
required: false,
},
})
@ -42,45 +42,45 @@ const emit = defineEmits(['update:modelValue'])
const {t} = useI18n({useScope: 'global'})
const list: IList = reactive(new ListModel())
const project: IProject = reactive(new ProjectModel())
watch(
() => props.modelValue,
(newList) => Object.assign(list, newList),
(newProject) => Object.assign(project, newProject),
{
immediate: true,
deep: true,
},
)
const listStore = useListStore()
const projectStore = useProjectStore()
const namespaceStore = useNamespaceStore()
const foundLists = ref<IList[]>([])
function findLists(query: string) {
const foundProjects = ref<IProject[]>([])
function findProjects(query: string) {
if (query === '') {
select(null)
}
foundLists.value = listStore.searchList(query)
foundProjects.value = projectStore.searchProject(query)
}
function select(l: IList | null) {
function select(l: IProject | null) {
if (l === null) {
return
}
Object.assign(list, l)
emit('update:modelValue', list)
Object.assign(project, l)
emit('update:modelValue', project)
}
function namespace(namespaceId: INamespace['id']) {
const namespace = namespaceStore.getNamespaceById(namespaceId)
return namespace !== null
? namespace.title
: t('list.shared')
: t('project.shared')
}
</script>
<style lang="scss" scoped>
.list-namespace-title {
.project-namespace-title {
color: var(--grey-500);
}
</style>

View File

@ -37,14 +37,14 @@
{{ $t('task.quickAddMagic.multiple') }}
</p>
<h3>{{ $t('list.list.title') }}</h3>
<h3>{{ $t('project.list.title') }}</h3>
<p>
{{ $t('task.quickAddMagic.list1', {prefix: prefixes.list}) }}
{{ $t('task.quickAddMagic.list2') }}
{{ $t('task.quickAddMagic.project1', {prefix: prefixes.project}) }}
{{ $t('task.quickAddMagic.project2') }}
</p>
<p>
{{ $t('task.quickAddMagic.list3') }}
{{ $t('task.quickAddMagic.list4', {prefix: prefixes.list}) }}
{{ $t('task.quickAddMagic.project3') }}
{{ $t('task.quickAddMagic.project4', {prefix: prefixes.project}) }}
</p>
<h3>{{ $t('task.quickAddMagic.dateAndTime') }}</h3>

View File

@ -43,8 +43,8 @@
:class="{'is-strikethrough': task.done}"
>
<span
class="different-list"
v-if="task.listId !== listId"
class="different-project"
v-if="task.projectId !== projectId"
>
<span
v-if="task.differentNamespace !== null"
@ -52,9 +52,9 @@
{{ task.differentNamespace }} >
</span>
<span
v-if="task.differentList !== null"
v-tooltip="$t('task.relation.differentList')">
{{ task.differentList }} >
v-if="task.differentProject !== null"
v-tooltip="$t('task.relation.differentProject')">
{{ task.differentProject }} >
</span>
</span>
{{ task.title }}
@ -98,8 +98,8 @@
:class="{ 'is-strikethrough': t.done}"
>
<span
class="different-list"
v-if="t.listId !== listId"
class="different-project"
v-if="t.projectId !== projectId"
>
<span
v-if="t.differentNamespace !== null"
@ -107,9 +107,9 @@
{{ t.differentNamespace }} >
</span>
<span
v-if="t.differentList !== null"
v-tooltip="$t('task.relation.differentList')">
{{ t.differentList }} >
v-if="t.differentProject !== null"
v-tooltip="$t('task.relation.differentProject')">
{{ t.differentProject }} >
</span>
</span>
{{ t.title }}
@ -186,7 +186,7 @@ const props = defineProps({
type: Boolean,
default: false,
},
listId: {
projectId: {
type: Number,
default: 0,
},
@ -230,17 +230,17 @@ async function findTasks(newQuery: string) {
foundTasks.value = await taskService.getAll({}, {s: newQuery})
}
const getListAndNamespaceById = (listId: number) => namespaceStore.getListAndNamespaceById(listId, true)
const getProjectAndNamespaceById = (projectId: number) => namespaceStore.getProjectAndNamespaceById(projectId, true)
const namespace = computed(() => getListAndNamespaceById(props.listId)?.namespace)
const namespace = computed(() => getProjectAndNamespaceById(props.projectId)?.namespace)
function mapRelatedTasks(tasks: ITask[]) {
return tasks.map(task => {
// by doing this here once we can save a lot of duplicate calls in the template
const {
list,
project,
namespace: taskNamespace,
} = getListAndNamespaceById(task.listId) || {list: null, namespace: null}
} = getProjectAndNamespaceById(task.projectId) || {project: null, namespace: null}
return {
...task,
@ -248,10 +248,10 @@ function mapRelatedTasks(tasks: ITask[]) {
(taskNamespace !== null &&
taskNamespace.id !== namespace.value.id &&
taskNamespace?.title) || null,
differentList:
(list !== null &&
task.listId !== props.listId &&
list?.title) || null,
differentProject:
(project !== null &&
task.projectId !== props.projectId &&
project?.title) || null,
}
})
}
@ -343,7 +343,7 @@ async function removeTaskRelation() {
}
async function createAndRelateTask(title: string) {
const newTask = await taskService.create(new TaskModel({title, listId: props.listId}))
const newTask = await taskService.create(new TaskModel({title, projectId: props.projectId}))
newTaskRelation.task = newTask
await addTaskRelation()
}
@ -351,7 +351,7 @@ async function createAndRelateTask(title: string) {
async function toggleTaskDone(task: ITask) {
await taskStore.update(task)
// Find the task in the list and update it so that it is correctly strike through
// Find the task in the project and update it so that it is correctly strike through
Object.entries(relatedTasks.value).some(([kind, tasks]) => {
return (tasks as ITask[]).some((t, key) => {
const found = t.id === task.id
@ -379,7 +379,7 @@ async function toggleTaskDone(task: ITask) {
}
}
.different-list {
.different-project {
color: var(--grey-500);
width: auto;
}

View File

@ -2,29 +2,29 @@
<div :class="{'is-loading': taskService.loading}" class="task loader-container">
<fancycheckbox
:disabled="(isArchived || disabled) && !canMarkAsDone"
@change="markAsDone"
@update:model-value="markAsDone"
v-model="task.done"
/>
<ColorBubble
v-if="showListColor && listColor !== '' && currentList.id !== task.listId"
:color="listColor"
v-if="showProjectColor && projectColor !== '' && currentProject.id !== task.projectId"
:color="projectColor"
class="mr-1"
/>
<router-link
:to="taskDetailRoute"
:class="{ 'done': task.done, 'show-list': showList && taskList !== null}"
:class="{ 'done': task.done, 'show-project': showProject && project !== null}"
class="tasktext"
>
<span>
<router-link
v-if="showList && taskList !== null"
:to="{ name: 'list.list', params: { listId: task.listId } }"
class="task-list"
v-if="showProject && project !== null"
:to="{ name: 'project.list', params: { projectId: task.projectId } }"
class="task-project"
:class="{'mr-2': task.hexColor !== ''}"
v-tooltip="$t('task.detail.belongsToList', {list: taskList.title})">
{{ taskList.title }}
v-tooltip="$t('task.detail.belongsToProject', {project: project.title})">
{{ project.title }}
</router-link>
<ColorBubble
@ -81,13 +81,13 @@
<priority-label :priority="task.priority" :done="task.done"/>
<span>
<span class="list-task-icon" v-if="task.attachments.length > 0">
<span class="project-task-icon" v-if="task.attachments.length > 0">
<icon icon="paperclip"/>
</span>
<span class="list-task-icon" v-if="task.description">
<span class="project-task-icon" v-if="task.description">
<icon icon="align-left"/>
</span>
<span class="list-task-icon" v-if="task.repeatAfter.amount > 0">
<span class="project-task-icon" v-if="task.repeatAfter.amount > 0">
<icon icon="history"/>
</span>
</span>
@ -104,12 +104,12 @@
</progress>
<router-link
v-if="!showList && currentList.id !== task.listId && taskList !== null"
:to="{ name: 'list.list', params: { listId: task.listId } }"
class="task-list"
v-tooltip="$t('task.detail.belongsToList', {list: taskList.title})"
v-if="!showProject && currentProject.id !== task.projectId && project !== null"
:to="{ name: 'project.list', params: { projectId: task.projectId } }"
class="task-project"
v-tooltip="$t('task.detail.belongsToProject', {project: project.title})"
>
{{ taskList.title }}
{{ project.title }}
</router-link>
<BaseButton
@ -148,7 +148,7 @@ import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {formatDateSince, formatISO, formatDateLong} from '@/helpers/time/formatDate'
import {success} from '@/message'
import {useListStore} from '@/stores/lists'
import {useProjectStore} from '@/stores/projects'
import {useNamespaceStore} from '@/stores/namespaces'
import {useBaseStore} from '@/stores/base'
import {useTaskStore} from '@/stores/tasks'
@ -162,7 +162,7 @@ const props = defineProps({
type: Boolean,
default: false,
},
showList: {
showProject: {
type: Boolean,
default: false,
},
@ -170,7 +170,7 @@ const props = defineProps({
type: Boolean,
default: false,
},
showListColor: {
showProjectColor: {
type: Boolean,
default: true,
},
@ -207,18 +207,18 @@ onBeforeUnmount(() => {
})
const baseStore = useBaseStore()
const listStore = useListStore()
const projectStore = useProjectStore()
const taskStore = useTaskStore()
const namespaceStore = useNamespaceStore()
const taskList = computed(() => listStore.getListById(task.value.listId))
const listColor = computed(() => taskList.value !== null ? taskList.value.hexColor : '')
const project = computed(() => projectStore.getProjectById(task.value.projectId))
const projectColor = computed(() => project.value !== null ? project.value.hexColor : '')
const currentList = computed(() => {
return typeof baseStore.currentList === 'undefined' ? {
const currentProject = computed(() => {
return typeof baseStore.currentProject === 'undefined' ? {
id: 0,
title: '',
} : baseStore.currentList
} : baseStore.currentProject
})
const taskDetailRoute = computed(() => ({
@ -239,7 +239,7 @@ async function markAsDone(checked: boolean) {
t('task.doneSuccess') :
t('task.undoneSuccess'),
}, [{
title: 'Undo',
title: t('task.undo'),
callback: () => undoDone(checked),
}])
}
@ -307,7 +307,7 @@ function hideDeferDueDatePopup(e) {
}
}
.task-list {
.task-project {
width: auto;
color: var(--grey-400);
font-size: .9rem;
@ -322,7 +322,7 @@ function hideDeferDueDatePopup(e) {
width: 27px;
}
.list-task-icon {
.project-task-icon {
margin-left: 6px;
&:not(:first-of-type) {
@ -341,7 +341,7 @@ function hideDeferDueDatePopup(e) {
}
.favorite {
opacity: 0;
opacity: 1;
text-align: center;
width: 27px;
transition: opacity $transition, color $transition;
@ -356,21 +356,26 @@ function hideDeferDueDatePopup(e) {
}
}
&:hover .favorite {
opacity: 1;
}
.handle {
opacity: 0;
opacity: 1;
transition: opacity $transition;
margin-right: .25rem;
cursor: grab;
}
&:hover .handle {
opacity: 1;
@media(hover: hover) and (pointer: fine) {
& .favorite,
& .handle {
opacity: 0;
}
&:hover .favorite,
&:hover .handle {
opacity: 1;
}
}
:deep(.fancycheckbox) {
height: 18px;
padding-top: 0;
@ -391,7 +396,7 @@ function hideDeferDueDatePopup(e) {
width: auto;
}
.show-list .parent-tasks {
.show-project .parent-tasks {
padding-left: .25rem;
}
@ -422,4 +427,4 @@ function hideDeferDueDatePopup(e) {
margin-bottom: 0;
}
}
</style>
</style>

View File

@ -1,6 +1,5 @@
import {computed} from 'vue'
import {computed, onActivated, ref} from 'vue'
import {useI18n} from 'vue-i18n'
import {useNow} from '@vueuse/core'
import {useAuthStore} from '@/stores/auth'
import {hourToDaytime} from '@/helpers/hourToDaytime'
@ -9,7 +8,8 @@ export type Daytime = 'night' | 'morning' | 'day' | 'evening'
export function useDaytimeSalutation() {
const {t} = useI18n({useScope: 'global'})
const now = useNow()
const now = ref(new Date())
onActivated(() => now.value = new Date())
const authStore = useAuthStore()
const name = computed(() => authStore.userDisplayName)

View File

@ -1,5 +1,6 @@
import {ref, watch, readonly} from 'vue'
import {useLocalStorage, useMediaQuery} from '@vueuse/core'
import {useRoute} from 'vue-router'
const BULMA_MOBILE_BREAKPOINT = 768
@ -15,7 +16,8 @@ export function useMenuActive() {
)
const menuActive = ref(false)
const route = useRoute()
// set to prefered value
watch(isMobile, (current) => {
menuActive.value = current
@ -31,6 +33,9 @@ export function useMenuActive() {
}
})
// Hide the menu on mobile when the route changes (e.g. when the user taps a menu item)
watch(() => route.fullPath, () => isMobile.value && setMenuActive(false))
function setMenuActive(newMenuActive: boolean) {
menuActive.value = newMenuActive
}
@ -44,4 +49,4 @@ export function useMenuActive() {
setMenuActive,
toggleMenu,
}
}
}

View File

@ -5,6 +5,22 @@ import TaskCollectionService from '@/services/taskCollection'
import type {ITask} from '@/modelTypes/ITask'
import {error} from '@/message'
export type Order = 'asc' | 'desc' | 'none'
export interface SortBy {
id?: Order
index?: Order
done?: Order
title?: Order
priority?: Order
due_date?: Order
start_date?: Order
end_date?: Order
percent_done?: Order
created?: Order
updated?: Order
}
// FIXME: merge with DEFAULT_PARAMS in filters.vue
export const getDefaultParams = () => ({
sort_by: ['position', 'id'],
@ -15,7 +31,7 @@ export const getDefaultParams = () => ({
filter_concat: 'and',
})
const SORT_BY_DEFAULT = {
const SORT_BY_DEFAULT: SortBy = {
id: 'desc',
}
@ -42,9 +58,9 @@ const SORT_BY_DEFAULT = {
}
/**
* This mixin provides a base set of methods and properties to get tasks on a list.
* This mixin provides a base set of methods and properties to get tasks.
*/
export function useTaskList(listId, sortByDefault = SORT_BY_DEFAULT) {
export function useTaskList(projectId, sortByDefault: SortBy = SORT_BY_DEFAULT) {
const params = ref({...getDefaultParams()})
const search = ref('')
@ -64,7 +80,7 @@ export function useTaskList(listId, sortByDefault = SORT_BY_DEFAULT) {
loadParams = formatSortOrder(sortBy.value, loadParams)
return [
{listId: listId.value},
{projectId: projectId.value},
loadParams,
page.value || 1,
]

View File

@ -6,13 +6,11 @@ declare global {
}
}
const cypressDirective: Directive = {
mounted(el, {value}) {
if (
(window.Cypress || import.meta.env.DEV) &&
value
) {
el.setAttribute('data-cy', value)
const cypressDirective = <Directive<HTMLElement,string>>{
mounted(el, {arg, value}) {
const testingId = arg || value
if ((window.Cypress || import.meta.env.DEV) && testingId) {
el.setAttribute('data-cy', testingId)
}
},
beforeUnmount(el) {

View File

@ -6,16 +6,16 @@ export const ERROR_NO_API_URL = 'noApiUrlProvided'
export const checkAndSetApiUrl = (url: string): Promise<string> => {
if(url.startsWith('/')) {
if (url.startsWith('/')) {
url = window.location.host + url
}
// Check if the url has an http prefix
// Check if the url has a http prefix
if (
!url.startsWith('http://') &&
!url.startsWith('https://')
) {
url = `http://${url}`
url = `${window.location.protocol}//${url}`
}
const urlToCheck: URL = new URL(url)
@ -41,15 +41,6 @@ export const checkAndSetApiUrl = (url: string): Promise<string> => {
}
throw e
})
.catch(e => {
// Check if it has a port and if not check if it is reachable at https
if (urlToCheck.protocol === 'http:') {
urlToCheck.protocol = 'https:'
window.API_URL = urlToCheck.toString()
return updateConfig()
}
throw e
})
.catch(e => {
// Check if it is reachable at /api/v1 and https
urlToCheck.pathname = origUrlToCheck.pathname
@ -66,7 +57,6 @@ export const checkAndSetApiUrl = (url: string): Promise<string> => {
.catch(e => {
// Check if it is reachable at port API_DEFAULT_PORT and https
if (urlToCheck.port !== API_DEFAULT_PORT) {
urlToCheck.protocol = 'https:'
urlToCheck.port = API_DEFAULT_PORT
window.API_URL = urlToCheck.toString()
return updateConfig()
@ -74,30 +64,7 @@ export const checkAndSetApiUrl = (url: string): Promise<string> => {
throw e
})
.catch(e => {
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and https
urlToCheck.pathname = origUrlToCheck.pathname
if (
!urlToCheck.pathname.endsWith('/api/v1') &&
!urlToCheck.pathname.endsWith('/api/v1/')
) {
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
window.API_URL = urlToCheck.toString()
return updateConfig()
}
throw e
})
.catch(e => {
// Check if it is reachable at port API_DEFAULT_PORT and http
if (urlToCheck.port !== API_DEFAULT_PORT) {
urlToCheck.protocol = 'http:'
urlToCheck.port = API_DEFAULT_PORT
window.API_URL = urlToCheck.toString()
return updateConfig()
}
throw e
})
.catch(e => {
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and http
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1
urlToCheck.pathname = origUrlToCheck.pathname
if (
!urlToCheck.pathname.endsWith('/api/v1') &&
@ -113,12 +80,12 @@ export const checkAndSetApiUrl = (url: string): Promise<string> => {
window.API_URL = oldUrl
throw e
})
.then(r => {
if (typeof r !== 'undefined') {
.then(success => {
if (success) {
localStorage.setItem('API_URL', window.API_URL)
return window.API_URL
}
throw new Error(ERROR_NO_API_URL)
})
}

View File

@ -1,9 +0,0 @@
import {i18n} from '@/i18n'
import type {IList} from '@/modelTypes/IList'
export function getListTitle(l: IList) {
if (l.id === -1) {
return i18n.global.t('list.pseudo.favorites.title')
}
return l.title
}

View File

@ -3,7 +3,7 @@ import type {INamespace} from '@/modelTypes/INamespace'
export const getNamespaceTitle = (n: INamespace) => {
if (n.id === -1) {
return i18n.global.t('namespace.pseudo.sharedLists.title')
return i18n.global.t('namespace.pseudo.sharedProjects.title')
}
if (n.id === -2) {
return i18n.global.t('namespace.pseudo.favorites.title')

View File

@ -0,0 +1,9 @@
import {i18n} from '@/i18n'
import type {IProject} from '@/modelTypes/IProject'
export function getProjectTitle(l: IProject) {
if (l.id === -1) {
return i18n.global.t('project.pseudo.favorites.title')
}
return l.title
}

View File

@ -113,8 +113,8 @@ task two`)
sub task 2`)
expect(tasks).to.have.length(3)
expect(tasks[0].list).to.eq('list')
expect(tasks[1].list).to.eq('list')
expect(tasks[2].list).to.eq('list')
expect(tasks[0].project).to.eq('list')
expect(tasks[1].project).to.eq('list')
expect(tasks[2].project).to.eq('list')
})
})

View File

@ -1,9 +1,9 @@
import {getListFromPrefix} from '@/modules/parseTaskText'
import {getProjectFromPrefix} from '@/modules/parseTaskText'
export interface TaskWithParent {
title: string,
parent: string | null,
list: string | null,
project: string | null,
}
function cleanupTitle(title: string) {
@ -23,10 +23,10 @@ export function parseSubtasksViaIndention(taskTitles: string): TaskWithParent[]
const task: TaskWithParent = {
title: cleanupTitle(title),
parent: null,
list: null,
project: null,
}
task.list = getListFromPrefix(task.title)
task.project = getProjectFromPrefix(task.title)
if (index === 0) {
return task
@ -47,9 +47,9 @@ export function parseSubtasksViaIndention(taskTitles: string): TaskWithParent[]
} while (parentSpaces >= matchedSpaces)
task.title = cleanupTitle(title.replace(spaceRegex, ''))
task.parent = task.parent.replace(spaceRegex, '')
if (task.list === null) {
// This allows to specify a list once for the parent task and inherit it to all subtasks
task.list = getListFromPrefix(task.parent)
if (task.project === null) {
// This allows to specify a project once for the parent task and inherit it to all subtasks
task.project = getProjectFromPrefix(task.parent)
}
}

100
src/helpers/projectView.ts Normal file
View File

@ -0,0 +1,100 @@
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>
>
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.')) {
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
if (savedProjectView !== null) {
savedProjectViewSettings = JSON.parse(savedProjectView) as ProjectViewSettings
}
let projectViewSettings: ProjectViewSettings = {}
if (savedProjectViewSettings) {
projectViewSettings = savedProjectViewSettings
}
projectViewSettings[projectId] = routeName
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
}
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
}
}

View File

@ -1,5 +1,5 @@
import type {IBucket} from '@/modelTypes/IBucket'
import type {IList} from '@/modelTypes/IList'
import type {IProject} from '@/modelTypes/IProject'
const key = 'collapsedBuckets'
@ -13,22 +13,22 @@ function getAllState() {
}
export const saveCollapsedBucketState = (
listId: IList['id'],
projectId: IProject['id'],
collapsedBuckets: CollapsedBuckets,
) => {
const state = getAllState()
state[listId] = collapsedBuckets
for (const bucketId in state[listId]) {
if (!state[listId][bucketId]) {
delete state[listId][bucketId]
state[projectId] = collapsedBuckets
for (const bucketId in state[projectId]) {
if (!state[projectId][bucketId]) {
delete state[projectId][bucketId]
}
}
localStorage.setItem(key, JSON.stringify(state))
}
export function getCollapsedBucketState(listId : IList['id']) {
export function getCollapsedBucketState(projectId : IProject['id']) {
const state = getAllState()
return typeof state[listId] !== 'undefined'
? state[listId]
return typeof state[projectId] !== 'undefined'
? state[projectId]
: {}
}

View File

@ -1,53 +0,0 @@
import type { IList } from '@/modelTypes/IList'
type ListView = Record<IList['id'], string>
const DEFAULT_LIST_VIEW = 'list.list' as const
/**
* Save the current list view to local storage
*/
export function saveListView(listId: IList['id'], routeName: string) {
if (routeName.includes('settings.')) {
return
}
if (!listId) {
return
}
// We use local storage and not the store here to make it persistent across reloads.
const savedListView = localStorage.getItem('listView')
let savedListViewJson: ListView | false = false
if (savedListView !== null) {
savedListViewJson = JSON.parse(savedListView) as ListView
}
let listView: ListView = {}
if (savedListViewJson) {
listView = savedListViewJson
}
listView[listId] = routeName
localStorage.setItem('listView', JSON.stringify(listView))
}
export const getListView = (listId: IList['id']) => {
// Remove old stored settings
const savedListView = localStorage.getItem('listView')
if (savedListView !== null && savedListView.startsWith('list.')) {
localStorage.removeItem('listView')
}
if (!savedListView) {
return DEFAULT_LIST_VIEW
}
const savedListViewJson: ListView = JSON.parse(savedListView)
if (!savedListViewJson[listId]) {
return DEFAULT_LIST_VIEW
}
return savedListViewJson[listId]
}

View File

@ -1,4 +1,5 @@
import { defineSetupVue3 } from '@histoire/plugin-vue'
import './polyfills'
import {defineSetupVue3} from '@histoire/plugin-vue'
import {i18n} from './i18n'
// import './histoire.css' // Import global CSS
@ -6,18 +7,21 @@ import './styles/global.scss'
import {createPinia} from 'pinia'
import cypress from '@/directives/cypress'
import FontAwesomeIcon from '@/components/misc/Icon'
import XButton from '@/components/input/button.vue'
import Modal from '@/components/misc/modal.vue'
import Card from '@/components/misc/card.vue'
export const setupVue3 = defineSetupVue3(({ app }) => {
// Add Pinia store
const pinia = createPinia()
app.use(pinia)
app.use(i18n)
app.directive('cy', cypress)
app.component('icon', FontAwesomeIcon)
app.component('XButton', XButton)
app.component('modal', Modal)

View File

@ -15,7 +15,7 @@ export const SUPPORTED_LOCALES = {
'pt-PT': 'Português',
'zh-CN': 'Chinese',
'no-NO': 'Norsk Bokmål',
} as Record<string, string>
} as const
export type SupportedLocale = keyof typeof SUPPORTED_LOCALES
@ -23,12 +23,12 @@ export const DEFAULT_LANGUAGE: SupportedLocale= 'en'
export type ISOLanguage = string
// we load all messsages async
// we load all messages async
export const i18n = createI18n({
fallbackLocale: DEFAULT_LANGUAGE,
legacy: false,
messages: {
en: langEN,
[DEFAULT_LANGUAGE]: langEN,
} as Record<SupportedLocale, any>,
})
@ -54,16 +54,16 @@ export async function setLanguage(lang: SupportedLocale = getCurrentLanguage()):
}
export function getCurrentLanguage(): SupportedLocale {
const savedLanguage = localStorage.getItem('language')
const savedLanguage = localStorage.getItem('language') as SupportedLocale | null
if (savedLanguage !== null) {
return savedLanguage
}
const browserLanguage = navigator.language
const language: SupportedLocale | undefined = Object.keys(SUPPORTED_LOCALES).find(langKey => {
const language = Object.keys(SUPPORTED_LOCALES).find(langKey => {
return langKey === browserLanguage || langKey.startsWith(browserLanguage + '-')
})
}) as SupportedLocale | undefined
return language || DEFAULT_LANGUAGE
}

View File

@ -5,10 +5,10 @@
"welcomeDay": "Hi {username}!",
"welcomeEvening": "Good Evening {username}!",
"lastViewed": "Last viewed",
"list": {
"newText": "You can create a new list for your new tasks:",
"new": "New list",
"importText": "Or import your lists and tasks from other services into Vikunja:",
"project": {
"newText": "You can create a new project for your new tasks:",
"new": "New project",
"importText": "Or import your projects and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
@ -85,7 +85,7 @@
"weekStartSunday": "Sunday",
"weekStartMonday": "Monday",
"language": "Language",
"defaultList": "Default List",
"defaultProject": "Default Project",
"timezone": "Time Zone",
"overdueTasksRemindersTime": "Overdue tasks reminder email time"
},
@ -143,7 +143,7 @@
},
"deletion": {
"title": "Delete your Vikunja Account",
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, lists, tasks and everything associated with it.",
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, projects, tasks and everything associated with it.",
"text2": "To proceed, please enter your password. You will receive an email with further instructions.",
"confirm": "Delete my account",
"requestSuccess": "The request was successful. You'll receive an email with further instructions.",
@ -157,47 +157,39 @@
},
"export": {
"title": "Export your Vikunja data",
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Lists, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Projects, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
"descriptionPasswordRequired": "Please enter your password to proceed:",
"request": "Request a copy of my Vikunja Data",
"success": "You've successfully requested your Vikunja Data! We will send you an email once it's ready to download.",
"downloadTitle": "Download your exported Vikunja data"
}
},
"list": {
"archived": "This list is archived. It is not possible to create new or edit tasks for it.",
"title": "List Title",
"project": {
"archived": "This project is archived. It is not possible to create new or edit tasks for it.",
"title": "Project Title",
"color": "Color",
"lists": "Lists",
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists",
"noDescriptionAvailable": "No list description is available.",
"projects": "Projects",
"search": "Type to search for a project…",
"searchSelect": "Click or press enter to select this project",
"shared": "Shared Projects",
"noDescriptionAvailable": "No project description is available.",
"create": {
"header": "New list",
"titlePlaceholder": "The list's title goes here…",
"header": "New project",
"titlePlaceholder": "The project's title goes here…",
"addTitleRequired": "Please specify a title.",
"createdSuccess": "The list was successfully created.",
"addListRequired": "Please specify a list or set a default list in the settings."
"createdSuccess": "The project was successfully created.",
"addProjectRequired": "Please specify a project or set a default project in the settings."
},
"archive": {
"title": "Archive \"{list}\"",
"archive": "Archive this list",
"unarchive": "Un-Archive this list",
"title": "Archive \"{project}\"",
"archive": "Archive this project",
"unarchive": "Un-Archive this project",
"unarchiveText": "You will be able to create new tasks or edit it.",
"archiveText": "You won't be able to edit this list or create new tasks until you un-archive it.",
"success": "The list was successfully archived."
"archiveText": "You won't be able to edit this project or create new tasks until you un-archive it.",
"success": "The project was successfully archived."
},
"background": {
"title": "Set list background",
"title": "Set project background",
"remove": "Remove Background",
"upload": "Choose a background from your pc",
"searchPlaceholder": "Search for a background…",
@ -207,40 +199,40 @@
"removeSuccess": "The background has been removed successfully!"
},
"delete": {
"title": "Delete \"{list}\"",
"header": "Delete this list",
"text1": "Are you sure you want to delete this list and all of its contents?",
"title": "Delete \"{project}\"",
"header": "Delete this project",
"text1": "Are you sure you want to delete this project and all of its contents?",
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The list was successfully deleted.",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
"noTasksToDelete": "This list does not contain any tasks, it should be safe to delete."
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
"title": "Duplicate this list",
"title": "Duplicate this project",
"label": "Duplicate",
"text": "Select a namespace which should hold the duplicated list:",
"success": "The list was successfully duplicated."
"text": "Select a namespace which should hold the duplicated project:",
"success": "The project was successfully duplicated."
},
"edit": {
"header": "Edit This List",
"title": "Edit \"{list}\"",
"titlePlaceholder": "The list title goes here…",
"identifierTooltip": "The list identifier can be used to uniquely identify a task across lists. You can set it to empty to disable it.",
"identifier": "List Identifier",
"identifierPlaceholder": "The list identifier goes here…",
"header": "Edit This Project",
"title": "Edit \"{project}\"",
"titlePlaceholder": "The project title goes here…",
"identifierTooltip": "The project identifier can be used to uniquely identify a task across projects. You can set it to empty to disable it.",
"identifier": "Project Identifier",
"identifierPlaceholder": "The project identifier goes here…",
"description": "Description",
"descriptionPlaceholder": "The lists description goes here…",
"descriptionPlaceholder": "The projects description goes here…",
"color": "Color",
"success": "The list was successfully updated."
"success": "The project was successfully updated."
},
"share": {
"header": "Share this list",
"title": "Share \"{list}\"",
"header": "Share this project",
"title": "Share \"{project}\"",
"share": "Share",
"links": {
"title": "Share Links",
"what": "What is a share link?",
"explanation": "Share Links allow you to easily share a list with other users who don't have an account on Vikunja.",
"explanation": "Share Links allow you to easily share a project with other users who don't have an account on Vikunja.",
"create": "Create a new link share",
"name": "Name (optional)",
"namePlaceholder": "e.g. Lorem Ipsum",
@ -249,7 +241,7 @@
"passwordExplanation": "When authenticating, the user will be required to enter this password.",
"noName": "No name set",
"remove": "Remove a link share",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this list with this link share. This cannot be undone!",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this project with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted",
"view": "View",
@ -278,6 +270,14 @@
"delete": "Delete"
}
},
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This project is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": {
"title": "Gantt",
"showTasksWithoutDates": "Show tasks which don't have dates set",
@ -322,36 +322,36 @@
}
},
"namespace": {
"title": "Namespaces & Lists",
"title": "Namespaces & Projects",
"namespace": "Namespace",
"showArchived": "Show Archived",
"noneAvailable": "You don't have any namespaces right now.",
"unarchive": "Un-Archive",
"archived": "Archived",
"noLists": "This namespace does not contain any lists.",
"createList": "Create a new list in this namespace.",
"noProjects": "This namespace does not contain any projects.",
"createProject": "Create a new project in this namespace.",
"namespaces": "Namespaces",
"search": "Type to search for a namespace…",
"create": {
"title": "New namespace",
"titleRequired": "Please specify a title.",
"explanation": "A namespace is a collection of lists you can share and use to organize your lists with. In fact, every list belongs to a namespace.",
"explanation": "A namespace is a collection of projects you can share and use to organize your projects with. In fact, every project belongs to a namespace.",
"tooltip": "What's a namespace?",
"success": "The namespace was successfully created."
},
"archive": {
"titleArchive": "Archive \"{namespace}\"",
"titleUnarchive": "Un-Archive \"{namespace}\"",
"archiveText": "You won't be able to edit this namespace or create new lists until you un-archive it. This will also archive all lists in this namespace.",
"unarchiveText": "You will be able to create new lists or edit it.",
"archiveText": "You won't be able to edit this namespace or create new projects until you un-archive it. This will also archive all projects in this namespace.",
"unarchiveText": "You will be able to create new projects or edit it.",
"success": "The namespace was successfully archived.",
"unarchiveSuccess": "The namespace was successfully un-archived.",
"description": "If a namespace is archived, you cannot create new lists or edit it."
"description": "If a namespace is archived, you cannot create new projects or edit it."
},
"delete": {
"title": "Delete \"{namespace}\"",
"text1": "Are you sure you want to delete this namespace and all of its contents?",
"text2": "This includes all lists and tasks and CANNOT BE UNDONE!",
"text2": "This includes all projects and tasks and CANNOT BE UNDONE!",
"success": "The namespace was successfully deleted."
},
"edit": {
@ -371,8 +371,8 @@
"isArchived": "This namespace is archived"
},
"pseudo": {
"sharedLists": {
"title": "Shared Lists"
"sharedProjects": {
"title": "Shared Projects"
},
"favorites": {
"title": "Favorites"
@ -403,8 +403,9 @@
},
"create": {
"title": "New Saved Filter",
"description": "A saved filter is a virtual list which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
"action": "Create new saved filter"
"description": "A saved filter is a virtual project which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
"action": "Create new saved filter",
"titleRequired": "Please provide a title for the filter."
},
"delete": {
"header": "Delete this saved filter",
@ -434,7 +435,7 @@
"label": {
"title": "Labels",
"manage": "Manage labels",
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose list you have access.",
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose project you have access.",
"newCTA": "You currently do not have any labels.",
"search": "Type to search for a label…",
"create": {
@ -459,7 +460,7 @@
},
"sharing": {
"authenticating": "Authenticating…",
"passwordRequired": "This shared list requires a password. Please enter it below:",
"passwordRequired": "This shared project requires a password. Please enter it below:",
"error": "An error occured.",
"invalidPassword": "The password is invalid."
},
@ -601,6 +602,7 @@
"addReminder": "Add a new reminder…",
"doneSuccess": "The task was successfully marked as done.",
"undoneSuccess": "The task was successfully un-marked as done.",
"undo": "Undo",
"openDetail": "Open task detail view",
"checklistTotal": "{checked} of {total} tasks",
"checklistAllDone": "{total} tasks",
@ -617,7 +619,7 @@
"chooseDueDate": "Click here to set a due date",
"chooseStartDate": "Click here to set a start date",
"chooseEndDate": "Click here to set an end date",
"move": "Move task to a different list",
"move": "Move task to a different project",
"done": "Mark task done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
@ -625,7 +627,7 @@
"doneAt": "Done {0}",
"updateSuccess": "The task was saved successfully.",
"deleteSuccess": "The task has been deleted successfully.",
"belongsToList": "This task belongs to list '{list}'",
"belongsToProject": "This task belongs to project '{project}'",
"due": "Due {at}",
"closePopup": "Close popup",
"delete": {
@ -645,7 +647,7 @@
"percentDone": "Set Progress",
"attachments": "Add Attachments",
"relatedTasks": "Add Relation",
"moveList": "Move",
"moveProject": "Move",
"color": "Set Color",
"delete": "Delete",
"favorite": "Add to Favorites",
@ -672,21 +674,21 @@
"updated": "Updated"
},
"subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
"subscribedProjectThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this project through its namespace.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
"subscribedTaskThroughParentProject": "You can't unsubscribe here because you are subscribed to this task through its project.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedProject": "You are currently subscribed to this project and will receive notifications for changes.",
"notSubscribedProject": "You are not subscribed to this project and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Subscribe",
"unsubscribe": "Unsubscribe",
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessProject": "You are now subscribed to this project",
"unsubscribeSuccessProject": "You are now unsubscribed to this project",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
},
@ -760,7 +762,7 @@
"new": "New Task Relation",
"searchPlaceholder": "Type search for a new task to add as related…",
"createPlaceholder": "Add this as new related task",
"differentList": "This task belongs to a different list.",
"differentProject": "This task belongs to a different project.",
"differentNamespace": "This task belongs to a different namespace.",
"noneYet": "No task relations yet.",
"delete": "Delete Task Relation",
@ -810,10 +812,10 @@
"priority1": "To set a task's priority, add a number 1-5, prefixed with a {prefix}.",
"priority2": "The higher the number, the higher the priority.",
"assignees": "To directly assign the task to a user, add their username prefixed with {prefix} to the task.",
"list1": "To set a list for the task to appear in, enter its name prefixed with {prefix}.",
"list2": "This will return an error if the list does not exist.",
"list3": "To use spaces, simply add a \" or ' around the list name.",
"list4": "For example: {prefix}\"List with spaces\".",
"project1": "To set a project for the task to appear in, enter its name prefixed with {prefix}.",
"project2": "This will return an error if the project does not exist.",
"project3": "To use spaces, simply add a \" or ' around the project name.",
"project4": "For example: {prefix}\"Project with spaces\".",
"dateAndTime": "Date and time",
"date": "Any date will be used as the due date of the new task. You can use dates in any of these formats:",
"dateWeekday": "any weekday, will use the next date with that date",
@ -846,19 +848,19 @@
"delete": {
"header": "Delete the team",
"text1": "Are you sure you want to delete this team and all of its members?",
"text2": "All team members will lose access to lists and namespaces shared with this team. This CANNOT BE UNDONE!",
"text2": "All team members will lose access to projects and namespaces shared with this team. This CANNOT BE UNDONE!",
"success": "The team was successfully deleted."
},
"deleteUser": {
"header": "Remove a user from the team",
"text1": "Are you sure you want to remove this user from the team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"text2": "They will lose access to all projects and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will lose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"text2": "You will lose access to all projects and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
}
},
@ -890,12 +892,12 @@
"attachment": "Add an attachment to this task",
"related": "Modify related tasks of this task",
"color": "Change the color of this task",
"move": "Move this task to another list",
"move": "Move this task to another project",
"reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
},
"list": {
"title": "List Views",
"project": {
"title": "Project Views",
"switchToListView": "Switch to list view",
"switchToGanttView": "Switch to gantt view",
"switchToKanbanView": "Switch to kanban view",
@ -905,7 +907,7 @@
"title": "Navigation",
"overview": "Navigate to overview",
"upcoming": "Navigate to upcoming tasks",
"namespaces": "Navigate to namespaces & lists",
"namespaces": "Navigate to namespaces & projects",
"labels": "Navigate to labels",
"teams": "Navigate to teams"
}
@ -922,7 +924,7 @@
"unarchive": "Un-Archive",
"setBackground": "Set background",
"share": "Share",
"newList": "New list"
"newProject": "New project"
},
"apiConfig": {
"url": "Vikunja URL",
@ -941,24 +943,24 @@
"notification": {
"title": "Notifications",
"none": "You don't have any notifications. Have a nice day!",
"explainer": "Notifications will appear here when actions on namespaces, lists or tasks you subscribed to happen."
"explainer": "Notifications will appear here when actions on namespaces, projects or tasks you subscribed to happen."
},
"quickActions": {
"commands": "Commands",
"placeholder": "Type a command or search…",
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
"hint": "You can use {project} to limit the search to a project. Combine {project} or {label} (labels) with a search query to search for a task with these labels or on that project. Use {assignee} to only search for teams.",
"tasks": "Tasks",
"lists": "Lists",
"projects": "Projects",
"teams": "Teams",
"newList": "Enter the title of the new list…",
"newProject": "Enter the title of the new project…",
"newTask": "Enter the title of the new task…",
"newNamespace": "Enter the title of the new namespace…",
"newTeam": "Enter the name of the new team…",
"createTask": "Create a task in the current list ({title})",
"createList": "Create a list in the current namespace ({title})",
"createTask": "Create a task in the current project ({title})",
"createProject": "Create a project in the current namespace ({title})",
"cmds": {
"newTask": "New task",
"newList": "New list",
"newProject": "New project",
"newNamespace": "New namespace",
"newTeam": "New team"
}
@ -990,15 +992,15 @@
"1018": "The user avatar type setting is invalid.",
"2001": "ID cannot be empty or 0.",
"2002": "Some of the request data was invalid.",
"3001": "The list does not exist.",
"3004": "You need to have read permissions on that list to perform that action.",
"3005": "The list title cannot be empty.",
"3006": "The list share does not exist.",
"3007": "A list with this identifier already exists.",
"3008": "The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list.",
"4001": "The list task text cannot be empty.",
"4002": "The list task does not exist.",
"4003": "All bulk editing tasks must belong to the same list.",
"3001": "The project does not exist.",
"3004": "You need to have read permissions on that project to perform that action.",
"3005": "The project title cannot be empty.",
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Need at least one task when bulk editing tasks.",
"4005": "You do not have the right to see the task.",
"4006": "You can't set a parent task as the task itself.",
@ -1024,21 +1026,21 @@
"5012": "The namespace is archived and can therefore only be accessed read only.",
"6001": "The team name cannot be empty.",
"6002": "The team does not exist.",
"6004": "The team already has access to that namespace or list.",
"6004": "The team already has access to that namespace or project.",
"6005": "The user is already a member of that team.",
"6006": "Cannot delete the last team member.",
"6007": "The team does not have access to the list to perform that action.",
"7002": "The user already has access to that list.",
"7003": "You do not have access to that list.",
"6007": "The team does not have access to the project to perform that action.",
"7002": "The user already has access to that project.",
"7003": "You do not have access to that project.",
"8001": "This label already exists on that task.",
"8002": "The label does not exist.",
"8003": "You do not have access to this label.",
"9001": "The right is invalid.",
"10001": "The bucket does not exist.",
"10002": "The bucket does not belong to that list.",
"10003": "You cannot remove the last bucket on a list.",
"10002": "The bucket does not belong to that project.",
"10003": "You cannot remove the last bucket on a project.",
"10004": "You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold.",
"10005": "There can be only one done bucket per list.",
"10005": "There can be only one done bucket per project.",
"11001": "The saved filter does not exist.",
"11002": "Saved filters are not available for link shares.",
"12001": "The subscription entity type is invalid.",

View File

@ -5,11 +5,11 @@
"welcomeDay": "Ahoj {username}!",
"welcomeEvening": "Dobrý večer {username}!",
"lastViewed": "Naposledy zobrazeno",
"list": {
"newText": "Můžete vytvořit nový seznam pro své nové úkoly:",
"new": "Nový seznam",
"importText": "Nebo importujte své seznamy a úkoly z jiných služeb:",
"import": "Importujte svá data do Vikunja"
"project": {
"newText": "You can create a new project for your new tasks:",
"new": "New project",
"importText": "Or import your projects and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
"404": {
@ -85,7 +85,7 @@
"weekStartSunday": "Neděle",
"weekStartMonday": "Pondělí",
"language": "Jazyk",
"defaultList": "Výchozí seznam",
"defaultProject": "Default Project",
"timezone": "Časové pásmo",
"overdueTasksRemindersTime": "Čas odeslání emailu o zpožděných úkolech"
},
@ -143,7 +143,7 @@
},
"deletion": {
"title": "Smazat svůj účet",
"text1": "Odstranění vašeho účtu je trvalé a nelze ho vrátit zpět. Vymažeme všechny vaše prostory, seznamy, úkoly a vše, co je s ním spojeno.",
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, projects, tasks and everything associated with it.",
"text2": "Chcete-li pokračovat, zadejte své heslo. Obdržíte e-mail s dalšími pokyny.",
"confirm": "Smazat můj účet",
"requestSuccess": "Požadavek byl úspěšný. Obdržíte e-mail s dalšími pokyny.",
@ -157,201 +157,201 @@
},
"export": {
"title": "Exportovat data účtu",
"description": "Můžete si vyžádat kopii všech vašich dat. To zahrnuje prostory, seznamy, úkoly a vše, co je s nimi spojeno. Tato data můžete importovat v libovolné instanci Vikunja prostřednictvím migrační funkce.",
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Projects, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
"descriptionPasswordRequired": "Pokračujte zadáním vašeho hesla:",
"request": "Požádat o kopii mých dat",
"success": "Úspěšně jste požádali o svá data! Jakmile budou připravena ke stažení, pošleme Vám e-mail.",
"downloadTitle": "Stáhnout exportovaná Vikunja data"
}
},
"list": {
"archived": "Tento seznam je archivován. Není možné vytvářet nebo upravovat jeho úkoly.",
"title": "Název seznamu",
"color": "Barva",
"lists": "Seznamy",
"list": {
"title": "Seznam",
"add": "Přidat",
"addPlaceholder": "Přidat nový úkol…",
"empty": "Tento seznam je nyní prázdný.",
"newTaskCta": "Vytvořit nový úkol.",
"editTask": "Upravit úkol"
},
"search": "Začni psát pro vyhledání seznamu…",
"searchSelect": "Klikněte nebo stiskněte Enter pro výběr tohoto seznamu",
"shared": "Sdílené seznamy",
"noDescriptionAvailable": "Popis seznamu není k dispozici.",
"project": {
"archived": "This project is archived. It is not possible to create new or edit tasks for it.",
"title": "Project Title",
"color": "Color",
"projects": "Projects",
"search": "Type to search for a project…",
"searchSelect": "Click or press enter to select this project",
"shared": "Shared Projects",
"noDescriptionAvailable": "No project description is available.",
"create": {
"header": "Nový seznam",
"titlePlaceholder": "Název seznamu přijde sem…",
"addTitleRequired": "Uveďte prosím název.",
"createdSuccess": "Seznam byl úspěšně vytvořen.",
"addListRequired": "Vyberte seznam nebo nastavte výchozí seznam v nastavení."
"header": "New project",
"titlePlaceholder": "The project's title goes here…",
"addTitleRequired": "Please specify a title.",
"createdSuccess": "The project was successfully created.",
"addProjectRequired": "Please specify a project or set a default project in the settings."
},
"archive": {
"title": "Archivovat \"{list}\"",
"archive": "Archivovat tento seznam",
"unarchive": "Odarchivovat tento seznam",
"unarchiveText": "Budete moci vytvářet nové úkoly nebo je upravovat.",
"archiveText": "Tento seznam nebude možné upravovat ani vytvářet nové úkoly, dokud jej neodarchivujete.",
"success": "Seznam byl úspěšně archivován."
"title": "Archive \"{project}\"",
"archive": "Archive this project",
"unarchive": "Un-Archive this project",
"unarchiveText": "You will be able to create new tasks or edit it.",
"archiveText": "You won't be able to edit this project or create new tasks until you un-archive it.",
"success": "The project was successfully archived."
},
"background": {
"title": "Nastavit pozadí seznamu",
"remove": "Odstranit pozadí",
"upload": "Vyberte si pozadí ze svého počítače",
"searchPlaceholder": "Hledat pozadí…",
"poweredByUnsplash": "Běží na Unsplash",
"loadMore": "Načíst více fotek",
"success": "Pozadí bylo úspěšně nastaveno!",
"removeSuccess": "Pozadí bylo úspěšně odebráno!"
"title": "Set project background",
"remove": "Remove Background",
"upload": "Choose a background from your pc",
"searchPlaceholder": "Search for a background…",
"poweredByUnsplash": "Powered by Unsplash",
"loadMore": "Load more photos",
"success": "The background has been set successfully!",
"removeSuccess": "The background has been removed successfully!"
},
"delete": {
"title": "Smazat \"{list}\"",
"header": "Smazat tento seznam",
"text1": "Opravdu chcete odstranit tento seznam a jeho obsah?",
"text2": "To zahrnuje všechny úkoly a NEMŮŽE BÝT VRÁCENO ZPĚT!",
"success": "Seznam byl úspěšně smazán.",
"tasksToDelete": "Navždy smazat asi {count} úkolů.",
"noTasksToDelete": "Tento seznam neobsahuje žádné úkoly, mělo by být bezpečné ho smazat."
"title": "Delete \"{project}\"",
"header": "Delete this project",
"text1": "Are you sure you want to delete this project and all of its contents?",
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
"title": "Duplikovat tento seznam",
"label": "Duplikovat",
"text": "Vyberte prostor, kam uložit duplicitní seznam:",
"success": "Seznam byl úspěšně duplikován."
"title": "Duplicate this project",
"label": "Duplicate",
"text": "Select a namespace which should hold the duplicated project:",
"success": "The project was successfully duplicated."
},
"edit": {
"header": "Upravit tento seznam",
"title": "Upravit \"{list}\"",
"titlePlaceholder": "Název seznamu přijde sem…",
"identifierTooltip": "Identifikátor seznamu může být použit k jedinečné identifikaci úkolu napříč seznamy. Nechte ho prázdný pro jeho vypnutí.",
"identifier": "Identifikátor seznamu",
"identifierPlaceholder": "Identifikátor seznamu přijde sem…",
"description": "Popis",
"descriptionPlaceholder": "Popis seznamu přijde sem…",
"color": "Barva",
"success": "Seznam byl úspěšně aktualizován."
"header": "Edit This Project",
"title": "Edit \"{project}\"",
"titlePlaceholder": "The project title goes here…",
"identifierTooltip": "The project identifier can be used to uniquely identify a task across projects. You can set it to empty to disable it.",
"identifier": "Project Identifier",
"identifierPlaceholder": "The project identifier goes here…",
"description": "Description",
"descriptionPlaceholder": "The projects description goes here…",
"color": "Color",
"success": "The project was successfully updated."
},
"share": {
"header": "Sdílet tento seznam",
"title": "Sdílet \"{list}\"",
"share": "Sdílet",
"header": "Share this project",
"title": "Share \"{project}\"",
"share": "Share",
"links": {
"title": "Sdílet odkazy",
"what": "Co je to sdílený odkaz?",
"explanation": "Sdílení odkazů vám umožňuje snadno sdílet seznam s ostatními uživateli, kteří nemají účet na Vikunja.",
"create": "Vytvořit sdílený odkaz",
"name": "Název (volitelný)",
"namePlaceholder": "např. Lorem Ipsum",
"nameExplanation": "Všechny akce provedené tímto odkazem se zobrazí s tímto názvem.",
"password": "Heslo (volitelné)",
"passwordExplanation": "Při autentizaci bude uživatel muset zadat toto heslo.",
"noName": "Není nastaven žádný název",
"remove": "Odstranit sdílený odkaz",
"removeText": "Jste si jisti, že chcete odstranit tento sdílený odkaz? K tomuto seznamu již nebude možné přistupovat s tímto sdíleným odkazem. Tuto akci nelze vrátit zpět!",
"createSuccess": "Sdílený odkaz byl úspěšně vytvořen.",
"deleteSuccess": "Sdílený odkaz byl úspěšně smazán",
"view": "Zobrazit",
"sharedBy": "Sdíleno {0}"
"title": "Share Links",
"what": "What is a share link?",
"explanation": "Share Links allow you to easily share a project with other users who don't have an account on Vikunja.",
"create": "Create a new link share",
"name": "Name (optional)",
"namePlaceholder": "e.g. Lorem Ipsum",
"nameExplanation": "All actions done by this link share will show up with the name.",
"password": "Password (optional)",
"passwordExplanation": "When authenticating, the user will be required to enter this password.",
"noName": "No name set",
"remove": "Remove a link share",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this project with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted",
"view": "View",
"sharedBy": "Shared by {0}"
},
"userTeam": {
"typeUser": "uživatel | uživatelé",
"typeTeam": "tým | týmy",
"shared": "Sdíleno s těmito {type}",
"you": "Ty",
"notShared": "Zatím není sdíleno s žádnou {type}.",
"removeHeader": "Odstranit {type} z {sharable}",
"removeText": "Jste si jisti, že chcete odstranit {sharable} z {type}.? Tuto akci nelze vrátit zpět!",
"removeSuccess": "{sharable} byl úspěšně odebrán z {type}.",
"addedSuccess": "{type} byl úspěšně přidán.",
"updatedSuccess": "{type} byl úspěšně přidán."
"typeUser": "user | users",
"typeTeam": "team | teams",
"shared": "Shared with these {type}",
"you": "You",
"notShared": "Not shared with any {type} yet.",
"removeHeader": "Remove a {type} from the {sharable}",
"removeText": "Are you sure you want to remove this {sharable} from the {type}? This cannot be undone!",
"removeSuccess": "The {sharable} was successfully removed from the {type}.",
"addedSuccess": "The {type} was successfully added.",
"updatedSuccess": "The {type} was successfully added."
},
"right": {
"title": "Oprávnění",
"read": "Pouze pro čtení",
"readWrite": "Čtení a zápis",
"admin": "Administrátor"
"title": "Permission",
"read": "Read only",
"readWrite": "Read & write",
"admin": "Admin"
},
"attributes": {
"link": "Odkaz",
"delete": "Smazat"
"link": "Link",
"delete": "Delete"
}
},
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This project is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": {
"title": "Gantt",
"showTasksWithoutDates": "Zobrazit úkoly, které nemají nastavené datum",
"size": "Velikost",
"default": "Výchozí",
"month": "Měsíc",
"day": "Den",
"hour": "Hodina",
"range": "Časové období",
"noDates": "Tento úkol nemá nastaveno žádné datum."
"showTasksWithoutDates": "Show tasks which don't have dates set",
"size": "Size",
"default": "Default",
"month": "Month",
"day": "Day",
"hour": "Hour",
"range": "Date Range",
"noDates": "This task has no dates set."
},
"table": {
"title": "Tabulka",
"columns": "Sloupce"
"title": "Table",
"columns": "Columns"
},
"kanban": {
"title": "Kanban",
"limit": "Limit: {limit}",
"noLimit": "Nenastaveno",
"doneBucket": "Sloupec \"Hotovo\"",
"doneBucketHint": "Všechny úkoly přesunuté do tohoto sloupce budou automaticky označeny jako dokončené.",
"doneBucketHintExtended": "Všechny úkoly přesunuté do sloupce \"Hotovo\" budou označeny jako dokončené automaticky. Všechny úkoly označené jako dokončené jinde sem budou přesunuty také.",
"doneBucketSavedSuccess": "Sloupec \"Hotovo\" byl úspěšně uložen.",
"deleteLast": "Nelze odstranit poslední sloupec.",
"addTaskPlaceholder": "Zadejte nový název úkolu…",
"addTask": "Přidat úkol",
"addAnotherTask": "Přidat další úkol",
"addBucket": "Vytvořit nový sloupec",
"addBucketPlaceholder": "Zadejte název nového sloupce…",
"deleteHeaderBucket": "Smazat sloupec",
"deleteBucketText1": "Opravdu chcete smazat tento sloupec?",
"deleteBucketText2": "Toto nesmaže žádné úkoly, ale přesune je do výchozího sloupce.",
"deleteBucketSuccess": "Sloupec byl úspěšně smazán.",
"bucketTitleSavedSuccess": "Název sloupce byl úspěšně uložen.",
"bucketLimitSavedSuccess": "Limit sloupce byl úspěšně uložen.",
"collapse": "Sbalit tento sloupec"
"noLimit": "Not Set",
"doneBucket": "Done bucket",
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.",
"doneBucketHintExtended": "All tasks moved into the done bucket will be marked as done automatically. All tasks marked as done from elsewhere will be moved as well.",
"doneBucketSavedSuccess": "The done bucket has been saved successfully.",
"deleteLast": "You cannot remove the last bucket.",
"addTaskPlaceholder": "Enter the new task title…",
"addTask": "Add a task",
"addAnotherTask": "Add another task",
"addBucket": "Create a new bucket",
"addBucketPlaceholder": "Enter the new bucket title…",
"deleteHeaderBucket": "Delete the bucket",
"deleteBucketText1": "Are you sure you want to delete this bucket?",
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.",
"deleteBucketSuccess": "The bucket has been deleted successfully.",
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.",
"bucketLimitSavedSuccess": "The bucket limit been saved successfully.",
"collapse": "Collapse this bucket"
},
"pseudo": {
"favorites": {
"title": "Oblíbené"
"title": "Favorites"
}
}
},
"namespace": {
"title": "Prostory a seznamy",
"title": "Namespaces & Projects",
"namespace": "Prostor",
"showArchived": "Zobrazit archivované",
"noneAvailable": "Momentálně nemáte žádné prostory.",
"unarchive": "Obnovit archiv",
"archived": "Archivováno",
"noLists": "Tento prostor neobsahuje žádné seznamy.",
"createList": "Vytvořit nový seznam v tomto prostoru.",
"noProjects": "This namespace does not contain any projects.",
"createProject": "Create a new project in this namespace.",
"namespaces": "Prostory",
"search": "Začni psát pro vyhledání prostoru…",
"create": {
"title": "Nový prostor",
"titleRequired": "Uveďte prosím název.",
"explanation": "Prostor je kolekce seznamů, které můžete sdílet a používat k organizaci seznamů. Každý seznam patří do nějakého prostoru.",
"explanation": "A namespace is a collection of projects you can share and use to organize your projects with. In fact, every project belongs to a namespace.",
"tooltip": "Co je prostor?",
"success": "Prostor byl úspěšně vytvořen."
},
"archive": {
"titleArchive": "Archivovat \"{namespace}\"",
"titleUnarchive": "Odarchivovat \"{namespace}\"",
"archiveText": "Nebudete moci upravovat tento jmenný prostor ani vytvářet nové seznamy, dokud jej neodarchivujete. Všechny seznamy v tomto prostoru budou také archivovány.",
"unarchiveText": "Budete moci vytvářet nové úkoly nebo je upravovat.",
"archiveText": "You won't be able to edit this namespace or create new projects until you un-archive it. This will also archive all projects in this namespace.",
"unarchiveText": "You will be able to create new projects or edit it.",
"success": "Prostor byl úspěšně archivován.",
"unarchiveSuccess": "Jmenný prostor byl úspěšně obnoven.",
"description": "Pokud je prostor archivován, nelze vytvořit nové seznamy nebo je upravit."
"description": "If a namespace is archived, you cannot create new projects or edit it."
},
"delete": {
"title": "Smazat \"{namespace}\"",
"text1": "Opravdu chcete odstranit tento prostor a všechen jeho obsah?",
"text2": "To zahrnuje všechny seznamy a úkoly a NEMŮŽE BÝT VRÁCENO ZPĚT!",
"text2": "This includes all projects and tasks and CANNOT BE UNDONE!",
"success": "Prostor byl úspěšně smazán."
},
"edit": {
@ -371,8 +371,8 @@
"isArchived": "Tento prostor je archivován"
},
"pseudo": {
"sharedLists": {
"title": "Sdílené seznamy"
"sharedProjects": {
"title": "Shared Projects"
},
"favorites": {
"title": "Oblíbené"
@ -403,8 +403,9 @@
},
"create": {
"title": "Nový uložený filtr",
"description": "Uložený filtr je virtuální seznam, který se počítá ze sady filtrů pokaždé, když je přístupný. Jakmile bude vytvořen, objeví se ve speciálním prostoru.",
"action": "Vytvořit uložený filtr"
"description": "A saved filter is a virtual project which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
"action": "Vytvořit uložený filtr",
"titleRequired": "Please provide a title for the filter."
},
"delete": {
"header": "Smazat tento uložený filtr",
@ -434,7 +435,7 @@
"label": {
"title": "Štítky",
"manage": "Spravovat štítky",
"description": "Klikněte na štítek pro jeho úpravu. Můžete upravit všechny štítky, které jste vytvořili, můžete použít všechny štítky, které jsou přiřazeny k úkolu, do kterého máte přístup.",
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose project you have access.",
"newCTA": "Momentálně nemáte žádné štítky.",
"search": "Zadejte hledaný štítek…",
"create": {
@ -459,7 +460,7 @@
},
"sharing": {
"authenticating": "Ověřování…",
"passwordRequired": "Tento sdílený seznam vyžaduje heslo. Zadejte jej níže:",
"passwordRequired": "This shared project requires a password. Please enter it below:",
"error": "Došlo k chybě.",
"invalidPassword": "Neplatné heslo."
},
@ -528,7 +529,7 @@
"code": "Kód",
"quote": "Citace",
"unorderedList": "Seznam s odrážkami",
"orderedList": "Číslovaný seznam",
"orderedList": "Ordered List",
"cleanBlock": "Čistý blok",
"link": "Odkaz",
"image": "Obrázek",
@ -601,6 +602,7 @@
"addReminder": "Přidat novou připomínku…",
"doneSuccess": "Úkol byl úspěšně označen jako dokončený.",
"undoneSuccess": "Úkol byl úspěšně znovu otevřen.",
"undo": "Undo",
"openDetail": "Otevřít zobrazení detailu úkolu",
"checklistTotal": "{checked} z {total} úkolů",
"checklistAllDone": "{total} úkolů",
@ -617,7 +619,7 @@
"chooseDueDate": "Klikněte zde pro nastavení termínu dokončení",
"chooseStartDate": "Klikněte zde pro nastavení počátečního data",
"chooseEndDate": "Klikněte zde pro nastavení data ukončení",
"move": "Přesunout úkol do jiného seznamu",
"move": "Move task to a different project",
"done": "Označit úkol jako hotový!",
"undone": "Označit jako znovu otevřené",
"created": "Vytvořeno {0} uživatelem {1}",
@ -625,7 +627,7 @@
"doneAt": "Dokončeno {0}",
"updateSuccess": "Úkol byl úspěšně uložen.",
"deleteSuccess": "Úkol byl úspěšně smazán.",
"belongsToList": "Tento úkol patří do seznamu '{list}'",
"belongsToProject": "This task belongs to project '{project}'",
"due": "Termín {at}",
"closePopup": "Zavřít vyskakovací okno",
"delete": {
@ -645,7 +647,7 @@
"percentDone": "Nastavit průběh",
"attachments": "Přidat přílohy",
"relatedTasks": "Přidat vztah",
"moveList": "Přesunout",
"moveProject": "Move",
"color": "Nastavit barvu",
"delete": "Smazat",
"favorite": "Přidat do oblíbených",
@ -672,21 +674,21 @@
"updated": "Aktualizováno"
},
"subscription": {
"subscribedListThroughParentNamespace": "Zde se nemůžete odhlásit, protože jste přihlášeni k odběru tohoto seznamu prostřednictvím jeho prostoru.",
"subscribedProjectThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this project through its namespace.",
"subscribedTaskThroughParentNamespace": "Zde se nemůžete odhlásit, protože jste přihlášeni k odběru tohoto úkolu prostřednictvím jeho prostoru.",
"subscribedTaskThroughParentList": "Zde se nemůžete odhlásit, protože jste přihlášeni k odběru tohoto úkolu prostřednictvím jeho seznamu.",
"subscribedTaskThroughParentProject": "You can't unsubscribe here because you are subscribed to this task through its project.",
"subscribedNamespace": "Nyní jste přihlášeni k odběru tohoto prostoru a budete dostávat oznámení o změnách.",
"notSubscribedNamespace": "Nejste přihlášeni k odběru tohoto prostoru, takže nebudete dostávat upozornění na změny.",
"subscribedList": "Nyní jste přihlášeni k odběru tohoto seznamu a budete dostávat oznámení o změnách.",
"notSubscribedList": "Nejste přihlášeni k odběru tohoto seznamu, takže nebudete dostávat upozornění na změny.",
"subscribedProject": "You are currently subscribed to this project and will receive notifications for changes.",
"notSubscribedProject": "You are not subscribed to this project and won't receive notifications for changes.",
"subscribedTask": "Nyní jste přihlášeni k odběru tohoto úkolu a budete dostávat oznámení o změnách.",
"notSubscribedTask": "Nejste přihlášeni k odběru tohoto úkolu, takže nebudete dostávat upozornění na změny.",
"subscribe": "Odebírat",
"unsubscribe": "Odhlásit odběr",
"subscribeSuccessNamespace": "Nyní jste přihlášeni k tomuto prostoru",
"unsubscribeSuccessNamespace": "Nyní jste odhlášeni od tohoto prostoru",
"subscribeSuccessList": "Nyní jste přihlášeni k tomuto seznamu",
"unsubscribeSuccessList": "Nyní jste odhlášeni od tohoto seznamu",
"subscribeSuccessProject": "You are now subscribed to this project",
"unsubscribeSuccessProject": "You are now unsubscribed to this project",
"subscribeSuccessTask": "Nyní jste přihlášeni k tomuto úkolu",
"unsubscribeSuccessTask": "Nyní jste odhlášeni od tohoto úkolu"
},
@ -760,7 +762,7 @@
"new": "Nový vztah k úkolu",
"searchPlaceholder": "Hledejte nový úkol, který chcete přidat jako související…",
"createPlaceholder": "Přidat toto jako nový související úkol",
"differentList": "Tento úkol patří do jiného seznamu.",
"differentProject": "This task belongs to a different project.",
"differentNamespace": "Tento úkol patří do jiného prostoru.",
"noneYet": "Zatím žádné vztahy mezi úkoly.",
"delete": "Odstranit vztah k úloze",
@ -810,10 +812,10 @@
"priority1": "Chcete-li nastavit prioritu úkolu, přidejte číslo 1-5, s prefixem {prefix}.",
"priority2": "Čím vyšší číslo, tím vyšší priorita.",
"assignees": "Chcete-li přímo přiřadit úkol k uživateli, přidejte k úkolu jejich uživatelské jméno s prefixem {prefix}.",
"list1": "Chcete-li nastavit seznam, ve kterém se má úkol zobrazit, zadejte jeho název s předponou {prefix}.",
"list2": "Toto vrátí chybu, pokud seznam neexistuje.",
"list3": "Chcete-li použít mezery, uzavřete název seznamu do \" nebo '.",
"list4": "Například: {prefix}\"Štítek s mezerami\".",
"project1": "To set a project for the task to appear in, enter its name prefixed with {prefix}.",
"project2": "This will return an error if the project does not exist.",
"project3": "To use spaces, simply add a \" or ' around the project name.",
"project4": "For example: {prefix}\"Project with spaces\".",
"dateAndTime": "Datum a čas",
"date": "Jakékoliv datum bude použito jako datum dokončení nového úkolu. Můžete použít data v kterémkoli z těchto formátů:",
"dateWeekday": "každý pracovní den použije další datum s tímto datem",
@ -846,19 +848,19 @@
"delete": {
"header": "Smazat tým",
"text1": "Jste si jisti, že chcete smazat tento tým a všechny jeho členy?",
"text2": "Všichni členové týmu ztratí přístup k seznamům a prostorům, které jsou sdíleny s tímto týmem. Toto NEMŮŽE BÝT VZATO ZPĚT!",
"text2": "All team members will lose access to projects and namespaces shared with this team. This CANNOT BE UNDONE!",
"success": "Tým byl úspěšně smazán."
},
"deleteUser": {
"header": "Odebrat uživatele z týmu",
"text1": "Opravdu chcete odebrat tohoto uživatele z týmu?",
"text2": "Ztratí přístup ke všem seznamům a prostorům, k nimž má tento tým přístup. To NEMŮŽE BÝT VZATO ZPĚT!",
"text2": "They will lose access to all projects and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "Uživatel byl úspěšně odstraněn z týmu."
},
"leave": {
"title": "Opustit tým",
"text1": "Opravdu chcete opustit tento tým?",
"text2": "Ztratíte přístup ke všem seznamům a prostorům, k nimž má tento tým přístup. Pokud změníte názor, budete potřebovat správce týmu, aby vás znovu přidal.",
"text2": "You will lose access to all projects and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "Úspěšně jste opustili tým."
}
},
@ -890,22 +892,22 @@
"attachment": "Přidat přílohu k tomuto úkolu",
"related": "Upravit související úkoly tohoto úkolu",
"color": "Změnit barvu tohoto úkolu",
"move": "Přesunout tento úkol do jiného seznamu",
"move": "Move this task to another project",
"reminder": "Spravovat připomenutí této úlohy",
"description": "Přepnout úpravy popisu úkolu"
},
"list": {
"title": "Zobrazení seznamů",
"switchToListView": "Přepnout na zobrazení seznamu",
"switchToGanttView": "Přepnout na zobrazení gantt",
"switchToKanbanView": "Přepnout na zobrazení kanbanu",
"switchToTableView": "Přepnout na zobrazení tabulky"
"project": {
"title": "Project Views",
"switchToListView": "Switch to list view",
"switchToGanttView": "Switch to gantt view",
"switchToKanbanView": "Switch to kanban view",
"switchToTableView": "Switch to table view"
},
"navigation": {
"title": "Navigace",
"overview": "Přejít na přehled",
"upcoming": "Přejít na nadcházející úkoly",
"namespaces": "Přejít na prostory a seznamy",
"namespaces": "Navigate to namespaces & projects",
"labels": "Přejít na štítky",
"teams": "Přejít na týmy"
}
@ -922,7 +924,7 @@
"unarchive": "Zrušit archivaci",
"setBackground": "Nastavit pozadí",
"share": "Sdílet",
"newList": "Nový seznam"
"newProject": "New project"
},
"apiConfig": {
"url": "Vikunja URL",
@ -941,24 +943,24 @@
"notification": {
"title": "Oznámení",
"none": "Nemáte žádná oznámení. Mějte příjemný den!",
"explainer": "Upozornění se zobrazí zde, když proběhne akce na prostorech, seznamech nebo úkolech, ke kterým jste se přihlásili."
"explainer": "Notifications will appear here when actions on namespaces, projects or tasks you subscribed to happen."
},
"quickActions": {
"commands": "Příkazy",
"placeholder": "Napište příkaz nebo vyhledávání…",
"hint": "Můžete použít {list} k omezení hledání na seznam. Kombinujte {list} nebo {label} (štítky) s vyhledávacím dotazem pro hledání úkolu s těmito štítky nebo na tomto seznamu. Použijte {assignee} pouze pro hledání týmů.",
"hint": "You can use {project} to limit the search to a project. Combine {project} or {label} (labels) with a search query to search for a task with these labels or on that project. Use {assignee} to only search for teams.",
"tasks": "Úkoly",
"lists": "Seznamy",
"projects": "Projects",
"teams": "Týmy",
"newList": "Zadejte název nového seznamu…",
"newProject": "Enter the title of the new project…",
"newTask": "Zadejte název nového úkolu…",
"newNamespace": "Zadejte název nového prostoru…",
"newTeam": "Zadejte název nového týmu…",
"createTask": "Vytvořit úkol v aktuálním seznamu ({title})",
"createList": "Vytvořit seznam v aktuálním prostoru ({title})",
"createTask": "Create a task in the current project ({title})",
"createProject": "Create a project in the current namespace ({title})",
"cmds": {
"newTask": "Nový úkol",
"newList": "Nový seznam",
"newProject": "New project",
"newNamespace": "Nový prostor",
"newTeam": "Nový tým"
}
@ -990,15 +992,15 @@
"1018": "Nastavení typu avatara uživatele je neplatné.",
"2001": "ID nemůže být prázdné nebo 0.",
"2002": "Některé údaje požadavku byly neplatné.",
"3001": "Seznam neexistuje.",
"3004": "Pro provedení této akce musíte mít oprávnění ke čtení v tomto seznamu.",
"3005": "Název seznamu nemůže být prázdný.",
"3006": "Sdílení seznamu neexistuje.",
"3007": "Seznam s tímto identifikátorem již existuje.",
"3008": "Seznam je archivován, a proto může být přístupný pouze pro čtení. To platí i pro všechny úkoly spojené s tímto seznamem.",
"4001": "Text úkolu seznamu nemůže být prázdný.",
"4002": "Úkol seznamu neexistuje.",
"4003": "Všechny úkoly pro hromadnou úpravu musí patřit do stejného seznamu.",
"3001": "The project does not exist.",
"3004": "You need to have read permissions on that project to perform that action.",
"3005": "The project title cannot be empty.",
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Při hromadných úpravách úkolů je potřeba alespoň jeden úkol.",
"4005": "Nemáte právo vidět tento úkol.",
"4006": "Nadřazený úkol nelze nastavit jako takový.",
@ -1024,21 +1026,21 @@
"5012": "Prostor je archivován, a proto je přístupný pouze pro čtení.",
"6001": "Název týmu nemůže být prázdný.",
"6002": "Tým neexistuje.",
"6004": "Tým již má přístup k tomuto prostoru nebo seznamu.",
"6004": "The team already has access to that namespace or project.",
"6005": "Uživatel je již členem tohoto týmu.",
"6006": "Nelze odstranit posledního člena týmu.",
"6007": "Tým nemá přístup k seznamu pro provedení této akce.",
"7002": "Uživatel již má k tomuto seznamu přístup.",
"7003": "Nemáte přístup k tomuto seznamu.",
"6007": "The team does not have access to the project to perform that action.",
"7002": "The user already has access to that project.",
"7003": "You do not have access to that project.",
"8001": "Tento štítek již v tomto úkolu existuje.",
"8002": "Štítek neexistuje.",
"8003": "K tomuto štítku nemáte přístup.",
"9001": "Právo je neplatné.",
"10001": "Sloupec neexistuje.",
"10002": "Sloupec nepatří do tohoto seznamu.",
"10003": "Poslední sloupec v seznamu nelze odstranit.",
"10002": "The bucket does not belong to that project.",
"10003": "You cannot remove the last bucket on a project.",
"10004": "Nemůžete přidat úkol do tohoto sloupce, protože již překročil limit úkolů, které do něj můžete uložit.",
"10005": "Pro seznam může být pouze jeden sloupec \"Hotovo\".",
"10005": "There can be only one done bucket per project.",
"11001": "Uložený filtr neexistuje.",
"11002": "Uložené filtry nejsou k dispozici pro sdílení odkazů.",
"12001": "Typ předplatného je neplatný.",

View File

@ -5,11 +5,11 @@
"welcomeDay": "Hej {username}!",
"welcomeEvening": "Godaften {username}!",
"lastViewed": "Sidst vist",
"list": {
"newText": "Du kan oprette en ny liste til dine nye opgaver:",
"new": "Ny liste",
"importText": "Eller importere dine lister og opgaver fra andre værktøjer til Vikunja:",
"import": "Importer dine data til Vikunja"
"project": {
"newText": "You can create a new project for your new tasks:",
"new": "New project",
"importText": "Or import your projects and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
"404": {
@ -85,7 +85,7 @@
"weekStartSunday": "Søndag",
"weekStartMonday": "Mandag",
"language": "Sprog",
"defaultList": "Standard liste",
"defaultProject": "Default Project",
"timezone": "Tidszone",
"overdueTasksRemindersTime": "Forfaldne opgaver påmindelse e-mail tidspunkt"
},
@ -143,7 +143,7 @@
},
"deletion": {
"title": "Slet din Vikunja konto",
"text1": "Sletningen af din konto er permanent og kan ikke fortrydes. Vi vil slette alle dine navneområder, lister, opgaver og alt, hvad der er knyttet til den.",
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, projects, tasks and everything associated with it.",
"text2": "For at fortsætte, skal du indtaste din adgangskode. Du vil modtage en e-mail med yderligere instruktioner.",
"confirm": "Slet min konto",
"requestSuccess": "Anmodningen blev gennemført. Du vil modtage en e-mail med yderligere instruktioner.",
@ -157,201 +157,201 @@
},
"export": {
"title": "Eksporter dine Vikunja-data",
"description": "Du kan anmode om en kopi af alle dine Vikunja-data. Dette omfatter Navneområder, Lister, Opgaver og alt, hvad der er knyttet til dem. Du kan importere disse data i enhver Vikunja-instans gennem migrationsfunktionen.",
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Projects, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
"descriptionPasswordRequired": "Indtast venligst din adgangskode for at fortsætte:",
"request": "Anmod om en kopi af mine Vikunja-data",
"success": "Du har anmodet om dine Vikunja-data! Vi sender dig en e-mail, når den er klar til hentning.",
"downloadTitle": "Download dine eksporterede Vikunja-data"
}
},
"list": {
"archived": "Denne liste er arkiveret. Det er ikke muligt at oprette eller redigere opgaver i den.",
"title": "Listetitel",
"color": "Farve",
"lists": "Lister",
"list": {
"title": "Liste",
"add": "Tilføj",
"addPlaceholder": "Tilføj en ny opgave…",
"empty": "Denne liste er i øjeblikket tom.",
"newTaskCta": "Opret en ny opgave.",
"editTask": "Rediger opgave"
},
"search": "Skriv for at søge efter en liste…",
"searchSelect": "Klik eller tryk på Enter for at vælge denne liste",
"shared": "Delte Lister",
"noDescriptionAvailable": "Ingen listebeskrivelse er tilgængelig.",
"project": {
"archived": "This project is archived. It is not possible to create new or edit tasks for it.",
"title": "Project Title",
"color": "Color",
"projects": "Projects",
"search": "Type to search for a project…",
"searchSelect": "Click or press enter to select this project",
"shared": "Shared Projects",
"noDescriptionAvailable": "No project description is available.",
"create": {
"header": "Ny liste",
"titlePlaceholder": "Listens titel skrives her…",
"addTitleRequired": "Angiv venligst en titel.",
"createdSuccess": "Listen blev oprettet.",
"addListRequired": "Angiv venligst en liste eller opsæt en standardliste i indstillingerne."
"header": "New project",
"titlePlaceholder": "The project's title goes here…",
"addTitleRequired": "Please specify a title.",
"createdSuccess": "The project was successfully created.",
"addProjectRequired": "Please specify a project or set a default project in the settings."
},
"archive": {
"title": "Arkiver \"{list}\"",
"archive": "Arkiver denne liste",
"unarchive": "Fjern arkivering af denne liste",
"unarchiveText": "Du vil være i stand til at oprette nye opgaver eller redigere den.",
"archiveText": "Du vil ikke være i stand til at redigere denne liste, eller oprette nye opgaver, før du fjerner arkiveringen af den.",
"success": "Listen blev arkiveret."
"title": "Archive \"{project}\"",
"archive": "Archive this project",
"unarchive": "Un-Archive this project",
"unarchiveText": "You will be able to create new tasks or edit it.",
"archiveText": "You won't be able to edit this project or create new tasks until you un-archive it.",
"success": "The project was successfully archived."
},
"background": {
"title": "Indstil listebaggrund",
"remove": "Fjern baggrund",
"upload": "Vælg en baggrund fra din pc",
"searchPlaceholder": "Søg efter en baggrund…",
"poweredByUnsplash": "Leveret af Unsplash",
"loadMore": "Indlæs flere billeder",
"success": "Baggrunden er indstillet!",
"removeSuccess": "Baggrunden er blevet fjernet!"
"title": "Set project background",
"remove": "Remove Background",
"upload": "Choose a background from your pc",
"searchPlaceholder": "Search for a background…",
"poweredByUnsplash": "Powered by Unsplash",
"loadMore": "Load more photos",
"success": "The background has been set successfully!",
"removeSuccess": "The background has been removed successfully!"
},
"delete": {
"title": "Slet \"{list}\"",
"header": "Slet denne liste",
"text1": "Er du sikker på, at du vil slette denne liste og alt dens indhold?",
"text2": "Dette omfatter alle opgaver og KAN IKKE GØRES OM!",
"success": "Listen blev slettet.",
"tasksToDelete": "Dette vil uigenkaldeligt fjerne ca. {count} opgaver.",
"noTasksToDelete": "Denne liste indeholder ingen opgaver, den bør være sikker at slette."
"title": "Delete \"{project}\"",
"header": "Delete this project",
"text1": "Are you sure you want to delete this project and all of its contents?",
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
"title": "Dupliker denne liste",
"label": "Dupliker",
"text": "Vælg et navneområde som skal indeholde kopien:",
"success": "Listen blev kopieret."
"title": "Duplicate this project",
"label": "Duplicate",
"text": "Select a namespace which should hold the duplicated project:",
"success": "The project was successfully duplicated."
},
"edit": {
"header": "Rediger Denne Liste",
"title": "Rediger \"{list}\"",
"titlePlaceholder": "Listens titel skrives her…",
"identifierTooltip": "Liste identifikatoren kan bruges til entydigt at identificere en opgave på tværs af lister. Du kan sætte den til tom for at deaktivere det.",
"identifier": "Liste identifikator",
"identifierPlaceholder": "Liste-identifikatoren skal være her…",
"description": "Beskrivelse",
"descriptionPlaceholder": "Listernes beskrivelse skrives her…",
"color": "Farve",
"success": "Listen blev opdateret."
"header": "Edit This Project",
"title": "Edit \"{project}\"",
"titlePlaceholder": "The project title goes here…",
"identifierTooltip": "The project identifier can be used to uniquely identify a task across projects. You can set it to empty to disable it.",
"identifier": "Project Identifier",
"identifierPlaceholder": "The project identifier goes here…",
"description": "Description",
"descriptionPlaceholder": "The projects description goes here…",
"color": "Color",
"success": "The project was successfully updated."
},
"share": {
"header": "Del denne liste",
"title": "Del \"{list}\"",
"share": "Del",
"header": "Share this project",
"title": "Share \"{project}\"",
"share": "Share",
"links": {
"title": "Delingslink",
"what": "Hvad er et delingslink?",
"explanation": "Delinglinks giver dig mulighed for nemt at dele en liste med andre brugere, som ikke har en konto på Vikunja.",
"create": "Opret et nyt delingslink",
"name": "Navn (valgfrit)",
"namePlaceholder": "f.eks. Lorem Ipsum",
"nameExplanation": "Alle handlinger udført med dette link deling vil blive vist med navnet.",
"password": "Adgangskode (valgfrit)",
"passwordExplanation": "Ved indlogning vil brugeren blive bedt om at indtaste denne adgangskode.",
"noName": "Intet navn indstillet",
"remove": "Fjern et delingslink",
"removeText": "Er du sikker på, at du vil fjerne dette delingslink? Det vil ikke længere være muligt at få adgang til denne liste med dette delingslink. Dette kan ikke gøres om!",
"createSuccess": "Delingslinket blev oprettet.",
"deleteSuccess": "Delingslinket blev slettet",
"view": "Vis",
"sharedBy": "Delt af {0}"
"title": "Share Links",
"what": "What is a share link?",
"explanation": "Share Links allow you to easily share a project with other users who don't have an account on Vikunja.",
"create": "Create a new link share",
"name": "Name (optional)",
"namePlaceholder": "e.g. Lorem Ipsum",
"nameExplanation": "All actions done by this link share will show up with the name.",
"password": "Password (optional)",
"passwordExplanation": "When authenticating, the user will be required to enter this password.",
"noName": "No name set",
"remove": "Remove a link share",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this project with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted",
"view": "View",
"sharedBy": "Shared by {0}"
},
"userTeam": {
"typeUser": "bruger | brugere",
"typeTeam": "hold | hold",
"shared": "Delt med disse {type}",
"you": "Dig",
"notShared": "Ikke delt med nogen {type} endnu.",
"removeHeader": "Fjern en {type} fra {sharable}",
"removeText": "Er du sikker på at du vil fjerne denne {sharable} fra {type}? Dette kan ikke fortrydes!",
"removeSuccess": "{sharable} blev fjernet fra {type}.",
"addedSuccess": "{type} blev tilføjet.",
"updatedSuccess": "{type} blev tilføjet."
"typeUser": "user | users",
"typeTeam": "team | teams",
"shared": "Shared with these {type}",
"you": "You",
"notShared": "Not shared with any {type} yet.",
"removeHeader": "Remove a {type} from the {sharable}",
"removeText": "Are you sure you want to remove this {sharable} from the {type}? This cannot be undone!",
"removeSuccess": "The {sharable} was successfully removed from the {type}.",
"addedSuccess": "The {type} was successfully added.",
"updatedSuccess": "The {type} was successfully added."
},
"right": {
"title": "Tilladelse",
"read": "Skrivebeskyttet",
"readWrite": "Læse & skrive",
"admin": "Administrator"
"title": "Permission",
"read": "Read only",
"readWrite": "Read & write",
"admin": "Admin"
},
"attributes": {
"link": "Link",
"delete": "Slet"
"delete": "Delete"
}
},
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This project is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": {
"title": "Gantt",
"showTasksWithoutDates": "Vis opgaver som ikke har angivet datoer",
"size": "Størrelse",
"default": "Standard",
"month": "Måned",
"day": "Dag",
"hour": "Time",
"range": "Datointerval",
"noDates": "Denne opgave har ingen datoer angivet."
"showTasksWithoutDates": "Show tasks which don't have dates set",
"size": "Size",
"default": "Default",
"month": "Month",
"day": "Day",
"hour": "Hour",
"range": "Date Range",
"noDates": "This task has no dates set."
},
"table": {
"title": "Tabel",
"columns": "Kolonner"
"title": "Table",
"columns": "Columns"
},
"kanban": {
"title": "Kanban",
"limit": "Grænse: {limit}",
"noLimit": "Ikke angivet",
"doneBucket": "Udført-bøtte",
"doneBucketHint": "Alle opgaver der flyttes ind i denne bøtte, vil automatisk blive markeret som udført.",
"doneBucketHintExtended": "Alle opgaver der flyttes ind i den udført-bøtten vil blive markeret som udført automatisk. Alle opgaver markeret som udført fra andre steder vil også blive flyttet.",
"doneBucketSavedSuccess": "Udført-bøtten er blevet gemt.",
"deleteLast": "Du kan ikke fjerne den sidste bøtte.",
"addTaskPlaceholder": "Indtast den nye opgavetitel…",
"addTask": "Tilføj en opgave",
"addAnotherTask": "Tilføj en anden opgave",
"addBucket": "Tilføj en ny bøtte",
"addBucketPlaceholder": "Indtast den nye titel på bøtten…",
"deleteHeaderBucket": "Slet bøtten",
"deleteBucketText1": "Er du sikker på, at du vil slette denne bøtte?",
"deleteBucketText2": "Dette vil ikke slette nogen opgaver, men flytter dem ind i standard-bøtten.",
"deleteBucketSuccess": "Bøtten er blevet slettet.",
"bucketTitleSavedSuccess": "Titlen på bøtten er blevet gemt.",
"bucketLimitSavedSuccess": "Grænsen for bøtten er blevet gemt.",
"collapse": "Sammenfold denne spand"
"limit": "Limit: {limit}",
"noLimit": "Not Set",
"doneBucket": "Done bucket",
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.",
"doneBucketHintExtended": "All tasks moved into the done bucket will be marked as done automatically. All tasks marked as done from elsewhere will be moved as well.",
"doneBucketSavedSuccess": "The done bucket has been saved successfully.",
"deleteLast": "You cannot remove the last bucket.",
"addTaskPlaceholder": "Enter the new task title…",
"addTask": "Add a task",
"addAnotherTask": "Add another task",
"addBucket": "Create a new bucket",
"addBucketPlaceholder": "Enter the new bucket title…",
"deleteHeaderBucket": "Delete the bucket",
"deleteBucketText1": "Are you sure you want to delete this bucket?",
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.",
"deleteBucketSuccess": "The bucket has been deleted successfully.",
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.",
"bucketLimitSavedSuccess": "The bucket limit been saved successfully.",
"collapse": "Collapse this bucket"
},
"pseudo": {
"favorites": {
"title": "Favoritter"
"title": "Favorites"
}
}
},
"namespace": {
"title": "Navneområder & Lister",
"title": "Namespaces & Projects",
"namespace": "Navneområde",
"showArchived": "Vis arkiverede",
"noneAvailable": "Du har ingen navneområder lige nu.",
"unarchive": "Tilbagekald",
"archived": "Arkiveret",
"noLists": "Dette navneområde indeholder ikke nogen lister.",
"createList": "Opret en ny liste i dette navneområde.",
"noProjects": "This namespace does not contain any projects.",
"createProject": "Create a new project in this namespace.",
"namespaces": "Navneområder",
"search": "Skriv for at søge efter et navneområde…",
"create": {
"title": "Nyt navneområde",
"titleRequired": "Angiv venligst en titel.",
"explanation": "Et navneområde er en samling af lister du kan dele og bruge til at organisere dine lister med. Faktisk tilhører hver liste et navneområde.",
"explanation": "A namespace is a collection of projects you can share and use to organize your projects with. In fact, every project belongs to a namespace.",
"tooltip": "Hvad er et navneområde?",
"success": "Navneområdet blev oprettet."
},
"archive": {
"titleArchive": "Arkiver \"{namespace}\"",
"titleUnarchive": "Fjern arkivering \"{namespace}\"",
"archiveText": "Du vil ikke være i stand til at redigere dette navneområde eller oprette nye lister, før du fjerner arkiveringen fra den. Dette vil også arkivere alle lister i dette navneområde.",
"unarchiveText": "Du vil kunne oprette nye lister eller redigere det.",
"archiveText": "You won't be able to edit this namespace or create new projects until you un-archive it. This will also archive all projects in this namespace.",
"unarchiveText": "You will be able to create new projects or edit it.",
"success": "Navneområdet blev arkiveret.",
"unarchiveSuccess": "Navneområdet blev tilbagekaldt.",
"description": "Hvis et navneområde arkiveres, kan du ikke oprette nye lister eller redigere det."
"description": "If a namespace is archived, you cannot create new projects or edit it."
},
"delete": {
"title": "Slet \"{namespace}\"",
"text1": "Er du sikker på, at du vil slette dette navneområde og alt dets indhold?",
"text2": "Dette omfatter alle lister og opgaver og KAN IKKE GØRES OM!",
"text2": "This includes all projects and tasks and CANNOT BE UNDONE!",
"success": "Navneområdet blev slettet."
},
"edit": {
@ -371,8 +371,8 @@
"isArchived": "Dette navneområde er arkiveret"
},
"pseudo": {
"sharedLists": {
"title": "Delte Lister"
"sharedProjects": {
"title": "Shared Projects"
},
"favorites": {
"title": "Favoritter"
@ -403,8 +403,9 @@
},
"create": {
"title": "Nyt Gemt Filter",
"description": "Et gemt filter er en virtuel liste, som beregnes ud fra et sæt filtre, hver gang det er tilgået. Når den er oprettet, vises den i et særligt navneområde.",
"action": "Opret nyt gemt filter"
"description": "A saved filter is a virtual project which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
"action": "Opret nyt gemt filter",
"titleRequired": "Please provide a title for the filter."
},
"delete": {
"header": "Slet dette gemte filter",
@ -434,7 +435,7 @@
"label": {
"title": "Etiketter",
"manage": "Administrer etiketter",
"description": "Klik på en etiket for at redigere den. Du kan redigere alle etiketter du har oprettet, du kan bruge alle etiketter som er tilknyttet en opgave tilhørende en liste du har adgang til.",
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose project you have access.",
"newCTA": "Du har i øjeblikket ingen etiketter.",
"search": "Skriv for at søge efter en etiket…",
"create": {
@ -459,7 +460,7 @@
},
"sharing": {
"authenticating": "Godkender…",
"passwordRequired": "Denne delte liste kræver en adgangskode. Indtast den nedenfor:",
"passwordRequired": "This shared project requires a password. Please enter it below:",
"error": "Der opstod en fejl.",
"invalidPassword": "Adgangskoden er ugyldig."
},
@ -528,7 +529,7 @@
"code": "Kode",
"quote": "Citat",
"unorderedList": "Usorteret liste",
"orderedList": "Sorteret liste",
"orderedList": "Ordered List",
"cleanBlock": "Ryd Blok",
"link": "Link",
"image": "Billede",
@ -601,6 +602,7 @@
"addReminder": "Tilføj en ny påmindelse…",
"doneSuccess": "Opgaven blev markeret som udført.",
"undoneSuccess": "Opgaven fik fjernet sin udført-markering.",
"undo": "Undo",
"openDetail": "Åbn detaljeret opgavevisning",
"checklistTotal": "{checked} af {total} opgaver",
"checklistAllDone": "{total} opgaver",
@ -617,7 +619,7 @@
"chooseDueDate": "Klik her for at angive en forfaldsdato",
"chooseStartDate": "Klik her for at angive en startdato",
"chooseEndDate": "Klik her for at angive en slutdato",
"move": "Flyt opgave til en anden liste",
"move": "Move task to a different project",
"done": "Markér opgave som udført!",
"undone": "Markér opgave som ikke-udført",
"created": "Oprettet {0} af {1}",
@ -625,7 +627,7 @@
"doneAt": "Udført {0}",
"updateSuccess": "Opgaven blev gemt.",
"deleteSuccess": "Opgaven er blevet slettet.",
"belongsToList": "Denne opgave tilhører listen '{list}'",
"belongsToProject": "This task belongs to project '{project}'",
"due": "Forfalder {at}",
"closePopup": "Luk pop-up",
"delete": {
@ -645,7 +647,7 @@
"percentDone": "Indstil Fremskridt",
"attachments": "Tilføj Vedhæftninger",
"relatedTasks": "Tilføj Relation",
"moveList": "Flyt",
"moveProject": "Move",
"color": "Indstil Farve",
"delete": "Slet",
"favorite": "Tilføj til Favoritter",
@ -672,21 +674,21 @@
"updated": "Opdateret"
},
"subscription": {
"subscribedListThroughParentNamespace": "Du kan ikke afmelde dig her, fordi du abonnerer på denne liste gennem dens navneområde.",
"subscribedProjectThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this project through its namespace.",
"subscribedTaskThroughParentNamespace": "Du kan ikke afmelde dig her, fordi du abonnerer på denne opgave gennem dens navneområde.",
"subscribedTaskThroughParentList": "Du kan ikke afmelde dig her, fordi du abonnerer på denne opgave gennem dens liste.",
"subscribedTaskThroughParentProject": "You can't unsubscribe here because you are subscribed to this task through its project.",
"subscribedNamespace": "Du abonnerer i øjeblikket på dette navneområde og vil modtage notifikationer om ændringer.",
"notSubscribedNamespace": "Du abonnerer ikke på dette navneområde og modtager ikke notifikationer om ændringer.",
"subscribedList": "Du abonnerer på denne liste og vil modtage notifikationer om ændringer.",
"notSubscribedList": "Du abonnerer ikke på denne liste og modtager ikke meddelelser om ændringer.",
"subscribedProject": "You are currently subscribed to this project and will receive notifications for changes.",
"notSubscribedProject": "You are not subscribed to this project and won't receive notifications for changes.",
"subscribedTask": "Du abonnerer på denne opgave og vil modtage notifikationer om ændringer.",
"notSubscribedTask": "Du abonnerer ikke på denne opgave og modtager ikke notifikationer om ændringer.",
"subscribe": "Abonner",
"unsubscribe": "Afmeld",
"subscribeSuccessNamespace": "Du abonnerer nu på dette navneområde",
"unsubscribeSuccessNamespace": "Du er nu afmeldt dette navneområde",
"subscribeSuccessList": "Du abonnerer nu på denne liste",
"unsubscribeSuccessList": "Du er nu afmeldt denne liste",
"subscribeSuccessProject": "You are now subscribed to this project",
"unsubscribeSuccessProject": "You are now unsubscribed to this project",
"subscribeSuccessTask": "Du abonnerer nu på denne opgave",
"unsubscribeSuccessTask": "Du er nu afmeldt denne opgave"
},
@ -760,7 +762,7 @@
"new": "Ny Opgaverelation",
"searchPlaceholder": "Indtast søgning efter en ny opgave der tilføjes som relateret…",
"createPlaceholder": "Tilføj dette som en ny relateret opgave",
"differentList": "Denne opgave hører til en anden liste.",
"differentProject": "This task belongs to a different project.",
"differentNamespace": "Denne opgave hører til et andet navneområde.",
"noneYet": "Ingen opgaverelationer endnu.",
"delete": "Slet Opgaverelation",
@ -810,10 +812,10 @@
"priority1": "For at sætte en opgaves prioritet, tilføj et nummer 1-5, præfikset med {prefix}.",
"priority2": "Jo højere nummer, des højere prioritet.",
"assignees": "For at tildele opgaven direkte til en bruger, tilføj deres brugernavn præfikset med {prefix} til opgaven.",
"list1": "For at angive en liste som opgaven skal høre til, skal du indtaste dens navn præfikset med {prefix}.",
"list2": "Dette vil returnere en fejl, hvis listen ikke eksisterer.",
"list3": "For at bruge mellemrum, skal du blot tilføje \" eller ' omkring listens navn.",
"list4": "For eksempel: {prefix}\"Liste med mellemrum\".",
"project1": "To set a project for the task to appear in, enter its name prefixed with {prefix}.",
"project2": "This will return an error if the project does not exist.",
"project3": "To use spaces, simply add a \" or ' around the project name.",
"project4": "For example: {prefix}\"Project with spaces\".",
"dateAndTime": "Dato og tid",
"date": "En dato vil blive brugt som forfaldsdato for den nye opgave. Du kan bruge datoer i enhver af disse formater:",
"dateWeekday": "enhver ugedag, vil bruge den næste dato med på den dag",
@ -846,19 +848,19 @@
"delete": {
"header": "Slet holdet",
"text1": "Er du sikker på du vil slette dette hold og alle dets medlemmer?",
"text2": "Alle holdmedlemmer vil miste adgang til lister og navneområder, der er delt med dette team. Dette kan IKKE GØRES OM!",
"text2": "All team members will lose access to projects and namespaces shared with this team. This CANNOT BE UNDONE!",
"success": "Holdet blev slettet."
},
"deleteUser": {
"header": "Fjern en bruger fra holdet",
"text1": "Er du sikker på du vil fjerne denne bruger fra holdet?",
"text2": "De vil miste adgang til alle lister og navneområder, som dette hold har adgang til. Dette IKKE GØRES OM!",
"text2": "They will lose access to all projects and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "Brugeren blev fjernet fra holdet."
},
"leave": {
"title": "Forlad hold",
"text1": "Er du sikker på du vil forlade dette hold?",
"text2": "De vil miste adgang til alle lister og navneområder, som dette hold har adgang til. Hvis du ombestemmer dig, skal du have en holdadministrator til at tilføje dig igen.",
"text2": "You will lose access to all projects and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "Du har forladt holdet."
}
},
@ -890,22 +892,22 @@
"attachment": "Tilføj en vedhæftet fil til denne opgave",
"related": "Rediger relaterede opgaver for denne opgave",
"color": "Skift farven på denne opgave",
"move": "Flyt denne opgave til en anden liste",
"move": "Move this task to another project",
"reminder": "Administrer påmindelser om denne opgave",
"description": "Slå redigering af opgavebeskrivelse til/fra"
},
"list": {
"title": "Listevisning",
"switchToListView": "Skift til listevisning",
"switchToGanttView": "Skift til gantt- visning",
"switchToKanbanView": "Skift til kanban visning",
"switchToTableView": "Skift til tabelvisning"
"project": {
"title": "Project Views",
"switchToListView": "Switch to list view",
"switchToGanttView": "Switch to gantt view",
"switchToKanbanView": "Switch to kanban view",
"switchToTableView": "Switch to table view"
},
"navigation": {
"title": "Navigation",
"overview": "Gå til oversigt",
"upcoming": "Gå til kommende opgaver",
"namespaces": "Gå til navneområder & lister",
"namespaces": "Navigate to namespaces & projects",
"labels": "Naviger til etiketter",
"teams": "Naviger til hold"
}
@ -922,7 +924,7 @@
"unarchive": "Tilbagekald",
"setBackground": "Indstil baggrund",
"share": "Del",
"newList": "Ny liste"
"newProject": "New project"
},
"apiConfig": {
"url": "Vikunja URL",
@ -941,24 +943,24 @@
"notification": {
"title": "Notifikationer",
"none": "Du har ingen notifikationer. Hav en dejlig dag!",
"explainer": "Notifikationer vises her, når handlinger på navneområder, lister eller opgaver du abonnerede på er nødvendige."
"explainer": "Notifications will appear here when actions on namespaces, projects or tasks you subscribed to happen."
},
"quickActions": {
"commands": "Kommandoer",
"placeholder": "Indtast en kommando eller søg…",
"hint": "Du kan bruge {list} til at begrænse søgningen til en liste. Kombiner {list} eller {label} (etiketter) med en søgning for at søge efter en opgave med disse etiketter eller på listen. Brug {assignee} til kun at søge efter hold.",
"hint": "You can use {project} to limit the search to a project. Combine {project} or {label} (labels) with a search query to search for a task with these labels or on that project. Use {assignee} to only search for teams.",
"tasks": "Opgaver",
"lists": "Lister",
"projects": "Projects",
"teams": "Hold",
"newList": "Indtast titlen på den nye liste…",
"newProject": "Enter the title of the new project…",
"newTask": "Indtast titlen på den nye opgave…",
"newNamespace": "Indtast titlen på det nye navneområde…",
"newTeam": "Indtast navnet på det nye hold…",
"createTask": "Opret en opgave i den nuværende liste ({title})",
"createList": "Opret en liste i det nuværende navneområde ({title})",
"createTask": "Create a task in the current project ({title})",
"createProject": "Create a project in the current namespace ({title})",
"cmds": {
"newTask": "Ny Opgave",
"newList": "Ny liste",
"newProject": "New project",
"newNamespace": "Nyt navneområde",
"newTeam": "Nyt hold"
}
@ -990,15 +992,15 @@
"1018": "Brugerens avatartype indstilling er ugyldig.",
"2001": "ID kan ikke være tomt eller 0.",
"2002": "Nogle af forespørgselsdataene var ugyldige.",
"3001": "Listen eksisterer ikke.",
"3004": "Du skal have læserettigheder på listen for at udføre denne handling.",
"3005": "Listens titel må ikke være tom.",
"3006": "Listendelingen findes ikke.",
"3007": "En liste med dette id findes allerede.",
"3008": "Listen er arkiveret og kan derfor kun tilgås skrivebeskyttet. Dette gælder også for alle opgaver der er tilknyttet denne liste.",
"4001": "Listeopgaveteksten kan ikke være tom.",
"4002": "Listeopgave eksisterer ikke.",
"4003": "Alle masseredigeringsopgaver skal tilhøre den samme liste.",
"3001": "The project does not exist.",
"3004": "You need to have read permissions on that project to perform that action.",
"3005": "The project title cannot be empty.",
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Der skal være mindst én opgave for at masseredigere opgaver.",
"4005": "Du har ikke rettigheder til at se opgaven.",
"4006": "Du kan ikke indstille en overordnet opgave som opgaven selv.",
@ -1024,21 +1026,21 @@
"5012": "Navneområdet er arkiveret og kan derfor kun læses.",
"6001": "Holdnavnet må ikke være tomt.",
"6002": "Holdet findes ikke.",
"6004": "Holdet har allerede adgang til dette navneområde eller liste.",
"6004": "The team already has access to that namespace or project.",
"6005": "Brugeren er allerede medlem af holdet.",
"6006": "Kan ikke slette det sidste holdmedlem.",
"6007": "Holdet har ikke adgang til listen for at udføre denne handling.",
"7002": "Brugeren har allerede adgang til denne liste.",
"7003": "Du har ikke adgang til denne liste.",
"6007": "The team does not have access to the project to perform that action.",
"7002": "The user already has access to that project.",
"7003": "You do not have access to that project.",
"8001": "Denne etiket findes allerede på denne opgave.",
"8002": "Etiketten findes ikke.",
"8003": "Du har ikke adgang til denne etiket.",
"9001": "Rettigheden er ugyldig.",
"10001": "Kurven findes ikke.",
"10002": "Kurven tilhører ikke denne liste.",
"10003": "Du kan ikke fjerne den sidste kurv på en liste.",
"10002": "The bucket does not belong to that project.",
"10003": "You cannot remove the last bucket on a project.",
"10004": "Du kan ikke tilføje opgaven til denne kurv, da den allerede overskrider grænsen for opgaver, den kan holde.",
"10005": "Der kan kun være en færdig kurv pr. liste.",
"10005": "There can be only one done bucket per project.",
"11001": "Det gemte filter eksisterer ikke.",
"11002": "Gemte filtre er ikke tilgængelige for delingslink.",
"12001": "Enhedstypen for abonnementet er ugyldigt.",

View File

@ -5,11 +5,11 @@
"welcomeDay": "Hallo {username}!",
"welcomeEvening": "Guten Abend, {username}!",
"lastViewed": "Zuletzt angesehen",
"list": {
"newText": "Du kannst eine neue Liste für deine neuen Aufgaben erstellen:",
"new": "Neue Liste",
"importText": "Oder importiere deine Listen und Aufgaben aus anderen Diensten in Vikunja:",
"import": "Deine Daten in Vikunja importieren"
"project": {
"newText": "Du kannst ein neues Projekt für deine neuen Aufgaben erstellen:",
"new": "New project",
"importText": "Or import your projects and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
"404": {
@ -85,7 +85,7 @@
"weekStartSunday": "Sonntag",
"weekStartMonday": "Montag",
"language": "Sprache",
"defaultList": "Standard-Liste",
"defaultProject": "Default Project",
"timezone": "Zeitzone",
"overdueTasksRemindersTime": "Zeit der E-Mail-Zusammenfassung der überfälligen Aufgaben"
},
@ -143,7 +143,7 @@
},
"deletion": {
"title": "Lösche deinen Vikunja-Account",
"text1": "Das Löschen deines Accounts ist dauerhaft und unwiderruflich. Alle Namensräume, Listen und zugehörige Daten werden gelöscht.",
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, projects, tasks and everything associated with it.",
"text2": "Zum Fortfahren gib bitte dein Passwort ein. Du erhältst eine E-Mail mit weiteren Anweisungen.",
"confirm": "Meinen Account löschen",
"requestSuccess": "Die Anfrage war erfolgreich. Du erhältst eine E-Mail mit weiteren Anweisungen.",
@ -157,201 +157,201 @@
},
"export": {
"title": "Exportiere deine Vikunja-Daten",
"description": "Du kannst eine Kopie deiner Daten bei Vikunja anfordern. Dazu gehören Namespaces, Listen, Aufgaben und alles, was damit zusammenhängt. Du kannst diese Daten dann in jeder Vikunja-Instanz über die Migrationsfunktion importieren.",
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Projects, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
"descriptionPasswordRequired": "Bitte gib dein Passwort ein, um fortzufahren:",
"request": "Eine Kopie meiner Vikunja Daten anfordern",
"success": "Du hast deine Daten bei Vikunja erfolgreich angefordert! Wir schicken dir eine E-Mail, sobald sie zum Download bereitstehen.",
"downloadTitle": "Lade deine exportierten Vikunja-Daten herunter"
}
},
"list": {
"archived": "Diese Liste ist archiviert. Es ist nicht möglich, neue Aufgaben zu erstellen oder sie zu bearbeiten.",
"title": "Listentitel",
"color": "Farbe",
"lists": "Listen",
"list": {
"title": "Liste",
"add": "Hinzufügen",
"addPlaceholder": "Neue Aufgabe hinzufügen …",
"empty": "Diese Liste ist derzeit leer.",
"newTaskCta": "Eine neue Aufgabe erstellen.",
"editTask": "Aufgabe bearbeiten"
},
"search": "Tippe, um nach einer Liste zu suchen…",
"searchSelect": "Klicke auf oder drücke die Eingabetaste, um diese Liste auszuwählen",
"shared": "Geteilte Listen",
"noDescriptionAvailable": "Keine Listenbeschreibung verfügbar.",
"project": {
"archived": "This project is archived. It is not possible to create new or edit tasks for it.",
"title": "Project Title",
"color": "Color",
"projects": "Projects",
"search": "Type to search for a project…",
"searchSelect": "Click or press enter to select this project",
"shared": "Shared Projects",
"noDescriptionAvailable": "No project description is available.",
"create": {
"header": "Neue Liste",
"titlePlaceholder": "Der Titel der Liste steht hier…",
"addTitleRequired": "Bitte gebe einen Namen an.",
"createdSuccess": "Die Liste wurde erfolgreich erstellt.",
"addListRequired": "Bitte gebe eine Liste an oder lege eine Standardliste in den Einstellungen fest."
"header": "New project",
"titlePlaceholder": "The project's title goes here…",
"addTitleRequired": "Please specify a title.",
"createdSuccess": "The project was successfully created.",
"addProjectRequired": "Please specify a project or set a default project in the settings."
},
"archive": {
"title": "„{list}“ archivieren",
"archive": "Diese Liste archivieren",
"unarchive": "Archivierung dieser Liste aufheben",
"unarchiveText": "Du wirst neue Aufgaben erstellen oder sie bearbeiten können.",
"archiveText": "Du kannst diese Liste nicht bearbeiten oder neue Aufgaben erstellen, bis du die Archivierung aufhebst.",
"success": "Die Liste wurde erfolgreich archiviert."
"title": "Archive \"{project}\"",
"archive": "Archive this project",
"unarchive": "Un-Archive this project",
"unarchiveText": "You will be able to create new tasks or edit it.",
"archiveText": "You won't be able to edit this project or create new tasks until you un-archive it.",
"success": "The project was successfully archived."
},
"background": {
"title": "Listenhintergrund festlegen",
"remove": "Hintergrund entfernen",
"upload": "Wähle einen Hintergrund von deinem Computer",
"searchPlaceholder": "Nach einem Hintergrund suchen…",
"title": "Set project background",
"remove": "Remove Background",
"upload": "Choose a background from your pc",
"searchPlaceholder": "Search for a background…",
"poweredByUnsplash": "Powered by Unsplash",
"loadMore": "Weitere Bilder laden",
"success": "Der Hintergrund wurde erfolgreich eingestellt!",
"removeSuccess": "Der Hintergrund wurde erfolgreich entfernt!"
"loadMore": "Load more photos",
"success": "The background has been set successfully!",
"removeSuccess": "The background has been removed successfully!"
},
"delete": {
"title": "„{list}“ löschen",
"header": "Diese Liste löschen",
"text1": "Bist du sicher, dass du diese Liste und alle ihre Inhalte löschen willst?",
"text2": "Dies umfasst alle Aufgaben und kann NICHT rückgängig gemacht werden!",
"success": "Die Liste wurde erfolgreich gelöscht.",
"tasksToDelete": "Dies löscht unwiderruflich ca. {count} Aufgaben.",
"noTasksToDelete": "Diese Liste enthält keine Aufgaben, sie kann sicher gelöscht werden."
"title": "Delete \"{project}\"",
"header": "Delete this project",
"text1": "Are you sure you want to delete this project and all of its contents?",
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
"title": "Diese Liste duplizieren",
"label": "Duplizieren",
"text": "Wähle einen Namespace aus, der die duplizierte Liste enthalten soll:",
"success": "Die Liste wurde erfolgreich dupliziert."
"title": "Duplicate this project",
"label": "Duplicate",
"text": "Select a namespace which should hold the duplicated project:",
"success": "The project was successfully duplicated."
},
"edit": {
"header": "Diese Liste bearbeiten",
"title": "„{list}“ bearbeiten",
"titlePlaceholder": "Listenname eingeben …",
"identifierTooltip": "Der Listenbezeichner kann zur eindeutigen Identifizierung einer Aufgabe über Listen hinweg verwendet werden. Du kannst ihn auf leer setzen, um ihn zu deaktivieren.",
"identifier": "Listenbezeichner",
"identifierPlaceholder": "Der Listenbezeichner kommt hierhin…",
"description": "Beschreibung",
"descriptionPlaceholder": "Listenbeschreibung eingeben…",
"color": "Farbe",
"success": "Die Liste wurde erfolgreich aktualisiert."
"header": "Edit This Project",
"title": "Edit \"{project}\"",
"titlePlaceholder": "The project title goes here…",
"identifierTooltip": "The project identifier can be used to uniquely identify a task across projects. You can set it to empty to disable it.",
"identifier": "Project Identifier",
"identifierPlaceholder": "The project identifier goes here…",
"description": "Description",
"descriptionPlaceholder": "The projects description goes here…",
"color": "Color",
"success": "The project was successfully updated."
},
"share": {
"header": "Diese Liste teilen",
"title": "„{list}“ teilen",
"share": "Teilen",
"header": "Share this project",
"title": "Share \"{project}\"",
"share": "Share",
"links": {
"title": "Linkfreigaben",
"what": "Was ist eine Linkfreigabe?",
"explanation": "Mit Linkfreigaben kannst du Listen mit Benutzer:innen ohne Vikunja-Account teilen.",
"create": "Erstelle einen neue Linkfreigabe",
"title": "Share Links",
"what": "What is a share link?",
"explanation": "Share Links allow you to easily share a project with other users who don't have an account on Vikunja.",
"create": "Create a new link share",
"name": "Name (optional)",
"namePlaceholder": "z.B. Max Muster",
"nameExplanation": "Alle Aktionen, die mit dieser Linkfreigabe durchgeführt werden, werden mit diesem Namen angezeigt.",
"password": "Passwort (optional)",
"passwordExplanation": "Bei der Authentifizierung wird der:die Benutzer:in aufgefordert, dieses Passwort einzugeben.",
"noName": "Kein Name angegeben",
"remove": "Eine Linkfreigabe löschen",
"removeText": "Bist du sicher, dass du diese Linkfreigabe unwiderruflich löschen möchtest? Über die Linkfreigabe ist danach der Zugriff auf die Liste nicht mehr möglich!",
"createSuccess": "Die Linkfreigabe wurde erfolgreich erstellt.",
"deleteSuccess": "Die Linkfreigabe wurde erfolgreich gelöscht",
"view": "Ansicht",
"sharedBy": "Von {0} geteilt"
"namePlaceholder": "e.g. Lorem Ipsum",
"nameExplanation": "All actions done by this link share will show up with the name.",
"password": "Password (optional)",
"passwordExplanation": "When authenticating, the user will be required to enter this password.",
"noName": "No name set",
"remove": "Remove a link share",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this project with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted",
"view": "View",
"sharedBy": "Shared by {0}"
},
"userTeam": {
"typeUser": "Benutzer:in | Benutzer:innen",
"typeTeam": "Team | Teams",
"shared": "Geteilt mit diesen {type}",
"you": "Du",
"notShared": "Noch nicht mit einem {type} geteilt.",
"removeHeader": "Einen {type} von {sharable} entfernen",
"removeText": "Diesen {sharable} von {type} entfernen? Dies kann nicht rückgängig gemacht werden!",
"removeSuccess": "{sharable} wurde erfolgreich von {type} entfernt.",
"addedSuccess": "{type} wurde erfolgreich hinzugefügt.",
"updatedSuccess": "{type} wurde erfolgreich hinzugefügt."
"typeUser": "user | users",
"typeTeam": "team | teams",
"shared": "Shared with these {type}",
"you": "You",
"notShared": "Not shared with any {type} yet.",
"removeHeader": "Remove a {type} from the {sharable}",
"removeText": "Are you sure you want to remove this {sharable} from the {type}? This cannot be undone!",
"removeSuccess": "The {sharable} was successfully removed from the {type}.",
"addedSuccess": "The {type} was successfully added.",
"updatedSuccess": "The {type} was successfully added."
},
"right": {
"title": "Berechtigung",
"read": "Nur Leserechte",
"readWrite": "Lesen & Schreiben",
"title": "Permission",
"read": "Read only",
"readWrite": "Read & write",
"admin": "Admin"
},
"attributes": {
"link": "Link",
"delete": "Löschen"
"delete": "Delete"
}
},
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This project is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": {
"title": "Gantt",
"showTasksWithoutDates": "Aufgaben anzeigen, für die keine Daten festgelegt sind",
"size": "Größe",
"default": "Standard",
"month": "Monat",
"day": "Tag",
"hour": "Stunde",
"range": "Zeitraum",
"noDates": "Diese Aufgabe hat keine Daten definiert."
"showTasksWithoutDates": "Show tasks which don't have dates set",
"size": "Size",
"default": "Default",
"month": "Month",
"day": "Day",
"hour": "Hour",
"range": "Date Range",
"noDates": "This task has no dates set."
},
"table": {
"title": "Tabelle",
"columns": "Spalten"
"title": "Table",
"columns": "Columns"
},
"kanban": {
"title": "Kanban",
"limit": "Limit: {limit}",
"noLimit": "Nicht gesetzt",
"doneBucket": "Erledigt Spalte",
"doneBucketHint": "Alle Aufgaben, die in diese Spalte verschoben werden, werden automatisch als erledigt markiert.",
"doneBucketHintExtended": "Alle Aufgaben, die in die Erledigt Spalte verschoben wurden, werden automatisch als erledigt markiert. Aufgaben, die in einer anderen Spalte als Erledigt markiert wurden, werden auch in diese Spalte verschoben.",
"doneBucketSavedSuccess": "Erledigt Spalte gespeichert.",
"deleteLast": "Du kannst die letzte Spalte nicht entfernen.",
"addTaskPlaceholder": "Gebe einen Aufgabenamen ein …",
"addTask": "Eine Aufgabe hinzufügen",
"addAnotherTask": "Weitere Aufgabe hinzufügen",
"addBucket": "Eine neue Spalte erstellen",
"addBucketPlaceholder": "Gebe einen Spalten-Titel ein…",
"deleteHeaderBucket": "Spalte löschen",
"deleteBucketText1": "Bist du sicher, dass du diese Spalte löschen möchtest?",
"deleteBucketText2": "Dies löscht keine Aufgaben, sondern verschiebt sie in die Standard Spalte.",
"deleteBucketSuccess": "Die Spalte wurde erfolgreich gelöscht.",
"bucketTitleSavedSuccess": "Der Spaltenname wurde erfolgreich gespeichert.",
"bucketLimitSavedSuccess": "Das Spaltenlimit wurde erfolgreich gespeichert.",
"collapse": "Spalte einklappen"
"noLimit": "Not Set",
"doneBucket": "Done bucket",
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.",
"doneBucketHintExtended": "All tasks moved into the done bucket will be marked as done automatically. All tasks marked as done from elsewhere will be moved as well.",
"doneBucketSavedSuccess": "The done bucket has been saved successfully.",
"deleteLast": "You cannot remove the last bucket.",
"addTaskPlaceholder": "Enter the new task title…",
"addTask": "Add a task",
"addAnotherTask": "Add another task",
"addBucket": "Create a new bucket",
"addBucketPlaceholder": "Enter the new bucket title…",
"deleteHeaderBucket": "Delete the bucket",
"deleteBucketText1": "Are you sure you want to delete this bucket?",
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.",
"deleteBucketSuccess": "The bucket has been deleted successfully.",
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.",
"bucketLimitSavedSuccess": "The bucket limit been saved successfully.",
"collapse": "Collapse this bucket"
},
"pseudo": {
"favorites": {
"title": "Favoriten"
"title": "Favorites"
}
}
},
"namespace": {
"title": "Namespaces & Listen",
"title": "Namespaces & Projects",
"namespace": "Namespace",
"showArchived": "Archivierte anzeigen",
"noneAvailable": "Du hast momentan keine Namespaces.",
"unarchive": "Archivierung aufheben",
"archived": "Archiviert",
"noLists": "Dieser Namespace enthält keine Listen.",
"createList": "Erstelle eine neue Liste in diesem Namespace.",
"noProjects": "This namespace does not contain any projects.",
"createProject": "Create a new project in this namespace.",
"namespaces": "Namespaces",
"search": "Beginne zu schreiben, um einen Namespace zu suchen…",
"create": {
"title": "Neuer Namespace",
"titleRequired": "Bitte gebe einen Titel an.",
"explanation": "Ein Namespace ist eine Sammlung von Listen, die du teilen und zur Organisation verwenden kannst. Jede Liste gehört zu einem Namespace.",
"explanation": "A namespace is a collection of projects you can share and use to organize your projects with. In fact, every project belongs to a namespace.",
"tooltip": "Was ist ein Namespace?",
"success": "Der Namespace wurde erfolgreich erstellt."
},
"archive": {
"titleArchive": "„{namespace}“ archivieren",
"titleUnarchive": "Archivierung von \"{namespace}\" aufheben",
"archiveText": "Du kannst diesen Namespace nicht mehr bearbeiten oder neue Listen erstellen, bis du die Archivierung rückgängig machst. Das gilt auch für alle Listen in diesem Namespace.",
"unarchiveText": "Du kannst neue Aufgaben erstellen oder diese bearbeiten.",
"archiveText": "You won't be able to edit this namespace or create new projects until you un-archive it. This will also archive all projects in this namespace.",
"unarchiveText": "You will be able to create new projects or edit it.",
"success": "Der Namespace wurde erfolgreich archiviert.",
"unarchiveSuccess": "Der Namespace wurde erfolgreich wiederhergestellt.",
"description": "In einem archivierten Namespace können Listen weder angelegt noch editiert werden."
"description": "If a namespace is archived, you cannot create new projects or edit it."
},
"delete": {
"title": "„{namespace}“ löschen",
"text1": "Diesen Namespace mit sämtlichem Inhalt löschen?",
"text2": "Dies umfasst alle Listen und Aufgaben und kann NICHT rückgängig gemacht werden!",
"text2": "This includes all projects and tasks and CANNOT BE UNDONE!",
"success": "Der Namespace wurde erfolgreich gelöscht."
},
"edit": {
@ -371,8 +371,8 @@
"isArchived": "Dieser Namespace ist archiviert"
},
"pseudo": {
"sharedLists": {
"title": "Geteilte Listen"
"sharedProjects": {
"title": "Shared Projects"
},
"favorites": {
"title": "Favoriten"
@ -403,8 +403,9 @@
},
"create": {
"title": "Neuer gespeicherter Filter",
"description": "Ein gespeicherter Filter ist eine virtuelle Liste, die bei jedem Zugriff aus einem Satz von Filtern errechnet wird. Einmal erstellt, erscheint diese in einem speziellen Namespace.",
"action": "Neuen gespeicherten Filter erstellen"
"description": "A saved filter is a virtual project which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
"action": "Neuen gespeicherten Filter erstellen",
"titleRequired": "Bitte gib den Titel für den Filter an."
},
"delete": {
"header": "Diesen gespeicherten Filter löschen",
@ -434,7 +435,7 @@
"label": {
"title": "Labels",
"manage": "Label verwalten",
"description": "Klicke auf ein Label um es zu editieren. Du kannst alle Labels, welche du erstellt hast, editieren. Du kannst alle Labels, welche mit einer Aufgabe, auf die du Zugriff hast, benutzen.",
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose project you have access.",
"newCTA": "Du hast momentan keine Labels.",
"search": "Beginne zu schreiben, um nach einem Label zu suchen…",
"create": {
@ -459,7 +460,7 @@
},
"sharing": {
"authenticating": "Authentifizierung …",
"passwordRequired": "Diese geteilte Liste benötigt ein Passwort. Bitte gebe es unten ein:",
"passwordRequired": "This shared project requires a password. Please enter it below:",
"error": "Es ist ein Fehler aufgetreten.",
"invalidPassword": "Das Passwort ist ungültig."
},
@ -601,6 +602,7 @@
"addReminder": "Eine Erinnerung hinzufügen…",
"doneSuccess": "Die Aufgabe wurde erfolgreich als erledigt markiert.",
"undoneSuccess": "Die Aufgabe wurde erfolgreich als nicht-erledigt markiert.",
"undo": "Rückgängig",
"openDetail": "Aufgabe in der Detailansicht anzeigen",
"checklistTotal": "{checked} von {total} Aufgaben",
"checklistAllDone": "{total} Aufgaben",
@ -617,7 +619,7 @@
"chooseDueDate": "Klicke hier, um ein Fälligkeitsdatum zu setzen",
"chooseStartDate": "Klicke hier, um ein Startdatum zu setzen",
"chooseEndDate": "Klicke hier, um ein Enddatum zu setzen",
"move": "Aufgabe in eine andere Liste verschieben",
"move": "Move task to a different project",
"done": "Als erledigt markieren!",
"undone": "Als nicht erledigt markieren",
"created": "Erstellt {0} von {1}",
@ -625,7 +627,7 @@
"doneAt": "Erledigt {0}",
"updateSuccess": "Die Aufgabe wurde erfolgreich gespeichert.",
"deleteSuccess": "Die Aufgabe wurde erfolgreich gelöscht.",
"belongsToList": "Diese Aufgabe gehört zur Liste „{list}“",
"belongsToProject": "This task belongs to project '{project}'",
"due": "Fällig {at}",
"closePopup": "Popup schließen",
"delete": {
@ -645,7 +647,7 @@
"percentDone": "Fortschritt einstellen",
"attachments": "Anhänge hinzufügen",
"relatedTasks": "Beziehung hinzufügen",
"moveList": "Verschieben",
"moveProject": "Move",
"color": "Farbe setzen",
"delete": "Löschen",
"favorite": "Zu Favoriten hinzufügen",
@ -672,21 +674,21 @@
"updated": "Aktualisiert"
},
"subscription": {
"subscribedListThroughParentNamespace": "Du kannst hier nicht de-abonnieren, da du diese Liste über ihren Namespace abonniert hast.",
"subscribedProjectThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this project through its namespace.",
"subscribedTaskThroughParentNamespace": "Du kannst hier nicht de-abonnieren, da du diese Aufgabe über ihren Namespace abonniert hast.",
"subscribedTaskThroughParentList": "Du kannst hier nicht de-abonnieren, da du diese Aufgabe über ihre Liste abonniert hast.",
"subscribedTaskThroughParentProject": "You can't unsubscribe here because you are subscribed to this task through its project.",
"subscribedNamespace": "Du hast diesen Namespace abonniert und erhältst Benachrichtigungen über Änderungen.",
"notSubscribedNamespace": "Du hast diesen Namespace nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
"subscribedList": "Du hast diese Liste abonniert und erhältst Benachrichtigungen über Änderungen.",
"notSubscribedList": "Du hast diese Liste nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
"subscribedProject": "You are currently subscribed to this project and will receive notifications for changes.",
"notSubscribedProject": "You are not subscribed to this project and won't receive notifications for changes.",
"subscribedTask": "Du hast diese Aufgabe abonniert und erhältst Benachrichtigungen über Änderungen.",
"notSubscribedTask": "Du hast diese Aufgabe nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
"subscribe": "Abonnieren",
"unsubscribe": "Abbestellen",
"subscribeSuccessNamespace": "Du hast diesen Namespace jetzt abonniert",
"unsubscribeSuccessNamespace": "Du hast diesen Namespace jetzt nicht mehr abonniert",
"subscribeSuccessList": "Du hast diese Liste jetzt abonniert",
"unsubscribeSuccessList": "Du hast diese Liste jetzt nicht mehr abonniert",
"subscribeSuccessProject": "You are now subscribed to this project",
"unsubscribeSuccessProject": "You are now unsubscribed to this project",
"subscribeSuccessTask": "Du hast diese Aufgabe jetzt abonniert",
"unsubscribeSuccessTask": "Du hast diese Aufgabe jetzt nicht mehr abonniert"
},
@ -760,7 +762,7 @@
"new": "Neue Aufgabenbeziehung",
"searchPlaceholder": "Beginne zu schreiben, um eine Aufgabe zu suchen, die als Beziehung hinzugefügt werden soll…",
"createPlaceholder": "Füge diese Aufgabe als neue Aufgabenbeziehung hinzu",
"differentList": "Diese Aufgabe gehört zu einer anderen Liste.",
"differentProject": "This task belongs to a different project.",
"differentNamespace": "Diese Aufgabe gehört zu einem anderen Namespace.",
"noneYet": "Keine Aufgabenbeziehung vorhanden.",
"delete": "Aufgabenbeziehung entfernen",
@ -810,10 +812,10 @@
"priority1": "Um die Priorität einer Aufgabe zu setzen, gibt eine Zahl zwischen 1 und 5 mit einem vorangestellten {prefix} ein.",
"priority2": "Je höher die Zahl, desto höher die Priorität.",
"assignees": "Um die Aufgabe direkt jemandem zuzuweisen, füge vor dem Anmeldenamen der Person ein {prefix} Zeichen ein.",
"list1": "Um eine Liste zu erstellen, in der die Aufgabe erscheinen soll, gib ihren Namen vorangestellt mit einem {prefix} ein.",
"list2": "Dies gibt einen Fehler zurück, wenn die Liste nicht existiert.",
"list3": "Um Leerzeichen zu verwenden, füge einfach ein \" oder ' um den Namen der Liste hinzu.",
"list4": "Zum Beispiel: {prefix}\"Liste mit Leerzeichen\".",
"project1": "To set a project for the task to appear in, enter its name prefixed with {prefix}.",
"project2": "This will return an error if the project does not exist.",
"project3": "To use spaces, simply add a \" or ' around the project name.",
"project4": "For example: {prefix}\"Project with spaces\".",
"dateAndTime": "Datum und Uhrzeit",
"date": "Jedes Datum wird als Enddatum der neuen Aufgabe verwendet. Du kannst Daten in jedem dieser Formate verwenden:",
"dateWeekday": "jeder Wochentag, wird das nächste Datum mit diesem Tag verwenden",
@ -846,19 +848,19 @@
"delete": {
"header": "Team löschen",
"text1": "Bist du sicher, dass du dieses Team und alle seine Mitglieder löschen willst?",
"text2": "Alle Teammitglieder verlieren den Zugriff auf Listen und Namespaces, die mit diesem Team geteilt sind. Dies KANN NICHT Rückgängig gemacht werden!",
"text2": "All team members will lose access to projects and namespaces shared with this team. This CANNOT BE UNDONE!",
"success": "Das Team wurde erfolgreich gelöscht."
},
"deleteUser": {
"header": "Benutzer:innen aus dem Team entfernen",
"text1": "Bist du sicher, dass du diese:n Benutzer:in aus dem Team entfernen willst?",
"text2": "Diese:r Benutzer:in verliert den Zugriff auf alle Listen und Namespaces auf die dieses Team Zugriff hat. Dies kann nicht rückgängig gemacht werden!",
"text2": "They will lose access to all projects and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "Der:die Benutzer:in wurde erfolgreich aus dem Team gelöscht."
},
"leave": {
"title": "Team verlassen",
"text1": "Bist du sicher, dass du dieses Team verlassen willst?",
"text2": "Du wirst Zugriff auf alle Listen und Namespaces verlieren, auf die dieses Team Zugriff hat. Wenn du deine Meinung änderst, musst du durch einen Team-Admin wieder hinzugefügt werden.",
"text2": "You will lose access to all projects and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "Du hast das Team erfolgreich verlassen."
}
},
@ -890,22 +892,22 @@
"attachment": "Einen Anhang dieser Aufgabe hinzufügen",
"related": "Ändere die Abhängigen Aufgaben dieser Aufgabe",
"color": "Die Farbe dieser Aufgabe ändern",
"move": "Diese Aufgabe in eine andere Liste verschieben",
"move": "Move this task to another project",
"reminder": "Erinnerungen für diese Aufgabe verwalten",
"description": "Aufgabenbeschreibung bearbeiten"
},
"list": {
"title": "Listenansicht",
"switchToListView": "Zur Listenansicht wechseln",
"switchToGanttView": "Zur Ganttansicht wechseln",
"switchToKanbanView": "Zur Kanbanansicht wechseln",
"switchToTableView": "Zur Tabellenansicht wechseln"
"project": {
"title": "Project Views",
"switchToListView": "Switch to list view",
"switchToGanttView": "Switch to gantt view",
"switchToKanbanView": "Switch to kanban view",
"switchToTableView": "Switch to table view"
},
"navigation": {
"title": "Navigation",
"overview": "Die Startseite aufrufen",
"upcoming": "Anstehende Aufgaben aufrufen",
"namespaces": "Namespaces & Listen aufrufen",
"namespaces": "Navigate to namespaces & projects",
"labels": "Labels aufrufen",
"teams": "Teams aufrufen"
}
@ -922,7 +924,7 @@
"unarchive": "Archivierung aufheben",
"setBackground": "Hintergrund einstellen",
"share": "Teilen",
"newList": "Neue Liste"
"newProject": "New project"
},
"apiConfig": {
"url": "Vikunja-URL",
@ -941,24 +943,24 @@
"notification": {
"title": "Benachrichtigungen",
"none": "Du hast keine Benachrichtigungen. Einen schönen Tag noch!",
"explainer": "Benachrichtigungen werden hier angezeigt, wenn Aktionen für Namespaces, Listen oder Aufgaben, die du abonniert hast, ausgeführt werden."
"explainer": "Notifications will appear here when actions on namespaces, projects or tasks you subscribed to happen."
},
"quickActions": {
"commands": "Befehle",
"placeholder": "Gib einen Befehl oder eine Suche ein …",
"hint": "Du kannst {list} verwenden, um die Suche auf eine Liste zu beschränken. Kombiniere {list} oder {label} (Labels) mit einer Suchabfrage, um eine Aufgabe mit diesen Labels oder auf dieser Liste zu suchen. Verwende {assignee}, um nur nach Teams zu suchen.",
"hint": "You can use {project} to limit the search to a project. Combine {project} or {label} (labels) with a search query to search for a task with these labels or on that project. Use {assignee} to only search for teams.",
"tasks": "Aufgaben",
"lists": "Listen",
"projects": "Projects",
"teams": "Teams",
"newList": "Gib den Titel der neuen Liste ein …",
"newProject": "Enter the title of the new project…",
"newTask": "Gib den Titel der neuen Aufgabe ein …",
"newNamespace": "Gib den Titel des neuen Namespaces ein…",
"newTeam": "Gib den Namen des neuen Teams ein …",
"createTask": "Eine Aufgabe in der aktuellen Liste erstellen ({title})",
"createList": "Eine Liste im aktuellen Namespace erstellen ({title})",
"createTask": "Create a task in the current project ({title})",
"createProject": "Create a project in the current namespace ({title})",
"cmds": {
"newTask": "Neue Aufgabe",
"newList": "Neue Liste",
"newProject": "New project",
"newNamespace": "Neuer Namespace",
"newTeam": "Neues Team"
}
@ -990,15 +992,15 @@
"1018": "Die Avatareinstellungen sind falsch.",
"2001": "Die ID kann nicht leer oder 0 sein.",
"2002": "Ein Teil der Anfragedaten ist ungültig.",
"3001": "Die Liste existiert nicht.",
"3004": "Du musst Leseberechtigungen für diese Liste haben, um diese Aktion ausführen zu können.",
"3005": "Der Listentitel darf nicht leer sein.",
"3006": "Diese Linkfreigabe existiert nicht.",
"3007": "Eine Liste mit diesem Bezeichner existiert bereits.",
"3008": "Diese Liste ist archiviert und kann deshalb nur gelesen werden. Dies gilt auch für alle Aufgaben, die mit dieser Liste verbunden sind.",
"4001": "Der Aufgabentitel kann nicht leer sein.",
"4002": "Diese Aufgabe existiert nicht.",
"4003": "Alle Massenbearbeitungen an Aufgaben müssen zu derselben Liste gehören.",
"3001": "The project does not exist.",
"3004": "You need to have read permissions on that project to perform that action.",
"3005": "The project title cannot be empty.",
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Es benötigt mindestens einen Task, um eine Massenänderung durchzuführen.",
"4005": "Du hast keine Berechtigungen, um diese Aufgabe anzuzeigen.",
"4006": "Du kannst die übergeordnete Aufgabe nicht auf sich selbst referenzieren.",
@ -1024,21 +1026,21 @@
"5012": "Dieser Namespace ist archiviert und kann deshalb nur gelesen werden.",
"6001": "Der Teamname kann nicht leer sein.",
"6002": "Das Team existiert nicht.",
"6004": "Das Team hat bereits Zugriff auf diesen Namespace oder diese Liste.",
"6004": "The team already has access to that namespace or project.",
"6005": "Diese:r Benutzer:in ist bereits dem Team beigetreten.",
"6006": "Du kannst den:die letzten Benutzer:in dieses Teams nicht löschen.",
"6007": "Das Team hat keine Berechtigungen auf dieser Liste, um das durchzuführen.",
"7002": "Der:die Benutzer:in hat bereits Zugriff auf diese Liste.",
"7003": "Du hast keinen Zugriff auf diese Liste.",
"6007": "The team does not have access to the project to perform that action.",
"7002": "The user already has access to that project.",
"7003": "You do not have access to that project.",
"8001": "Dieses Label existiert bereits auf dieser Aufgabe.",
"8002": "Das Label existiert nicht.",
"8003": "Du hast keinen Zugriff auf dieses Label.",
"9001": "Das Recht ist ungültig.",
"10001": "Diese Spalte existiert nicht.",
"10002": "Diese Spalte gehört nicht zu dieser Liste.",
"10003": "Du kannst die letze Spalte in einer Liste nicht entfernen.",
"10002": "The bucket does not belong to that project.",
"10003": "You cannot remove the last bucket on a project.",
"10004": "Du kannst die Aufgabe nicht in diese Spalte legen, da sie schon die maximale Anzahl an Aufgaben enthält.",
"10005": "Es kann nur eine Erledigt-Spalte pro Liste geben.",
"10005": "There can be only one done bucket per project.",
"11001": "Der gespeicherte Filter existiert nicht.",
"11002": "Gespeicherte Ansichten sind für Linkfreigaben nicht verfügbar.",
"12001": "Der Abonnement-Typ ist ungültig.",

View File

@ -5,11 +5,11 @@
"welcomeDay": "Hallo {username}!",
"welcomeEvening": "Guten Abend, {username}!",
"lastViewed": "Zletscht ahglueget",
"list": {
"newText": "Du chasch e Liste für dini neue Uufgabe erstelle:",
"new": "Neue Liste",
"importText": "Oder importier dini Liste und Uufgabe us anderne Dienst nach Vikunja:",
"import": "Dini Date in Vikunja importiere"
"project": {
"newText": "Du kannst ein neues Projekt für deine neuen Aufgaben erstellen:",
"new": "New project",
"importText": "Or import your projects and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
"404": {
@ -85,7 +85,7 @@
"weekStartSunday": "Sunntig",
"weekStartMonday": "Määntig",
"language": "Sproch",
"defaultList": "Standard Liste",
"defaultProject": "Default Project",
"timezone": "Zeitzone",
"overdueTasksRemindersTime": "Zeit der E-Mail-Zusammenfassung der überfälligen Aufgaben"
},
@ -143,7 +143,7 @@
},
"deletion": {
"title": "Lösche deinen Vikunja-Account",
"text1": "Das Löschen deines Accounts ist dauerhaft und unwiderruflich. Alle Namensräume, Listen und zugehörige Daten werden gelöscht.",
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, projects, tasks and everything associated with it.",
"text2": "Zum Fortfahren gib bitte dein Passwort ein. Du erhältst eine E-Mail mit weiteren Anweisungen.",
"confirm": "Meinen Account löschen",
"requestSuccess": "Die Anfrage war erfolgreich. Du erhältst eine E-Mail mit weiteren Anweisungen.",
@ -157,201 +157,201 @@
},
"export": {
"title": "Exportiere deine Vikunja-Daten",
"description": "Du kannst eine Kopie deiner Daten bei Vikunja anfordern. Dazu gehören Namespaces, Listen, Aufgaben und alles, was damit zusammenhängt. Du kannst diese Daten dann in jeder Vikunja-Instanz über die Migrationsfunktion importieren.",
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Projects, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
"descriptionPasswordRequired": "Bitte gib dein Passwort ein, um fortzufahren:",
"request": "Eine Kopie meiner Vikunja Daten anfordern",
"success": "Du hast deine Daten bei Vikunja erfolgreich angefordert! Wir schicken dir eine E-Mail, sobald sie zum Download bereitstehen.",
"downloadTitle": "Lade deine exportierten Vikunja-Daten herunter"
}
},
"list": {
"archived": "Diese Liste ist archiviert. Es ist nicht möglich, neue Aufgaben zu erstellen oder sie zu bearbeiten.",
"title": "Liste Titl",
"color": "Farb",
"lists": "Listene",
"list": {
"title": "Liste",
"add": "Hinzuefüege",
"addPlaceholder": "E neui Uufgab erstelle…",
"empty": "D'Liste isch momentan leer.",
"newTaskCta": "Neui Uufgab erstelle.",
"editTask": "Uufgab bearbeite"
},
"search": "Schriib, um nachere Liste z'sueche…",
"searchSelect": "Druck uf Enter um die Liste uuszwähle",
"shared": "Teilti Liste",
"noDescriptionAvailable": "Keine Listenbeschreibung verfügbar.",
"project": {
"archived": "This project is archived. It is not possible to create new or edit tasks for it.",
"title": "Project Title",
"color": "Color",
"projects": "Projects",
"search": "Type to search for a project…",
"searchSelect": "Click or press enter to select this project",
"shared": "Shared Projects",
"noDescriptionAvailable": "No project description is available.",
"create": {
"header": "Neue Liste",
"titlePlaceholder": "Listetitl da ahgeh…",
"addTitleRequired": "Bitte gib en Titl ah.",
"createdSuccess": "Liste erfolgriich erstellt.",
"addListRequired": "Bitte gib e Liste ah oder definier e standard Liste i de Iihstellige."
"header": "New project",
"titlePlaceholder": "The project's title goes here…",
"addTitleRequired": "Please specify a title.",
"createdSuccess": "The project was successfully created.",
"addProjectRequired": "Please specify a project or set a default project in the settings."
},
"archive": {
"title": "\"{list}\" archiviere",
"archive": "Die Liste archiviere",
"unarchive": "Die Liste nüm archiviere",
"unarchiveText": "Du chasch neui Uufgabe erstelle oder bearbeite.",
"archiveText": "Du chasch kei neui Uufgabe erstelle oder bearbeite bis du das nüm archiviert häsch.",
"success": "Die Liste isch erfolgriich archiviert worde."
"title": "Archive \"{project}\"",
"archive": "Archive this project",
"unarchive": "Un-Archive this project",
"unarchiveText": "You will be able to create new tasks or edit it.",
"archiveText": "You won't be able to edit this project or create new tasks until you un-archive it.",
"success": "The project was successfully archived."
},
"background": {
"title": "Listehintergrund setze",
"remove": "Listehintergrund entferne",
"upload": "Wähl en Hintergrund vo diim Pc uus",
"searchPlaceholder": "Suech nach emne Hintergrund…",
"poweredByUnsplash": "Unterstützt vo Unsplash",
"loadMore": "Meh Fotis ladä",
"success": "De Hintergrund isch erfolgriich gsetzt worde!",
"removeSuccess": "De Hintergrund isch erfolgriich entfernt worde!"
"title": "Set project background",
"remove": "Remove Background",
"upload": "Choose a background from your pc",
"searchPlaceholder": "Search for a background…",
"poweredByUnsplash": "Powered by Unsplash",
"loadMore": "Load more photos",
"success": "The background has been set successfully!",
"removeSuccess": "The background has been removed successfully!"
},
"delete": {
"title": "\"{list}\" chüble",
"header": "Die Liste chüble",
"text1": "Bisch du dir sicher, dass du die Liste und all ihren Inhalt chüble wetsch?",
"text2": "Das beinhalltet alli Uufgabe und CHAN NID RÜCKGÄNGIG GMACHT WERDE!",
"success": "Die Liste isch erfolgriich g'chüblet wore.",
"tasksToDelete": "Dies löscht unwiderruflich ca. {count} Aufgaben.",
"noTasksToDelete": "Diese Liste enthält keine Aufgaben, sie kann sicher gelöscht werden."
"title": "Delete \"{project}\"",
"header": "Delete this project",
"text1": "Are you sure you want to delete this project and all of its contents?",
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
"title": "Die Liste dubliziere",
"label": "Dubliziere",
"text": "Wähl en Namensruum uus, wo die dubliziert Liste hii set gah:",
"success": "D'Liste isch erfolgriich dubliziert wore."
"title": "Duplicate this project",
"label": "Duplicate",
"text": "Select a namespace which should hold the duplicated project:",
"success": "The project was successfully duplicated."
},
"edit": {
"header": "Die Liste bearbeite",
"title": "\"{list}\" bearbeite",
"titlePlaceholder": "Listetitl da ahgeh…",
"identifierTooltip": "De Liste Identifikator chan benutzt werde, um Uufgabe differenzierbar über mehreri Liste z'erkenne. Du chasch das Feld leer lah, um die Funktion z'deaktiviere.",
"identifier": "Liste Identifikator",
"identifierPlaceholder": "Listeidentifikator da ahgeh…",
"description": "Beschriibig",
"descriptionPlaceholder": "D'Listebeschriibig chunt da hane…",
"color": "Farb",
"success": "D'Liste isch erfolgriich aktualisiert wore."
"header": "Edit This Project",
"title": "Edit \"{project}\"",
"titlePlaceholder": "The project title goes here…",
"identifierTooltip": "The project identifier can be used to uniquely identify a task across projects. You can set it to empty to disable it.",
"identifier": "Project Identifier",
"identifierPlaceholder": "The project identifier goes here…",
"description": "Description",
"descriptionPlaceholder": "The projects description goes here…",
"color": "Color",
"success": "The project was successfully updated."
},
"share": {
"header": "Die Liste teile",
"title": "\"{list}\" teile",
"share": "Teilä",
"header": "Share this project",
"title": "Share \"{project}\"",
"share": "Share",
"links": {
"title": "Link teile",
"what": "Was isch en teilte Link?",
"explanation": "En Teilte Link git dir d'möglichkeit, e Liste mit anderne Benutzer wo kein Vikunja account hend, z'teile.",
"create": "En neue teilte Link erstelle",
"name": "Namä (optional)",
"namePlaceholder": "z.B. Reto Häberli",
"nameExplanation": "Alli aktione, wo mit dem teilte Link gmacht werdet, werded mit dem Name hinterleit.",
"password": "Passwort (optional)",
"passwordExplanation": "De Benutzer wird um es Passwort bättet, wenn er dadruf zuegriift.",
"noName": "Kein Name g'setzt",
"remove": "Linkfreigab lösche",
"removeText": "Bisch du sicher, dass du die Freigab lösche wetsch? Es wird nüme möglich sii, die Liste mit dem Link z'erreiche. Das chann nid rückgängig gmacht werde!",
"createSuccess": "De Freigabelink isch erfolgriich erstellt wore.",
"deleteSuccess": "De g'teilti Link isch erfolgriich g'chüblet wore",
"view": "Ansicht",
"sharedBy": "Von {0} geteilt"
"title": "Share Links",
"what": "What is a share link?",
"explanation": "Share Links allow you to easily share a project with other users who don't have an account on Vikunja.",
"create": "Create a new link share",
"name": "Name (optional)",
"namePlaceholder": "e.g. Lorem Ipsum",
"nameExplanation": "All actions done by this link share will show up with the name.",
"password": "Password (optional)",
"passwordExplanation": "When authenticating, the user will be required to enter this password.",
"noName": "No name set",
"remove": "Remove a link share",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this project with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted",
"view": "View",
"sharedBy": "Shared by {0}"
},
"userTeam": {
"typeUser": "Benutzer | Benutzer",
"typeTeam": "Team | Teams",
"shared": "Mit dene {type} teilt",
"you": "Du",
"notShared": "Nid mit {type} teilt.",
"removeHeader": "{type} vo dere {sharable} entferne",
"removeText": "Bisch du sicher du wersch de {sharable} vo dem {type} entferne? Das cha nid rückgängig gmacht werde!",
"removeSuccess": "De {sharable} isch erfolgriich vom {type} entfernt wore.",
"addedSuccess": "De {type} isch erfolgriich hinzuegfüegt wore.",
"updatedSuccess": "De {type} isch erfolgriich hinzuegfüegt wore."
"typeUser": "user | users",
"typeTeam": "team | teams",
"shared": "Shared with these {type}",
"you": "You",
"notShared": "Not shared with any {type} yet.",
"removeHeader": "Remove a {type} from the {sharable}",
"removeText": "Are you sure you want to remove this {sharable} from the {type}? This cannot be undone!",
"removeSuccess": "The {sharable} was successfully removed from the {type}.",
"addedSuccess": "The {type} was successfully added.",
"updatedSuccess": "The {type} was successfully added."
},
"right": {
"title": "Berechtigung",
"read": "Nur Lese",
"readWrite": "Lese und Schriibe",
"admin": "Chef"
"title": "Permission",
"read": "Read only",
"readWrite": "Read & write",
"admin": "Admin"
},
"attributes": {
"link": "Link",
"delete": "Chüble"
"delete": "Delete"
}
},
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This project is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": {
"title": "Gantt",
"showTasksWithoutDates": "Zeig Uufgabe, wo kei Date hend",
"size": "Grössi",
"default": "Standard",
"month": "Monet",
"day": "Taag",
"hour": "Stunde",
"range": "Zeitraum",
"noDates": "Die Uufgab het no kei Datum gsetzt."
"showTasksWithoutDates": "Show tasks which don't have dates set",
"size": "Size",
"default": "Default",
"month": "Month",
"day": "Day",
"hour": "Hour",
"range": "Date Range",
"noDates": "This task has no dates set."
},
"table": {
"title": "Tabällä",
"columns": "Spaltä"
"title": "Table",
"columns": "Columns"
},
"kanban": {
"title": "Kanban",
"limit": "Limitä: {limit}",
"noLimit": "Nid gsetzt",
"doneBucket": "Fertig-Chübl",
"doneBucketHint": "Alli Uufgabe wo i de Chübl gworfe werded, sind automatisch als Färtig markiert.",
"doneBucketHintExtended": "Alli Uufgabe wo i de Chübl gworfe werded, sind automatisch als Färtig markiert. Alli Uufgabe wo vo irgendwo anders als Färtig markiert worde sind, werded au dahii gschobe.",
"doneBucketSavedSuccess": "De Färtig-Chübl isch erfolgriich gspeicheret worde.",
"deleteLast": "Du chasch de letscht Chübl nid entferne.",
"addTaskPlaceholder": "Neue Uufgabe Titl hinzuefüege…",
"addTask": "Uufgab hinzuefüegä",
"addAnotherTask": "Nomol e Uufgab hinzuefüege",
"addBucket": "Neue Chübl erstelle",
"addBucketPlaceholder": "Gib en neue Chübl-Name iih…",
"deleteHeaderBucket": "De Chübl chüblä",
"deleteBucketText1": "Bisch du dir sicher, dass du de Chübl chüblä wetsch?",
"deleteBucketText2": "Das löscht kei Uufgabe, aber wirft si in Standard-Chübl.",
"deleteBucketSuccess": "De Chübl isch erfolgriich g'chüblet worde.",
"bucketTitleSavedSuccess": "De Chübl Name isch erfolgriich gspeicheret worde.",
"bucketLimitSavedSuccess": "Das Chübl-Limit isch erfolgriich gspeicheret worde.",
"collapse": "De Chübl zemeklappe"
"limit": "Limit: {limit}",
"noLimit": "Not Set",
"doneBucket": "Done bucket",
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.",
"doneBucketHintExtended": "All tasks moved into the done bucket will be marked as done automatically. All tasks marked as done from elsewhere will be moved as well.",
"doneBucketSavedSuccess": "The done bucket has been saved successfully.",
"deleteLast": "You cannot remove the last bucket.",
"addTaskPlaceholder": "Enter the new task title…",
"addTask": "Add a task",
"addAnotherTask": "Add another task",
"addBucket": "Create a new bucket",
"addBucketPlaceholder": "Enter the new bucket title…",
"deleteHeaderBucket": "Delete the bucket",
"deleteBucketText1": "Are you sure you want to delete this bucket?",
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.",
"deleteBucketSuccess": "The bucket has been deleted successfully.",
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.",
"bucketLimitSavedSuccess": "The bucket limit been saved successfully.",
"collapse": "Collapse this bucket"
},
"pseudo": {
"favorites": {
"title": "Favoriite"
"title": "Favorites"
}
}
},
"namespace": {
"title": "Namensrüüm und Listene",
"title": "Namespaces & Projects",
"namespace": "Namensruum",
"showArchived": "Archivierti aahzeige",
"noneAvailable": "Du hesch momentan kein Namensruuim.",
"unarchive": "Ent-archiviere",
"archived": "Archiviert",
"noLists": "De Namensruum het kei Listene drin.",
"createList": "E neui Liste i dem Namensruum erstelle.",
"noProjects": "This namespace does not contain any projects.",
"createProject": "Create a new project in this namespace.",
"namespaces": "Namensrüüm",
"search": "Schriib, um nachemne Namensruum z'sueche…",
"create": {
"title": "Neuer Namespace",
"titleRequired": "Bitte gib en Titl ah.",
"explanation": "Ein Namespace ist eine Sammlung von Listen, die du teilen und zur Organisation verwenden kannst. Jede Liste gehört zu einem Namespace.",
"explanation": "A namespace is a collection of projects you can share and use to organize your projects with. In fact, every project belongs to a namespace.",
"tooltip": "Was isch en Namensruum?",
"success": "Namensruum erstellt."
},
"archive": {
"titleArchive": "\"{namespace}\" archiviere",
"titleUnarchive": "\"{namespace}\" ent-archiviere",
"archiveText": "Du hesch kei möglichkeit meh de Namensruum z'bearbeite oder neui Listene drin z'erstelle, bis du si wider ent-archiviert hesch. Das archiviert au grad alli Liste im Namensruum.",
"unarchiveText": "Du chasch neui Liste erstelle oder bearbeite.",
"archiveText": "You won't be able to edit this namespace or create new projects until you un-archive it. This will also archive all projects in this namespace.",
"unarchiveText": "You will be able to create new projects or edit it.",
"success": "De Namensruum isch erfolgriich archiviert worde.",
"unarchiveSuccess": "Der Namespace wurde erfolgreich wiederhergestellt.",
"description": "Wenn en Namensruum archiviert isch, chasch du kei neui Liste erstelle oder die bearbeite."
"description": "If a namespace is archived, you cannot create new projects or edit it."
},
"delete": {
"title": "\"{namespace}\" chüble",
"text1": "Bisch du dir sicher, dass du de Namensruum und all ihren Inhalt chüble wetsch?",
"text2": "Das beinhalltet alli Liste und Uufgabe und CHAN NID RÜCKGÄNGIG GMACHT WERDE!",
"text2": "This includes all projects and tasks and CANNOT BE UNDONE!",
"success": "Namensruum g'chüblet."
},
"edit": {
@ -371,8 +371,8 @@
"isArchived": "De Namensruum isch archiviert"
},
"pseudo": {
"sharedLists": {
"title": "G'Teilti Liste"
"sharedProjects": {
"title": "Shared Projects"
},
"favorites": {
"title": "Favorite"
@ -403,8 +403,9 @@
},
"create": {
"title": "Neuer gespeicherter Filter",
"description": "En gspeicherete Filter isch e virtuelli Liste, welche vomene Satz a Filter zemmegsetzt wird, sobald me uf sie zuegriift. Wenn sie mal erstellt worde isch, erhaltet si ihren eigene Namensruum.",
"action": "Neue gspeicherete Filter erstelle"
"description": "A saved filter is a virtual project which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
"action": "Neue gspeicherete Filter erstelle",
"titleRequired": "Bitte gib den Titel für den Filter an."
},
"delete": {
"header": "De g'speicheret Filter chüble",
@ -434,7 +435,7 @@
"label": {
"title": "Labels",
"manage": "Label migriere",
"description": "Klick uf es Label ums z'editiere. Du chasch alli Labels, wo du erstellt hesch editiere. Du chasch au uf alli Labels zuegriife, wo ufere Liste sind, i dere du Zuegriff hesch.",
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose project you have access.",
"newCTA": "Du hesch momentan kei Labels.",
"search": "Schriib, um nachemne Label z'sueche…",
"create": {
@ -459,7 +460,7 @@
},
"sharing": {
"authenticating": "Authentifiziere…",
"passwordRequired": "Die teilt Liste bruucht es Passwort. Bitte gibs une ah:",
"passwordRequired": "This shared project requires a password. Please enter it below:",
"error": "Het en Fähler geh. :(",
"invalidPassword": "Da Passwort isch ungültig."
},
@ -528,7 +529,7 @@
"code": "Code",
"quote": "Zitaat",
"unorderedList": "Ungordnedi Listä",
"orderedList": "Gordneti Listä",
"orderedList": "Geordnete Liste",
"cleanBlock": "Formatierig Lösche",
"link": "Link",
"image": "Bild",
@ -601,6 +602,7 @@
"addReminder": "Neui Errinnerig erstelle…",
"doneSuccess": "Die Uufgab isch erfolgriich als \"Fertig\" markiert wordä.",
"undoneSuccess": "Die Uufgaab isch nüme als fertig markiert.",
"undo": "Rückgängig",
"openDetail": "Uufgab i de Detailaahsicht öffne",
"checklistTotal": "{checked} von {total} Aufgaben",
"checklistAllDone": "{total} Aufgaben",
@ -617,7 +619,7 @@
"chooseDueDate": "Druck da, um es Fälligkeitsdatum z'setze",
"chooseStartDate": "Druck dah, um es Startdatum z'setze",
"chooseEndDate": "Druck da, um es Enddatum z'setze",
"move": "Schieb die Uufgab in e anderi Liste",
"move": "Move task to a different project",
"done": "Als erledigt markieren!",
"undone": "Als unerledigt markierä",
"created": "Erstellt am {0} vo {1}",
@ -625,7 +627,7 @@
"doneAt": "{0} erledigt",
"updateSuccess": "Die Uufgab isch erfolgriich g'speichered wore.",
"deleteSuccess": "Die Uufgab isch erfolgriich g'chüblet wore.",
"belongsToList": "Die Uufgab ghlrt zu de \"{list}\" Liste",
"belongsToProject": "This task belongs to project '{project}'",
"due": "Fällig bis {at}",
"closePopup": "Popup schließen",
"delete": {
@ -645,7 +647,7 @@
"percentDone": "Fortschritt einstellen",
"attachments": "Anhänge hinzufügen",
"relatedTasks": "Beziehung hinzufügen",
"moveList": "Verschieben",
"moveProject": "Move",
"color": "Farbe setzen",
"delete": "Löschen",
"favorite": "Zu Favoriten hinzufügen",
@ -672,21 +674,21 @@
"updated": "Aktualisiert"
},
"subscription": {
"subscribedListThroughParentNamespace": "Du kannst hier nicht de-abonnieren, da du diese Liste über ihren Namespace abonniert hast.",
"subscribedProjectThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this project through its namespace.",
"subscribedTaskThroughParentNamespace": "Du kannst hier nicht de-abonnieren, da du diese Aufgabe über ihren Namespace abonniert hast.",
"subscribedTaskThroughParentList": "Du kannst hier nicht de-abonnieren, da du diese Aufgabe über ihre Liste abonniert hast.",
"subscribedTaskThroughParentProject": "You can't unsubscribe here because you are subscribed to this task through its project.",
"subscribedNamespace": "Du hast diesen Namespace abonniert und erhältst Benachrichtigungen über Änderungen.",
"notSubscribedNamespace": "Du hast diesen Namespace nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
"subscribedList": "Du hast diese Liste abonniert und erhältst Benachrichtigungen über Änderungen.",
"notSubscribedList": "Du hast diese Liste nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
"subscribedProject": "You are currently subscribed to this project and will receive notifications for changes.",
"notSubscribedProject": "You are not subscribed to this project and won't receive notifications for changes.",
"subscribedTask": "Du hast diese Aufgabe abonniert und erhältst Benachrichtigungen über Änderungen.",
"notSubscribedTask": "Du hast diese Aufgabe nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
"subscribe": "Abooniere",
"unsubscribe": "Deabonniere",
"subscribeSuccessNamespace": "Du hast diesen Namespace jetzt abonniert",
"unsubscribeSuccessNamespace": "Du hast diesen Namespace jetzt nicht mehr abonniert",
"subscribeSuccessList": "Du hast diese Liste jetzt abonniert",
"unsubscribeSuccessList": "Du hast diese Liste jetzt nicht mehr abonniert",
"subscribeSuccessProject": "You are now subscribed to this project",
"unsubscribeSuccessProject": "You are now unsubscribed to this project",
"subscribeSuccessTask": "Du hast diese Aufgabe jetzt abonniert",
"unsubscribeSuccessTask": "Du hast diese Aufgabe jetzt nicht mehr abonniert"
},
@ -760,7 +762,7 @@
"new": "Neui Uufgabe Beziehig",
"searchPlaceholder": "Schriib, um e neui Uufgab als Zueghörigkeit hinzuezfüege…",
"createPlaceholder": "Das als en neui Zueghörigkeit hinzuefüege",
"differentList": "Die Uufgab ghöört zu ere andere Liste.",
"differentProject": "This task belongs to a different project.",
"differentNamespace": "Diese Aufgabe gehört zu einem anderen Namespace.",
"noneYet": "S'git kei Uufgabe Beziehige.",
"delete": "Uufgabe Beziehig chüble",
@ -810,10 +812,10 @@
"priority1": "Um e Task Priorität z'setze: füeg e nummere zwüsched 1 und 5, mit em {prefix} als Prefix iih.",
"priority2": "Je höher d'nummere, desto höher d'Priorität.",
"assignees": "Um die Aufgabe direkt jemandem zuzuweisen, füge vor dem Anmeldenamen der Person ein {prefix} Zeichen ein.",
"list1": "Um e Liste ahzgeh, wo de Task drin erschiine set, gib de name vo de liste mit emne {prefix} ah.",
"list2": "Das wird en Fehler werfe, wenn d'Liste nid existiert.",
"list3": "Um Leerzeichen zu verwenden, füge einfach ein \" oder ' um den Namen der Liste hinzu.",
"list4": "Zum Beispiel: {prefix}\"Liste mit Leerzeichen\".",
"project1": "To set a project for the task to appear in, enter its name prefixed with {prefix}.",
"project2": "This will return an error if the project does not exist.",
"project3": "To use spaces, simply add a \" or ' around the project name.",
"project4": "For example: {prefix}\"Project with spaces\".",
"dateAndTime": "Datum und Ziit",
"date": "Jedes Datum wird als Abgabedatum für di neu Uufgab gnoh. Du chasch Date i de folgende Format verwende:",
"dateWeekday": "jede Wuchetaag wird nimmt s'negste Datum mit dem Datum",
@ -846,19 +848,19 @@
"delete": {
"header": "Das Team chüble",
"text1": "Bischder sicher, dasst wetsch da Team mit allne Mitglieder lösche?",
"text2": "Alle Teammitglieder verlieren den Zugriff auf Listen und Namespaces, die mit diesem Team geteilt sind. Dies KANN NICHT Rückgängig gemacht werden!",
"text2": "All team members will lose access to projects and namespaces shared with this team. This CANNOT BE UNDONE!",
"success": "Da Team isch erfolgriich g'chüblet wore."
},
"deleteUser": {
"header": "Benutzer usem Team entferne",
"text1": "Bisch du dir sicher, dass du de Benutzer usm Team werfe wetsch?",
"text2": "Diese:r Benutzer:in verliert den Zugriff auf alle Listen und Namespaces auf die dieses Team Zugriff hat. Dies kann nicht rückgängig gemacht werden!",
"text2": "They will lose access to all projects and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "Benutzer erfolgriich usegworfe."
},
"leave": {
"title": "Team verlassen",
"text1": "Bist du sicher, dass du dieses Team verlassen willst?",
"text2": "Du wirst Zugriff auf alle Listen und Namespaces verlieren, auf die dieses Team Zugriff hat. Wenn du deine Meinung änderst, musst du durch einen Team-Admin wieder hinzugefügt werden.",
"text2": "You will lose access to all projects and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "Du hast das Team erfolgreich verlassen."
}
},
@ -890,22 +892,22 @@
"attachment": "En Aahang dere Uufgab hinzuefüege",
"related": "Beziehige vo dere Uufgab bearbeite",
"color": "Die Farbe dieser Aufgabe ändern",
"move": "Diese Aufgabe in eine andere Liste verschieben",
"move": "Move this task to another project",
"reminder": "Erinnerungen für diese Aufgabe verwalten",
"description": "Aufgabenbeschreibung bearbeiten"
},
"list": {
"title": "Listenansicht",
"switchToListView": "Zur Listenansicht wechseln",
"switchToGanttView": "Zur Ganttansicht wechseln",
"switchToKanbanView": "Zur Kanbanansicht wechseln",
"switchToTableView": "Zur Tabellenansicht wechseln"
"project": {
"title": "Project Views",
"switchToListView": "Switch to list view",
"switchToGanttView": "Switch to gantt view",
"switchToKanbanView": "Switch to kanban view",
"switchToTableView": "Switch to table view"
},
"navigation": {
"title": "Navigation",
"overview": "Die Startseite aufrufen",
"upcoming": "Anstehende Aufgaben aufrufen",
"namespaces": "Namespaces & Listen aufrufen",
"namespaces": "Navigate to namespaces & projects",
"labels": "Labels aufrufen",
"teams": "Teams aufrufen"
}
@ -922,7 +924,7 @@
"unarchive": "Ent-archiviere",
"setBackground": "Hintergrund iihstelle",
"share": "Teilä",
"newList": "Neui Listä"
"newProject": "New project"
},
"apiConfig": {
"url": "Vikunja URL",
@ -941,24 +943,24 @@
"notification": {
"title": "Benachrichtigunge",
"none": "Du hesch kei neui Benachrichtunge. Heb e schös Tägli!",
"explainer": "Benachrichtigunge landed do, wenn eppis bi de Namensrüüm, Liste oder Uufgabe passiert, welche du abonniert hesch."
"explainer": "Notifications will appear here when actions on namespaces, projects or tasks you subscribed to happen."
},
"quickActions": {
"commands": "Befehl",
"placeholder": "Schriib en Befehl oder suech…",
"hint": "Du kannst {list} verwenden, um die Suche auf eine Liste zu beschränken. Kombiniere {list} oder {label} (Labels) mit einer Suchabfrage, um eine Aufgabe mit diesen Labels oder auf dieser Liste zu suchen. Verwende {assignee}, um nur nach Teams zu suchen.",
"hint": "You can use {project} to limit the search to a project. Combine {project} or {label} (labels) with a search query to search for a task with these labels or on that project. Use {assignee} to only search for teams.",
"tasks": "Uufgabe",
"lists": "Listene",
"projects": "Projects",
"teams": "Teams",
"newList": "Gib en Titl für die neu Liste iih…",
"newProject": "Enter the title of the new project…",
"newTask": "Gib en Titl für die neu Uufgab iih…",
"newNamespace": "Gib en Titl für de neu Namensruum iih…",
"newTeam": "Gib en Name für da neui Team iih…",
"createTask": "Erstell e Uufgab i de momentane Listä ({title})",
"createList": "Erstell e Liste im momentane Namensruum ({title})",
"createTask": "Create a task in the current project ({title})",
"createProject": "Create a project in the current namespace ({title})",
"cmds": {
"newTask": "Neui Uufgab",
"newList": "Neui Liste",
"newProject": "New project",
"newNamespace": "Neue Namensruum",
"newTeam": "Neus Team"
}
@ -990,15 +992,15 @@
"1018": "Die Benutzer Profilbild Iihstellige sind nid gültig.",
"2001": "ID chann nid leer oder 0 sii.",
"2002": "Ebbis vo de Ahfragedate isch ungültig.",
"3001": "Die Liste giz nid.",
"3004": "Du hesch kei Läseberächtigung für die Liste um das durezfüehre.",
"3005": "De Titl cha nid leer sii.",
"3006": "Die Freigab giz nid.",
"3007": "E Liste mit dere Identifizierig giz scho.",
"3008": "Die Liste isch archiviert und chan desshalb nur glese werde. Das gliiche gilt für alli Uufgabe i dere Liste.",
"4001": "De Liste Uufgabe Titl cha nid leer sii.",
"4002": "Die Liste Uufgab giz nid.",
"4003": "Alli Massebearbeitige a Uufgabe müend i de selbe Liste passiere.",
"3001": "The project does not exist.",
"3004": "You need to have read permissions on that project to perform that action.",
"3005": "The project title cannot be empty.",
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Es bruucht mindestens ei Uufgab, um e Masseänderig durezfüehre.",
"4005": "Du hesch kei Berechtigung, um die Uufgab ahzzeige.",
"4006": "Du chasch kei übergordneti Uufgab uf sich selbst refferenziere.",
@ -1024,21 +1026,21 @@
"5012": "De Namensruum isch momentan schriibgschützt weil er archiviert isch.",
"6001": "Der Teamname kann nicht leer sein.",
"6002": "Da Team giz nid.",
"6004": "Da Team het scho Zuegang zu dem Namensruum oder Liste.",
"6004": "The team already has access to that namespace or project.",
"6005": "De Benutzer isch scho bi dem Team.",
"6006": "Du chasch nid de letschti Benutzer vom Team lösche.",
"6007": "Da Team het kei Zuegriff uf die Liste, um das durezfüehre.",
"7002": "De Benutzer het scho Zuegriff uf die Liste.",
"7003": "Du hesch kei Zuegriff uf die Liste.",
"6007": "The team does not have access to the project to perform that action.",
"7002": "The user already has access to that project.",
"7003": "You do not have access to that project.",
"8001": "Da Label existiert scho für die Uufgab.",
"8002": "Das Label giz nid.",
"8003": "Du hesch kei Zuegriff uf da Label.",
"9001": "Die Berechtigung isch ungültig.",
"10001": "De Chübl gits nid.",
"10002": "De Chübl ghört nid i die Liste.",
"10003": "Du chasch de letscht Chübl nid vo de Liste neh.",
"10002": "The bucket does not belong to that project.",
"10003": "You cannot remove the last bucket on a project.",
"10004": "Du chasch die Uufgab nid dem Chübl zue wiise, weil er d'Limite für Uufgabe erreicht het.",
"10005": "Es chan nur ein Fertig Chübl pro Liste geh.",
"10005": "There can be only one done bucket per project.",
"11001": "De g'speicheret Filter giz nid.",
"11002": "G'speichereti Filter chend nid Teilt werde.",
"12001": "De Abonnement Entitätstyp isch ungültig.",

View File

@ -5,10 +5,10 @@
"welcomeDay": "Hi {username}!",
"welcomeEvening": "Good Evening {username}!",
"lastViewed": "Last viewed",
"list": {
"newText": "You can create a new list for your new tasks:",
"new": "New list",
"importText": "Or import your lists and tasks from other services into Vikunja:",
"project": {
"newText": "You can create a new project for your new tasks:",
"new": "New project",
"importText": "Or import your projects and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
@ -85,7 +85,7 @@
"weekStartSunday": "Sunday",
"weekStartMonday": "Monday",
"language": "Language",
"defaultList": "Default List",
"defaultProject": "Default Project",
"timezone": "Time Zone",
"overdueTasksRemindersTime": "Overdue tasks reminder email time"
},
@ -143,7 +143,7 @@
},
"deletion": {
"title": "Delete your Vikunja Account",
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, lists, tasks and everything associated with it.",
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, projects, tasks and everything associated with it.",
"text2": "To proceed, please enter your password. You will receive an email with further instructions.",
"confirm": "Delete my account",
"requestSuccess": "The request was successful. You'll receive an email with further instructions.",
@ -157,40 +157,39 @@
},
"export": {
"title": "Export your Vikunja data",
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Lists, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Projects, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
"descriptionPasswordRequired": "Please enter your password to proceed:",
"request": "Request a copy of my Vikunja Data",
"success": "You've successfully requested your Vikunja Data! We will send you an email once it's ready to download.",
"downloadTitle": "Download your exported Vikunja data"
}
},
"list": {
"archived": "This list is archived. It is not possible to create new or edit tasks for it.",
"title": "List Title",
"project": {
"archived": "This project is archived. It is not possible to create new or edit tasks for it.",
"title": "Project Title",
"color": "Color",
"lists": "Lists",
"list": "List",
"search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists",
"noDescriptionAvailable": "No list description is available.",
"projects": "Projects",
"search": "Type to search for a project…",
"searchSelect": "Click or press enter to select this project",
"shared": "Shared Projects",
"noDescriptionAvailable": "No project description is available.",
"create": {
"header": "New list",
"titlePlaceholder": "The list's title goes here…",
"header": "New project",
"titlePlaceholder": "The project's title goes here…",
"addTitleRequired": "Please specify a title.",
"createdSuccess": "The list was successfully created.",
"addListRequired": "Please specify a list or set a default list in the settings."
"createdSuccess": "The project was successfully created.",
"addProjectRequired": "Please specify a project or set a default project in the settings."
},
"archive": {
"title": "Archive \"{list}\"",
"archive": "Archive this list",
"unarchive": "Un-Archive this list",
"title": "Archive \"{project}\"",
"archive": "Archive this project",
"unarchive": "Un-Archive this project",
"unarchiveText": "You will be able to create new tasks or edit it.",
"archiveText": "You won't be able to edit this list or create new tasks until you un-archive it.",
"success": "The list was successfully archived."
"archiveText": "You won't be able to edit this project or create new tasks until you un-archive it.",
"success": "The project was successfully archived."
},
"background": {
"title": "Set list background",
"title": "Set project background",
"remove": "Remove Background",
"upload": "Choose a background from your pc",
"searchPlaceholder": "Search for a background…",
@ -200,40 +199,40 @@
"removeSuccess": "The background has been removed successfully!"
},
"delete": {
"title": "Delete \"{list}\"",
"header": "Delete this list",
"text1": "Are you sure you want to delete this list and all of its contents?",
"title": "Delete \"{project}\"",
"header": "Delete this project",
"text1": "Are you sure you want to delete this project and all of its contents?",
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The list was successfully deleted.",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
"noTasksToDelete": "This list does not contain any tasks, it should be safe to delete."
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
"title": "Duplicate this list",
"title": "Duplicate this project",
"label": "Duplicate",
"text": "Select a namespace which should hold the duplicated list:",
"success": "The list was successfully duplicated."
"text": "Select a namespace which should hold the duplicated project:",
"success": "The project was successfully duplicated."
},
"edit": {
"header": "Edit This List",
"title": "Edit \"{list}\"",
"titlePlaceholder": "The list title goes here…",
"identifierTooltip": "The list identifier can be used to uniquely identify a task across lists. You can set it to empty to disable it.",
"identifier": "List Identifier",
"identifierPlaceholder": "The list identifier goes here…",
"header": "Edit This Project",
"title": "Edit \"{project}\"",
"titlePlaceholder": "The project title goes here…",
"identifierTooltip": "The project identifier can be used to uniquely identify a task across projects. You can set it to empty to disable it.",
"identifier": "Project Identifier",
"identifierPlaceholder": "The project identifier goes here…",
"description": "Description",
"descriptionPlaceholder": "The lists description goes here…",
"descriptionPlaceholder": "The projects description goes here…",
"color": "Color",
"success": "The list was successfully updated."
"success": "The project was successfully updated."
},
"share": {
"header": "Share this list",
"title": "Share \"{list}\"",
"header": "Share this project",
"title": "Share \"{project}\"",
"share": "Share",
"links": {
"title": "Share Links",
"what": "What is a share link?",
"explanation": "Share Links allow you to easily share a list with other users who don't have an account on Vikunja.",
"explanation": "Share Links allow you to easily share a project with other users who don't have an account on Vikunja.",
"create": "Create a new link share",
"name": "Name (optional)",
"namePlaceholder": "e.g. Lorem Ipsum",
@ -242,7 +241,7 @@
"passwordExplanation": "When authenticating, the user will be required to enter this password.",
"noName": "No name set",
"remove": "Remove a link share",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this list with this link share. This cannot be undone!",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this project with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted",
"view": "View",
@ -275,7 +274,7 @@
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"empty": "This project is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
@ -323,36 +322,36 @@
}
},
"namespace": {
"title": "Namespaces & Lists",
"title": "Namespaces & Projects",
"namespace": "Namespace",
"showArchived": "Show Archived",
"noneAvailable": "You don't have any namespaces right now.",
"unarchive": "Un-Archive",
"archived": "Archived",
"noLists": "This namespace does not contain any lists.",
"createList": "Create a new list in this namespace.",
"noProjects": "This namespace does not contain any projects.",
"createProject": "Create a new project in this namespace.",
"namespaces": "Namespaces",
"search": "Type to search for a namespace…",
"create": {
"title": "New namespace",
"titleRequired": "Please specify a title.",
"explanation": "A namespace is a collection of lists you can share and use to organize your lists with. In fact, every list belongs to a namespace.",
"explanation": "A namespace is a collection of projects you can share and use to organize your projects with. In fact, every project belongs to a namespace.",
"tooltip": "What's a namespace?",
"success": "The namespace was successfully created."
},
"archive": {
"titleArchive": "Archive \"{namespace}\"",
"titleUnarchive": "Un-Archive \"{namespace}\"",
"archiveText": "You won't be able to edit this namespace or create new lists until you un-archive it. This will also archive all lists in this namespace.",
"unarchiveText": "You will be able to create new lists or edit it.",
"archiveText": "You won't be able to edit this namespace or create new projects until you un-archive it. This will also archive all projects in this namespace.",
"unarchiveText": "You will be able to create new projects or edit it.",
"success": "The namespace was successfully archived.",
"unarchiveSuccess": "The namespace was successfully un-archived.",
"description": "If a namespace is archived, you cannot create new lists or edit it."
"description": "If a namespace is archived, you cannot create new projects or edit it."
},
"delete": {
"title": "Delete \"{namespace}\"",
"text1": "Are you sure you want to delete this namespace and all of its contents?",
"text2": "This includes all lists and tasks and CANNOT BE UNDONE!",
"text2": "This includes all projects and tasks and CANNOT BE UNDONE!",
"success": "The namespace was successfully deleted."
},
"edit": {
@ -372,8 +371,8 @@
"isArchived": "This namespace is archived"
},
"pseudo": {
"sharedLists": {
"title": "Shared Lists"
"sharedProjects": {
"title": "Shared Projects"
},
"favorites": {
"title": "Favorites"
@ -404,8 +403,9 @@
},
"create": {
"title": "New Saved Filter",
"description": "A saved filter is a virtual list which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
"action": "Create new saved filter"
"description": "A saved filter is a virtual project which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
"action": "Create new saved filter",
"titleRequired": "Please provide a title for the filter."
},
"delete": {
"header": "Delete this saved filter",
@ -435,7 +435,7 @@
"label": {
"title": "Labels",
"manage": "Manage labels",
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose list you have access.",
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose project you have access.",
"newCTA": "You currently do not have any labels.",
"search": "Type to search for a label…",
"create": {
@ -460,7 +460,7 @@
},
"sharing": {
"authenticating": "Authenticating…",
"passwordRequired": "This shared list requires a password. Please enter it below:",
"passwordRequired": "This shared project requires a password. Please enter it below:",
"error": "An error occured.",
"invalidPassword": "The password is invalid."
},
@ -605,6 +605,7 @@
"addReminder": "Add a new reminder…",
"doneSuccess": "The task was successfully marked as done.",
"undoneSuccess": "The task was successfully un-marked as done.",
"undo": "Undo",
"openDetail": "Open task detail view",
"checklistTotal": "{checked} of {total} tasks",
"checklistAllDone": "{total} tasks",
@ -621,7 +622,7 @@
"chooseDueDate": "Click here to set a due date",
"chooseStartDate": "Click here to set a start date",
"chooseEndDate": "Click here to set an end date",
"move": "Move task to a different list",
"move": "Move task to a different project",
"done": "Mark task done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
@ -629,7 +630,7 @@
"doneAt": "Done {0}",
"updateSuccess": "The task was saved successfully.",
"deleteSuccess": "The task has been deleted successfully.",
"belongsToList": "This task belongs to list '{list}'",
"belongsToProject": "This task belongs to project '{project}'",
"due": "Due {at}",
"closePopup": "Close popup",
"delete": {
@ -649,7 +650,7 @@
"percentDone": "Set Progress",
"attachments": "Add Attachments",
"relatedTasks": "Add Relation",
"moveList": "Move",
"moveProject": "Move",
"color": "Set Color",
"delete": "Delete",
"favorite": "Add to Favorites",
@ -676,21 +677,21 @@
"updated": "Updated"
},
"subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
"subscribedProjectThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this project through its namespace.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
"subscribedTaskThroughParentProject": "You can't unsubscribe here because you are subscribed to this task through its project.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedProject": "You are currently subscribed to this project and will receive notifications for changes.",
"notSubscribedProject": "You are not subscribed to this project and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Subscribe",
"unsubscribe": "Unsubscribe",
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessProject": "You are now subscribed to this project",
"unsubscribeSuccessProject": "You are now unsubscribed to this project",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
},
@ -764,7 +765,7 @@
"new": "New Task Relation",
"searchPlaceholder": "Type search for a new task to add as related…",
"createPlaceholder": "Add this as new related task",
"differentList": "This task belongs to a different list.",
"differentProject": "This task belongs to a different project.",
"differentNamespace": "This task belongs to a different namespace.",
"noneYet": "No task relations yet.",
"delete": "Delete Task Relation",
@ -814,10 +815,10 @@
"priority1": "To set a task's priority, add a number 1-5, prefixed with a {prefix}.",
"priority2": "The higher the number, the higher the priority.",
"assignees": "To directly assign the task to a user, add their username prefixed with {prefix} to the task.",
"list1": "To set a list for the task to appear in, enter its name prefixed with {prefix}.",
"list2": "This will return an error if the list does not exist.",
"list3": "To use spaces, simply add a \" or ' around the list name.",
"list4": "For example: {prefix}\"List with spaces\".",
"project1": "To set a project for the task to appear in, enter its name prefixed with {prefix}.",
"project2": "This will return an error if the project does not exist.",
"project3": "To use spaces, simply add a \" or ' around the project name.",
"project4": "For example: {prefix}\"Project with spaces\".",
"dateAndTime": "Date and time",
"date": "Any date will be used as the due date of the new task. You can use dates in any of these formats:",
"dateWeekday": "any weekday, will use the next date with that date",
@ -850,19 +851,19 @@
"delete": {
"header": "Delete the team",
"text1": "Are you sure you want to delete this team and all of its members?",
"text2": "All team members will lose access to lists and namespaces shared with this team. This CANNOT BE UNDONE!",
"text2": "All team members will lose access to projects and namespaces shared with this team. This CANNOT BE UNDONE!",
"success": "The team was successfully deleted."
},
"deleteUser": {
"header": "Remove a user from the team",
"text1": "Are you sure you want to remove this user from the team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"text2": "They will lose access to all projects and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will lose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"text2": "You will lose access to all projects and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
}
},
@ -894,12 +895,12 @@
"attachment": "Add an attachment to this task",
"related": "Modify related tasks of this task",
"color": "Change the color of this task",
"move": "Move this task to another list",
"move": "Move this task to another project",
"reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
},
"list": {
"title": "List Views",
"project": {
"title": "Project Views",
"switchToListView": "Switch to list view",
"switchToGanttView": "Switch to gantt view",
"switchToKanbanView": "Switch to kanban view",
@ -909,7 +910,7 @@
"title": "Navigation",
"overview": "Navigate to overview",
"upcoming": "Navigate to upcoming tasks",
"namespaces": "Navigate to namespaces & lists",
"namespaces": "Navigate to namespaces & projects",
"labels": "Navigate to labels",
"teams": "Navigate to teams"
}
@ -926,7 +927,7 @@
"unarchive": "Un-Archive",
"setBackground": "Set background",
"share": "Share",
"newList": "New list"
"newProject": "New project"
},
"apiConfig": {
"url": "Vikunja URL",
@ -945,24 +946,24 @@
"notification": {
"title": "Notifications",
"none": "You don't have any notifications. Have a nice day!",
"explainer": "Notifications will appear here when actions on namespaces, lists or tasks you subscribed to happen."
"explainer": "Notifications will appear here when actions on namespaces, projects or tasks you subscribed to happen."
},
"quickActions": {
"commands": "Commands",
"placeholder": "Type a command or search…",
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
"hint": "You can use {project} to limit the search to a project. Combine {project} or {label} (labels) with a search query to search for a task with these labels or on that project. Use {assignee} to only search for teams.",
"tasks": "Tasks",
"lists": "Lists",
"projects": "Projects",
"teams": "Teams",
"newList": "Enter the title of the new list…",
"newProject": "Enter the title of the new project…",
"newTask": "Enter the title of the new task…",
"newNamespace": "Enter the title of the new namespace…",
"newTeam": "Enter the name of the new team…",
"createTask": "Create a task in the current list ({title})",
"createList": "Create a list in the current namespace ({title})",
"createTask": "Create a task in the current project ({title})",
"createProject": "Create a project in the current namespace ({title})",
"cmds": {
"newTask": "New task",
"newList": "New list",
"newProject": "New project",
"newNamespace": "New namespace",
"newTeam": "New team"
}
@ -994,15 +995,15 @@
"1018": "The user avatar type setting is invalid.",
"2001": "ID cannot be empty or 0.",
"2002": "Some of the request data was invalid.",
"3001": "The list does not exist.",
"3004": "You need to have read permissions on that list to perform that action.",
"3005": "The list title cannot be empty.",
"3006": "The list share does not exist.",
"3007": "A list with this identifier already exists.",
"3008": "The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list.",
"4001": "The list task text cannot be empty.",
"4002": "The list task does not exist.",
"4003": "All bulk editing tasks must belong to the same list.",
"3001": "The project does not exist.",
"3004": "You need to have read permissions on that project to perform that action.",
"3005": "The project title cannot be empty.",
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Need at least one task when bulk editing tasks.",
"4005": "You do not have the right to see the task.",
"4006": "You can't set a parent task as the task itself.",
@ -1028,21 +1029,21 @@
"5012": "The namespace is archived and can therefore only be accessed read only.",
"6001": "The team name cannot be empty.",
"6002": "The team does not exist.",
"6004": "The team already has access to that namespace or list.",
"6004": "The team already has access to that namespace or project.",
"6005": "The user is already a member of that team.",
"6006": "Cannot delete the last team member.",
"6007": "The team does not have access to the list to perform that action.",
"7002": "The user already has access to that list.",
"7003": "You do not have access to that list.",
"6007": "The team does not have access to the project to perform that action.",
"7002": "The user already has access to that project.",
"7003": "You do not have access to that project.",
"8001": "This label already exists on that task.",
"8002": "The label does not exist.",
"8003": "You do not have access to this label.",
"9001": "The right is invalid.",
"10001": "The bucket does not exist.",
"10002": "The bucket does not belong to that list.",
"10003": "You cannot remove the last bucket on a list.",
"10002": "The bucket does not belong to that project.",
"10003": "You cannot remove the last bucket on a project.",
"10004": "You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold.",
"10005": "There can be only one done bucket per list.",
"10005": "There can be only one done bucket per project.",
"11001": "The saved filter does not exist.",
"11002": "Saved filters are not available for link shares.",
"12001": "The subscription entity type is invalid.",

View File

@ -5,11 +5,11 @@
"welcomeDay": "¡Hola {username}!",
"welcomeEvening": "¡Buenas Tardes {username}!",
"lastViewed": "Visto por última vez",
"list": {
"newText": "Puedes crear una nueva lista para las tareas nuevas:",
"new": "Nueva lista",
"importText": "O importa tus listas y tareas de otros servicios a Vikunja:",
"import": "Importa tus datos a Vikunja"
"project": {
"newText": "You can create a new project for your new tasks:",
"new": "New project",
"importText": "Or import your projects and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
"404": {
@ -85,7 +85,7 @@
"weekStartSunday": "Domingo",
"weekStartMonday": "Lunes",
"language": "Idioma",
"defaultList": "Lista Predeterminada",
"defaultProject": "Default Project",
"timezone": "Zona Horaria",
"overdueTasksRemindersTime": "Hora de envío del correo electrónico de tareas pendientes"
},
@ -143,7 +143,7 @@
},
"deletion": {
"title": "Eliminar tu Cuenta de Vikunja",
"text1": "La eliminación de tu cuenta es permanente y no se puede deshacer. Eliminaremos todos tus proyectos, listas, tareas y todo lo relacionado con ella.",
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, projects, tasks and everything associated with it.",
"text2": "Para continuar, por favor, introduce tu contraseña. Recibirás un correo electrónico con más instrucciones.",
"confirm": "Eliminar mi cuenta",
"requestSuccess": "La solicitud ha sido exitosa. Recibirás un correo electrónico con más instrucciones.",
@ -157,201 +157,201 @@
},
"export": {
"title": "Exportar tus datos de Vikunja",
"description": "Puede solicitar una copia de todos sus datos de Vikunja. Esto incluye proyectos, listas, tareas y todo lo relacionado con ellos. Puede importar estos datos en cualquier instancia de Vikunja, a través de la función de migración.",
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Projects, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
"descriptionPasswordRequired": "Por favor, introduce tu contraseña para continuar:",
"request": "Solicitar una copia de mis datos de Vikunja",
"success": "Tu petición de datos de Vikunja ha sido procesada correctamente. Te enviaremos un correo una vez esté lista para descargar.",
"downloadTitle": "Descargar tus datos de Vikunja exportados"
}
},
"list": {
"archived": "La lista está archivada. No es posible crear nuevas tareas, ni editarlas.",
"title": "Título de Lista",
"project": {
"archived": "This project is archived. It is not possible to create new or edit tasks for it.",
"title": "Project Title",
"color": "Color",
"lists": "Listas",
"list": {
"title": "Lista",
"add": "Añadir",
"addPlaceholder": "Añadir una nueva tarea…",
"empty": "This list is currently empty.",
"newTaskCta": "Crear una nueva tarea.",
"editTask": "Editar Tarea"
},
"search": "Escribe para buscar una lista…",
"searchSelect": "Haga clic o presione enter para seleccionar esta lista",
"shared": "Listas Compartidas",
"noDescriptionAvailable": "Descripción de lista no disponible.",
"projects": "Projects",
"search": "Type to search for a project…",
"searchSelect": "Click or press enter to select this project",
"shared": "Shared Projects",
"noDescriptionAvailable": "No project description is available.",
"create": {
"header": "Nueva lista",
"titlePlaceholder": "El título de la lista va aquí…",
"addTitleRequired": "Por favor, especifica un título.",
"createdSuccess": "La lista se ha creado correctamente.",
"addListRequired": "Por favor, especifica una lista o establece una lista por defecto en la configuración."
"header": "New project",
"titlePlaceholder": "The project's title goes here…",
"addTitleRequired": "Please specify a title.",
"createdSuccess": "The project was successfully created.",
"addProjectRequired": "Please specify a project or set a default project in the settings."
},
"archive": {
"title": "Archivar \"{list}\"",
"archive": "Archivar esta lista",
"unarchive": "Desarchivar esta lista",
"unarchiveText": "Podrás crear tareas nuevas o editarlas.",
"archiveText": "No podrás editar esta lista ni crear nuevas tareas hasta que la desarchives.",
"success": "La lista fue archivada con éxito."
"title": "Archive \"{project}\"",
"archive": "Archive this project",
"unarchive": "Un-Archive this project",
"unarchiveText": "You will be able to create new tasks or edit it.",
"archiveText": "You won't be able to edit this project or create new tasks until you un-archive it.",
"success": "The project was successfully archived."
},
"background": {
"title": "Establecer fondo para la lista",
"remove": "Eliminar Fondo",
"upload": "Elige un fondo desde tu pc",
"searchPlaceholder": "Buscar un fondo…",
"poweredByUnsplash": "Con tecnología de Unsplash",
"loadMore": "Cargar más fotos",
"success": "¡El fondo se ha establecido con éxito!",
"removeSuccess": "¡El fondo se ha eliminado con éxito!"
"title": "Set project background",
"remove": "Remove Background",
"upload": "Choose a background from your pc",
"searchPlaceholder": "Search for a background…",
"poweredByUnsplash": "Powered by Unsplash",
"loadMore": "Load more photos",
"success": "The background has been set successfully!",
"removeSuccess": "The background has been removed successfully!"
},
"delete": {
"title": "Eliminar \"{list}\"",
"header": "Eliminar esta lista",
"text1": "¿Estás seguro de que quieres eliminar esta lista y todo su contenido?",
"text2": "¡Esto incluye todas las tareas y NO SE PUEDE DESHACER!",
"success": "La lista se ha eliminado con éxito.",
"tasksToDelete": "Esto eliminará irrevocablemente aprox. {count} tareas.",
"noTasksToDelete": "Esta lista no contiene ninguna tarea, debería ser segura de eliminar."
"title": "Delete \"{project}\"",
"header": "Delete this project",
"text1": "Are you sure you want to delete this project and all of its contents?",
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
"title": "Duplicar esta lista",
"label": "Duplicar",
"text": "Seleccione un proyecto que debería contener la lista duplicada:",
"success": "La lista se ha duplicado con éxito."
"title": "Duplicate this project",
"label": "Duplicate",
"text": "Select a namespace which should hold the duplicated project:",
"success": "The project was successfully duplicated."
},
"edit": {
"header": "Editar Esta Lista",
"title": "Editar \"{list}\"",
"titlePlaceholder": "El título de la lista va aquí…",
"identifierTooltip": "El identificador de lista se puede usar para identificar una tarea de forma única a través de las listas. Puedes establecerlo en blanco para desactivarlo.",
"identifier": "Identificador de la lista",
"identifierPlaceholder": "El identificador de la lista va aquí…",
"description": "Descripción",
"descriptionPlaceholder": "La descripción de la lista va aquí…",
"header": "Edit This Project",
"title": "Edit \"{project}\"",
"titlePlaceholder": "The project title goes here…",
"identifierTooltip": "The project identifier can be used to uniquely identify a task across projects. You can set it to empty to disable it.",
"identifier": "Project Identifier",
"identifierPlaceholder": "The project identifier goes here…",
"description": "Description",
"descriptionPlaceholder": "The projects description goes here…",
"color": "Color",
"success": "La lista se ha actualizado correctamente."
"success": "The project was successfully updated."
},
"share": {
"header": "Compartir esta lista",
"title": "Compartir \"{list}\"",
"share": "Compartir",
"header": "Share this project",
"title": "Share \"{project}\"",
"share": "Share",
"links": {
"title": "Compartir enlaces",
"what": "¿Qué es un enlace compartido?",
"explanation": "Enlaces compartidos te permiten compartir fácilmente una lista con otros usuarios que no tienen una cuenta de Vikunja.",
"create": "Crear un nuevo enlace compartido",
"name": "Nombre (opcional)",
"namePlaceholder": "ej. Lorem Ipsum",
"nameExplanation": "Todas las acciones realizadas por este enlace compartido se mostrarán con el nombre.",
"password": "Contraseña (opcional)",
"passwordExplanation": "Al autenticarse, el usuario deberá introducir esta contraseña.",
"noName": "Sin nombre establecido",
"remove": "Eliminar un enlace compartido",
"removeText": "¿Estás seguro de que deseas eliminar este enlace compartido? Ya no será posible acceder a esta lista con este enlace compartido. ¡Esto no se puede deshacer!",
"createSuccess": "El enlace compartido se ha creado correctamente.",
"deleteSuccess": "El enlace compartido se ha eliminado correctamente",
"view": "Vista",
"sharedBy": "Compartido por {0}"
"title": "Share Links",
"what": "What is a share link?",
"explanation": "Share Links allow you to easily share a project with other users who don't have an account on Vikunja.",
"create": "Create a new link share",
"name": "Name (optional)",
"namePlaceholder": "e.g. Lorem Ipsum",
"nameExplanation": "All actions done by this link share will show up with the name.",
"password": "Password (optional)",
"passwordExplanation": "When authenticating, the user will be required to enter this password.",
"noName": "No name set",
"remove": "Remove a link share",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this project with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted",
"view": "View",
"sharedBy": "Shared by {0}"
},
"userTeam": {
"typeUser": "usuario | usuarios",
"typeTeam": "equipo | equipos",
"shared": "Compartido con estos {type}",
"you": "",
"notShared": "No compartido con ningún {type} aún.",
"removeHeader": "Eliminar un {type} de la {sharable}",
"removeText": "¿Estás seguro de que quieres eliminar este {sharable} del {type}? ¡Esto no se puede deshacer!",
"removeSuccess": "El {sharable} fue eliminado correctamente de {type}.",
"typeUser": "user | users",
"typeTeam": "team | teams",
"shared": "Shared with these {type}",
"you": "You",
"notShared": "Not shared with any {type} yet.",
"removeHeader": "Remove a {type} from the {sharable}",
"removeText": "Are you sure you want to remove this {sharable} from the {type}? This cannot be undone!",
"removeSuccess": "The {sharable} was successfully removed from the {type}.",
"addedSuccess": "The {type} was successfully added.",
"updatedSuccess": "El {type} fue añadido correctamente."
"updatedSuccess": "The {type} was successfully added."
},
"right": {
"title": "Permiso",
"read": "Solo lectura",
"readWrite": "Lectura y escritura",
"title": "Permission",
"read": "Read only",
"readWrite": "Read & write",
"admin": "Admin"
},
"attributes": {
"link": "Enlace",
"delete": "Eliminar"
"link": "Link",
"delete": "Delete"
}
},
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This project is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": {
"title": "Gantt",
"showTasksWithoutDates": "Mostrar tareas que no tienen fechas establecidas",
"size": "Tamaño",
"default": "Predeterminado",
"month": "Mes",
"day": "Día",
"hour": "Hora",
"range": "Rango de fechas",
"noDates": "Esta tarea no tiene fechas establecidas."
"showTasksWithoutDates": "Show tasks which don't have dates set",
"size": "Size",
"default": "Default",
"month": "Month",
"day": "Day",
"hour": "Hour",
"range": "Date Range",
"noDates": "This task has no dates set."
},
"table": {
"title": "Tabla",
"columns": "Columnas"
"title": "Table",
"columns": "Columns"
},
"kanban": {
"title": "Kanban",
"limit": "Límite: {limit}",
"noLimit": "No Establecido",
"doneBucket": "Contenedor Hecho",
"doneBucketHint": "Todas las tareas movidas a este contenedor se marcarán automáticamente como hechas.",
"doneBucketHintExtended": "Todas las tareas que se trasladen al cubo de finalizadas se marcarán como realizadas automáticamente. Todas las tareas marcadas como realizadas desde otro lugar también se moverán.",
"doneBucketSavedSuccess": "El cubo finalizado ha sido guardado correctamente.",
"deleteLast": "No puedes eliminar el último contenedor.",
"addTaskPlaceholder": "Introduce el nuevo título de la tarea…",
"addTask": "Añadir una tarea",
"addAnotherTask": "Añadir otra tarea",
"addBucket": "Crear un nuevo contenedor",
"addBucketPlaceholder": "Introduzca el nuevo título del contenedor…",
"deleteHeaderBucket": "Eliminar el contenedor",
"deleteBucketText1": "¿Estás seguro de que quieres eliminar este contenedor?",
"deleteBucketText2": "Esto no eliminará ninguna tarea pero las moverá al contenedor por defecto.",
"deleteBucketSuccess": "El contenedor se ha eliminado con éxito.",
"bucketTitleSavedSuccess": "El título del contenedor se ha guardado con éxito.",
"bucketLimitSavedSuccess": "El límite del contenedor se ha guardado con éxito.",
"collapse": "Contraer este contenedor"
"limit": "Limit: {limit}",
"noLimit": "Not Set",
"doneBucket": "Done bucket",
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.",
"doneBucketHintExtended": "All tasks moved into the done bucket will be marked as done automatically. All tasks marked as done from elsewhere will be moved as well.",
"doneBucketSavedSuccess": "The done bucket has been saved successfully.",
"deleteLast": "You cannot remove the last bucket.",
"addTaskPlaceholder": "Enter the new task title…",
"addTask": "Add a task",
"addAnotherTask": "Add another task",
"addBucket": "Create a new bucket",
"addBucketPlaceholder": "Enter the new bucket title…",
"deleteHeaderBucket": "Delete the bucket",
"deleteBucketText1": "Are you sure you want to delete this bucket?",
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.",
"deleteBucketSuccess": "The bucket has been deleted successfully.",
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.",
"bucketLimitSavedSuccess": "The bucket limit been saved successfully.",
"collapse": "Collapse this bucket"
},
"pseudo": {
"favorites": {
"title": "Favoritos"
"title": "Favorites"
}
}
},
"namespace": {
"title": "Proyectos y listas",
"title": "Namespaces & Projects",
"namespace": "Proyecto",
"showArchived": "Mostrar Archivados",
"noneAvailable": "No tienes ningún proyecto en este momento.",
"unarchive": "Desarchivar",
"archived": "Archivado",
"noLists": "Este proyecto no contiene ninguna lista.",
"createList": "Crear una nueva lista en este proyecto.",
"noProjects": "This namespace does not contain any projects.",
"createProject": "Create a new project in this namespace.",
"namespaces": "Proyectos",
"search": "Escribe para buscar un proyecto…",
"create": {
"title": "Nuevo proyecto",
"titleRequired": "Por favor, especifica un título.",
"explanation": "Un proyecto es una colección de listas que puedes compartir y utilizar para organizar tus listas. De hecho, cada lista pertenece a un proyecto.",
"explanation": "A namespace is a collection of projects you can share and use to organize your projects with. In fact, every project belongs to a namespace.",
"tooltip": "¿Qué es un proyecto?",
"success": "El proyecto se ha creado correctamente."
},
"archive": {
"titleArchive": "Archivar \"{namespace}\"",
"titleUnarchive": "Desarchivar \"{namespace}\"",
"archiveText": "No podrás editar este proyecto o crear nuevas listas hasta que no lo desarchives. Esto también archivará todas las listas de este proyecto.",
"unarchiveText": "Podrás crear listas nuevas o editarlas.",
"archiveText": "You won't be able to edit this namespace or create new projects until you un-archive it. This will also archive all projects in this namespace.",
"unarchiveText": "You will be able to create new projects or edit it.",
"success": "El proyecto fue archivado con éxito.",
"unarchiveSuccess": "El proyecto se ha desarchivado con éxito.",
"description": "Si un proyecto está archivado, no podrás crear nuevas listas o editarlo."
"description": "If a namespace is archived, you cannot create new projects or edit it."
},
"delete": {
"title": "Eliminar \"{namespace}\"",
"text1": "¿Estás seguro de que deseas eliminar este proyecto y todo su contenido?",
"text2": "¡Esto incluye todas las listas y tareas y NO SE PUEDE DESHACER!",
"text2": "This includes all projects and tasks and CANNOT BE UNDONE!",
"success": "El proyecto se ha eliminado con éxito."
},
"edit": {
@ -371,8 +371,8 @@
"isArchived": "Este proyecto está archivado"
},
"pseudo": {
"sharedLists": {
"title": "Listas compartidas"
"sharedProjects": {
"title": "Shared Projects"
},
"favorites": {
"title": "Favoritos"
@ -403,8 +403,9 @@
},
"create": {
"title": "New Saved Filter",
"description": "A saved filter is a virtual list which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
"action": "Create new saved filter"
"description": "A saved filter is a virtual project which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
"action": "Create new saved filter",
"titleRequired": "Please provide a title for the filter."
},
"delete": {
"header": "Delete this saved filter",
@ -434,7 +435,7 @@
"label": {
"title": "Labels",
"manage": "Manage labels",
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose list you have access.",
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose project you have access.",
"newCTA": "You currently do not have any labels.",
"search": "Type to search for a label…",
"create": {
@ -459,7 +460,7 @@
},
"sharing": {
"authenticating": "Authenticating…",
"passwordRequired": "This shared list requires a password. Please enter it below:",
"passwordRequired": "This shared project requires a password. Please enter it below:",
"error": "Ha ocurrido un error.",
"invalidPassword": "La contraseña es inválida."
},
@ -528,7 +529,7 @@
"code": "Código",
"quote": "Cita",
"unorderedList": "Lista no ordenada",
"orderedList": "Lista ordenada",
"orderedList": "Ordered List",
"cleanBlock": "Borrar Bloque",
"link": "Enlace",
"image": "Imagen",
@ -601,6 +602,7 @@
"addReminder": "Add a new reminder…",
"doneSuccess": "La tarea fue marcada con éxito como realizada.",
"undoneSuccess": "La tarea fue marcada correctamente como incompleta.",
"undo": "Undo",
"openDetail": "Open task detail view",
"checklistTotal": "{checked} of {total} tasks",
"checklistAllDone": "{total} tasks",
@ -617,7 +619,7 @@
"chooseDueDate": "Click here to set a due date",
"chooseStartDate": "Click here to set a start date",
"chooseEndDate": "Click here to set an end date",
"move": "Move task to a different list",
"move": "Move task to a different project",
"done": "Mark task done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
@ -625,7 +627,7 @@
"doneAt": "Done {0}",
"updateSuccess": "The task was saved successfully.",
"deleteSuccess": "La tarea se ha eliminado con éxito.",
"belongsToList": "This task belongs to list '{list}'",
"belongsToProject": "This task belongs to project '{project}'",
"due": "Due {at}",
"closePopup": "Close popup",
"delete": {
@ -645,7 +647,7 @@
"percentDone": "Definir Progreso",
"attachments": "Añadir Adjuntos",
"relatedTasks": "Añadir una Relación",
"moveList": "Mover",
"moveProject": "Move",
"color": "Definir Color",
"delete": "Eliminar",
"favorite": "Añadir a Favoritos",
@ -672,21 +674,21 @@
"updated": "Actualizado"
},
"subscription": {
"subscribedListThroughParentNamespace": "No puede cancelar la suscripción aquí porque está suscrito a esta lista a través de su proyecto.",
"subscribedProjectThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this project through its namespace.",
"subscribedTaskThroughParentNamespace": "No puede cancelar la suscripción aquí porque está suscrito a esta tarea a través de su proyecto.",
"subscribedTaskThroughParentList": "No puedes darte de baja aquí porque estás suscrito a esta tarea a través de su lista.",
"subscribedTaskThroughParentProject": "You can't unsubscribe here because you are subscribed to this task through its project.",
"subscribedNamespace": "Actualmente está suscrito a este proyecto y recibirás notificaciones de cambios.",
"notSubscribedNamespace": "No está suscrito a este proyecto y no recibirá notificaciones de cambios.",
"subscribedList": "Actualmente estás suscrito a esta lista y recibirás notificaciones de cambios.",
"notSubscribedList": "No estás suscrito a esta lista y no recibirás notificaciones de cambios.",
"subscribedProject": "You are currently subscribed to this project and will receive notifications for changes.",
"notSubscribedProject": "You are not subscribed to this project and won't receive notifications for changes.",
"subscribedTask": "Actualmente estás suscrito a esta tarea y recibirás notificaciones de cambios.",
"notSubscribedTask": "No estás suscrito a esta tarea y no recibirás notificaciones de cambios.",
"subscribe": "Suscribirse",
"unsubscribe": "Desuscribirse",
"subscribeSuccessNamespace": "Ahora está suscrito a este proyecto",
"unsubscribeSuccessNamespace": "Ya no está suscrito a este proyecto",
"subscribeSuccessList": "Ahora estás suscrito a esta lista",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessProject": "You are now subscribed to this project",
"unsubscribeSuccessProject": "You are now unsubscribed to this project",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
},
@ -760,7 +762,7 @@
"new": "Nueva Tarea Relacionada",
"searchPlaceholder": "Escriba para buscar una nueva tarea a añadir como relacionada…",
"createPlaceholder": "Añadir esto como nueva tarea relacionada",
"differentList": "Esta tarea pertenece a una lista diferente.",
"differentProject": "This task belongs to a different project.",
"differentNamespace": "Esta tarea pertenece a un proyecto diferente.",
"noneYet": "Aún no hay tareas relacionadas.",
"delete": "Eliminar Relación de Tarea",
@ -810,10 +812,10 @@
"priority1": "Para establecer la prioridad de una tarea, agregue un número 1-5, prefijado por {prefix}.",
"priority2": "The higher the number, the higher the priority.",
"assignees": "To directly assign the task to a user, add their username prefixed with {prefix} to the task.",
"list1": "To set a list for the task to appear in, enter its name prefixed with {prefix}.",
"list2": "This will return an error if the list does not exist.",
"list3": "To use spaces, simply add a \" or ' around the list name.",
"list4": "For example: {prefix}\"List with spaces\".",
"project1": "To set a project for the task to appear in, enter its name prefixed with {prefix}.",
"project2": "This will return an error if the project does not exist.",
"project3": "To use spaces, simply add a \" or ' around the project name.",
"project4": "For example: {prefix}\"Project with spaces\".",
"dateAndTime": "Date and time",
"date": "Any date will be used as the due date of the new task. You can use dates in any of these formats:",
"dateWeekday": "any weekday, will use the next date with that date",
@ -846,19 +848,19 @@
"delete": {
"header": "Delete the team",
"text1": "Are you sure you want to delete this team and all of its members?",
"text2": "All team members will lose access to lists and namespaces shared with this team. This CANNOT BE UNDONE!",
"text2": "All team members will lose access to projects and namespaces shared with this team. This CANNOT BE UNDONE!",
"success": "El equipo fue eliminado con éxito."
},
"deleteUser": {
"header": "Remove a user from the team",
"text1": "Are you sure you want to remove this user from the team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"text2": "They will lose access to all projects and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "El usuario fue quitado del equipo con éxito."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will lose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"text2": "You will lose access to all projects and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "Has abandonado el equipo con éxito."
}
},
@ -890,22 +892,22 @@
"attachment": "Añadir un adjunto a esta tarea",
"related": "Modificar tareas relacionadas de esta tarea",
"color": "Cambia el color de esta tarea",
"move": "Mover esta tarea a otra lista",
"move": "Move this task to another project",
"reminder": "Administrar recordatorios de esta tarea",
"description": "Editar la descripción de la tarea"
},
"list": {
"title": "Lista de vistas",
"switchToListView": "Cambiar a vista de lista",
"switchToGanttView": "Cambiar a vista gantt",
"switchToKanbanView": "Cambiar a vista kanban",
"switchToTableView": "Cambiar a vista de tabla"
"project": {
"title": "Project Views",
"switchToListView": "Switch to list view",
"switchToGanttView": "Switch to gantt view",
"switchToKanbanView": "Switch to kanban view",
"switchToTableView": "Switch to table view"
},
"navigation": {
"title": "Secciones",
"overview": "Ir a resumen",
"upcoming": "Ir a tareas próximas",
"namespaces": "Ir a proyectos y listas",
"namespaces": "Navigate to namespaces & projects",
"labels": "Ir a etiquetas",
"teams": "Ir a equipos"
}
@ -922,7 +924,7 @@
"unarchive": "Desarchivar",
"setBackground": "Establecer fondo",
"share": "Compartir",
"newList": "Nueva lista"
"newProject": "New project"
},
"apiConfig": {
"url": "URL de Vikunja",
@ -941,24 +943,24 @@
"notification": {
"title": "Notificaciones",
"none": "No tienes notificaciones. ¡Que tengas un buen día!",
"explainer": "Notifications will appear here when actions on namespaces, lists or tasks you subscribed to happen."
"explainer": "Notifications will appear here when actions on namespaces, projects or tasks you subscribed to happen."
},
"quickActions": {
"commands": "Commands",
"placeholder": "Type a command or search…",
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
"hint": "You can use {project} to limit the search to a project. Combine {project} or {label} (labels) with a search query to search for a task with these labels or on that project. Use {assignee} to only search for teams.",
"tasks": "Tasks",
"lists": "Lists",
"projects": "Projects",
"teams": "Teams",
"newList": "Enter the title of the new list…",
"newProject": "Enter the title of the new project…",
"newTask": "Enter the title of the new task…",
"newNamespace": "Enter the title of the new namespace…",
"newTeam": "Enter the name of the new team…",
"createTask": "Create a task in the current list ({title})",
"createList": "Create a list in the current namespace ({title})",
"createTask": "Create a task in the current project ({title})",
"createProject": "Create a project in the current namespace ({title})",
"cmds": {
"newTask": "New task",
"newList": "New list",
"newProject": "New project",
"newNamespace": "New namespace",
"newTeam": "New team"
}
@ -990,15 +992,15 @@
"1018": "The user avatar type setting is invalid.",
"2001": "ID cannot be empty or 0.",
"2002": "Some of the request data was invalid.",
"3001": "The list does not exist.",
"3004": "You need to have read permissions on that list to perform that action.",
"3005": "The list title cannot be empty.",
"3006": "The list share does not exist.",
"3007": "A list with this identifier already exists.",
"3008": "The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list.",
"4001": "The list task text cannot be empty.",
"4002": "The list task does not exist.",
"4003": "All bulk editing tasks must belong to the same list.",
"3001": "The project does not exist.",
"3004": "You need to have read permissions on that project to perform that action.",
"3005": "The project title cannot be empty.",
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Se necesita al menos una tarea cuando se editan tareas masivamente.",
"4005": "No tiene permiso para ver la tarea.",
"4006": "No se puede establecer como tarea padre la propia tarea.",
@ -1024,21 +1026,21 @@
"5012": "El proyecto está archivado y por lo tanto solo podrá acceder en modo solo lectura.",
"6001": "El nombre del equipo no puede estar vacío.",
"6002": "Este equipo no existe.",
"6004": "El equipo ya tiene acceso a ese proyecto o lista.",
"6004": "The team already has access to that namespace or project.",
"6005": "El usuario ya es miembro de ese equipo.",
"6006": "No se puede quitar al último miembro del equipo.",
"6007": "El equipo no tiene acceso a la lista para realizar esa acción.",
"7002": "El usuario ya tiene acceso a esa lista.",
"7003": "No tiene acceso a esta lista.",
"6007": "The team does not have access to the project to perform that action.",
"7002": "The user already has access to that project.",
"7003": "You do not have access to that project.",
"8001": "Esta etiqueta ya existe en esta tarea.",
"8002": "La etiqueta no existe.",
"8003": "No tiene acceso a esta etiqueta.",
"9001": "El permiso es inválido.",
"10001": "El contenedor no existe.",
"10002": "El contenedor no pertenece a esa lista.",
"10003": "No puede eliminar el último contenedor de una lista.",
"10002": "The bucket does not belong to that project.",
"10003": "You cannot remove the last bucket on a project.",
"10004": "No se puede añadir la tarea a este contenedor, ya que ya ha superado el límite de tareas establecido.",
"10005": "Solo puede haber un contenedor hecho por lista.",
"10005": "There can be only one done bucket per project.",
"11001": "El filtro guardado no existe.",
"11002": "Los filtros guardados no están disponibles para enlaces compartidos.",
"12001": "El tipo de entidad de suscripción es inválido.",

View File

@ -5,11 +5,11 @@
"welcomeDay": "Hi {username}!",
"welcomeEvening": "Good Evening {username}!",
"lastViewed": "Dernière consultation",
"list": {
"newText": "Tu peux créer une nouvelle liste pour tes nouvelles tâches :",
"new": "Nouvelle liste",
"importText": "Ou importe tes listes et tâches dautres services dans Vikunja :",
"import": "Importer tes données dans Vikunja"
"project": {
"newText": "You can create a new project for your new tasks:",
"new": "New project",
"importText": "Or import your projects and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
"404": {
@ -85,7 +85,7 @@
"weekStartSunday": "dimanche",
"weekStartMonday": "lundi",
"language": "Langue",
"defaultList": "Liste par défaut",
"defaultProject": "Default Project",
"timezone": "Fuseau horaire",
"overdueTasksRemindersTime": "Overdue tasks reminder email time"
},
@ -143,7 +143,7 @@
},
"deletion": {
"title": "Supprimer ton compte Vikunja",
"text1": "La suppression de ton compte est permanente et ne peut pas être annulée. Nous supprimerons tous tes espaces de noms, listes, tâches et tout ce qui y est associés.",
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, projects, tasks and everything associated with it.",
"text2": "Pour continuer, entre ton mot de passe. Tu recevras un courriel contenant les instructions suivantes.",
"confirm": "Supprimer mon compte",
"requestSuccess": "La demande a réussi. Tu recevras un courriel avec des instructions supplémentaires.",
@ -157,201 +157,201 @@
},
"export": {
"title": "Exporter tes données Vikunja",
"description": "Tu peux demander une copie de toutes tes données de Vikunja. Ceci inclut les espaces de noms, les listes, les tâches et tout ce qui leur est associé. Tu peux importer ces données dans n'importe quelle instance de Vikunja par le biais de la fonction migration.",
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Projects, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
"descriptionPasswordRequired": "Entre ton mot de passe pour continuer :",
"request": "Demander une copie de mes données Vikunja",
"success": "Tu as bien demandé tes données Vikunja ! Nous t'enverrons un courriel dès qu'elles seront prêtes à être téléchargées.",
"downloadTitle": "Télécharger tes données exportées de Vikunja"
}
},
"list": {
"archived": "Cette liste est archivée. Il nest pas possible dy créer de nouvelles tâches ou de les modifier.",
"title": "Nom de la liste",
"color": "Couleur",
"lists": "Listes",
"list": {
"title": "Liste",
"add": "Ajouter",
"addPlaceholder": "Ajouter une nouvelle tâche…",
"empty": "Cette liste est actuellement vide.",
"newTaskCta": "Créer une nouvelle tâche.",
"editTask": "Modifier la tâche"
},
"search": "Écris pour rechercher une liste…",
"searchSelect": "Clique ou appuie sur la touche Entrée pour sélectionner cette liste",
"shared": "Listes partagées",
"noDescriptionAvailable": "No list description is available.",
"project": {
"archived": "This project is archived. It is not possible to create new or edit tasks for it.",
"title": "Project Title",
"color": "Color",
"projects": "Projects",
"search": "Type to search for a project…",
"searchSelect": "Click or press enter to select this project",
"shared": "Shared Projects",
"noDescriptionAvailable": "No project description is available.",
"create": {
"header": "Nouvelle liste",
"titlePlaceholder": "Entre le nom de la liste…",
"addTitleRequired": "Indique un nom.",
"createdSuccess": "Liste créée.",
"addListRequired": "Indique une liste ou définis une liste par défaut dans les paramètres."
"header": "New project",
"titlePlaceholder": "The project's title goes here…",
"addTitleRequired": "Please specify a title.",
"createdSuccess": "The project was successfully created.",
"addProjectRequired": "Please specify a project or set a default project in the settings."
},
"archive": {
"title": "Archiver « {list} »",
"archive": "Archiver cette liste",
"unarchive": "Désarchiver cette liste",
"unarchiveText": "Tu pourras créer de nouvelles tâches ou les modifier.",
"archiveText": "Tu ne pourras pas modifier cette liste ni créer de nouvelles tâches tant que tu ne lauras pas désarchivée.",
"success": "Liste archivée."
"title": "Archive \"{project}\"",
"archive": "Archive this project",
"unarchive": "Un-Archive this project",
"unarchiveText": "You will be able to create new tasks or edit it.",
"archiveText": "You won't be able to edit this project or create new tasks until you un-archive it.",
"success": "The project was successfully archived."
},
"background": {
"title": "Définir larrière-plan de la liste",
"remove": "Retirer larrière-plan",
"upload": "Choisis un arrière-plan depuis ton ordinateur",
"searchPlaceholder": "Rechercher un arrière-plan…",
"poweredByUnsplash": "Propulsé par Unsplash",
"loadMore": "Charger plus de photos",
"success": "Arrière-plan défini.",
"removeSuccess": "Arrière-plan supprimé."
"title": "Set project background",
"remove": "Remove Background",
"upload": "Choose a background from your pc",
"searchPlaceholder": "Search for a background…",
"poweredByUnsplash": "Powered by Unsplash",
"loadMore": "Load more photos",
"success": "The background has been set successfully!",
"removeSuccess": "The background has been removed successfully!"
},
"delete": {
"title": "Supprimer « {list} »",
"header": "Supprimer cette liste",
"text1": "Supprimer cette liste et tout son contenu ?",
"text2": "Ceci inclut toutes les tâches et ne peut pas être annulé !",
"success": "Liste supprimée.",
"title": "Delete \"{project}\"",
"header": "Delete this project",
"text1": "Are you sure you want to delete this project and all of its contents?",
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
"noTasksToDelete": "This list does not contain any tasks, it should be safe to delete."
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
"title": "Dupliquer cette liste",
"label": "Dupliquer",
"text": "Sélectionne un espace de noms qui doit contenir la liste dupliquée :",
"success": "Liste dupliquée."
"title": "Duplicate this project",
"label": "Duplicate",
"text": "Select a namespace which should hold the duplicated project:",
"success": "The project was successfully duplicated."
},
"edit": {
"header": "Modifier cette liste",
"title": "Modifier « {list} »",
"titlePlaceholder": "Entre le nom de la liste…",
"identifierTooltip": "Lidentifiant de liste peut être utilisé pour identifier de manière unique une tâche dans toutes les listes. Tu peux le régler sur vide pour le désactiver.",
"identifier": "Identifiant de la liste",
"identifierPlaceholder": "Lidentifiant de la liste va ici…",
"header": "Edit This Project",
"title": "Edit \"{project}\"",
"titlePlaceholder": "The project title goes here…",
"identifierTooltip": "The project identifier can be used to uniquely identify a task across projects. You can set it to empty to disable it.",
"identifier": "Project Identifier",
"identifierPlaceholder": "The project identifier goes here…",
"description": "Description",
"descriptionPlaceholder": "Entre la description de la liste…",
"color": "Couleur",
"success": "Liste mise à jour."
"descriptionPlaceholder": "The projects description goes here…",
"color": "Color",
"success": "The project was successfully updated."
},
"share": {
"header": "Partager cette liste",
"title": "Partager « {list} »",
"share": "Partager",
"header": "Share this project",
"title": "Share \"{project}\"",
"share": "Share",
"links": {
"title": "Liens de partage",
"what": "Quest-ce quun lien de partage ?",
"explanation": "Permet de partager une liste avec les personnes qui nont pas de compte sur Vikunja.",
"create": "Créer un nouveau lien de partage",
"name": "Nom (facultatif)",
"namePlaceholder": "p. ex. Lorem Ipsum",
"nameExplanation": "Toutes les actions effectuées par ce partage de lien apparaîtront avec le nom.",
"password": "Mot de passe (facultatif)",
"passwordExplanation": "Lutilisateur·rice doit saisir ce mot de passe pour se connecter.",
"noName": "Aucun nom défini",
"remove": "Retirer un lien de partage",
"removeText": "Retirer ce partage de lien ? Il ne sera plus possible daccéder à cette liste avec ce partage de lien. Cette opération ne peut être annulée !",
"createSuccess": "Partage créé.",
"deleteSuccess": "Lien supprimé",
"title": "Share Links",
"what": "What is a share link?",
"explanation": "Share Links allow you to easily share a project with other users who don't have an account on Vikunja.",
"create": "Create a new link share",
"name": "Name (optional)",
"namePlaceholder": "e.g. Lorem Ipsum",
"nameExplanation": "All actions done by this link share will show up with the name.",
"password": "Password (optional)",
"passwordExplanation": "When authenticating, the user will be required to enter this password.",
"noName": "No name set",
"remove": "Remove a link share",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this project with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted",
"view": "View",
"sharedBy": "Shared by {0}"
},
"userTeam": {
"typeUser": "utilisateur·rice | utilisateur·rice·s",
"typeTeam": "équipe | équipes",
"shared": "Partagé avec ces {type}",
"you": "Toi",
"notShared": "Pas encore partagé avec des {type}.",
"removeHeader": "Retirer un {type} de la liste {sharable}",
"removeText": "Retirer ce {sharable} du {type} ? Ceci ne peut pas être annulé !",
"removeSuccess": "{sharable} retiré de {type}.",
"addedSuccess": "{type} ajouté.",
"updatedSuccess": "{type} ajouté."
"typeUser": "user | users",
"typeTeam": "team | teams",
"shared": "Shared with these {type}",
"you": "You",
"notShared": "Not shared with any {type} yet.",
"removeHeader": "Remove a {type} from the {sharable}",
"removeText": "Are you sure you want to remove this {sharable} from the {type}? This cannot be undone!",
"removeSuccess": "The {sharable} was successfully removed from the {type}.",
"addedSuccess": "The {type} was successfully added.",
"updatedSuccess": "The {type} was successfully added."
},
"right": {
"title": "Permission",
"read": "Lecture seule",
"readWrite": "Lecture et écriture",
"read": "Read only",
"readWrite": "Read & write",
"admin": "Admin"
},
"attributes": {
"link": "Lien",
"delete": "Supprimer"
"link": "Link",
"delete": "Delete"
}
},
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This project is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": {
"title": "Gantt",
"showTasksWithoutDates": "Afficher les tâches pour lesquelles aucune date na été fixée",
"size": "Taille",
"default": "Par défaut",
"month": "Mois",
"day": "Jour",
"showTasksWithoutDates": "Show tasks which don't have dates set",
"size": "Size",
"default": "Default",
"month": "Month",
"day": "Day",
"hour": "Hour",
"range": "Date Range",
"noDates": "Aucune date na été fixée pour cette tâche."
"noDates": "This task has no dates set."
},
"table": {
"title": "Tableau",
"columns": "Colonnes"
"title": "Table",
"columns": "Columns"
},
"kanban": {
"title": "Kanban",
"limit": "Limite : {limit}",
"noLimit": "Non défini",
"doneBucket": "Seau des terminés",
"doneBucketHint": "Toutes les tâches déplacées dans ce seau seront automatiquement marquées comme faites.",
"doneBucketHintExtended": "Toutes les tâches déplacées dans le seau des choses terminées seront marquées comme terminées automatiquement. Toutes les tâches marquées comme terminées ailleurs seront également déplacées.",
"doneBucketSavedSuccess": "Seau des terminés enregistré.",
"deleteLast": "Tu ne peux pas supprimer le dernier seau.",
"addTaskPlaceholder": "Entre le nom de la tâche…",
"addTask": "Ajouter une tâche",
"addAnotherTask": "Ajouter une autre tâche",
"addBucket": "Créer un nouveau seau",
"addBucketPlaceholder": "Entre le nouveau nom du seau…",
"deleteHeaderBucket": "Supprimer le seau",
"deleteBucketText1": "Supprimer ce seau ?",
"deleteBucketText2": "Ceci ne supprimera pas les tâches mais les déplacera dans le seau par défaut.",
"deleteBucketSuccess": "Seau supprimé.",
"bucketTitleSavedSuccess": "Nom du seau enregistré.",
"bucketLimitSavedSuccess": "Limite du seau enregistrée.",
"collapse": "Réduire ce seau"
"limit": "Limit: {limit}",
"noLimit": "Not Set",
"doneBucket": "Done bucket",
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.",
"doneBucketHintExtended": "All tasks moved into the done bucket will be marked as done automatically. All tasks marked as done from elsewhere will be moved as well.",
"doneBucketSavedSuccess": "The done bucket has been saved successfully.",
"deleteLast": "You cannot remove the last bucket.",
"addTaskPlaceholder": "Enter the new task title…",
"addTask": "Add a task",
"addAnotherTask": "Add another task",
"addBucket": "Create a new bucket",
"addBucketPlaceholder": "Enter the new bucket title…",
"deleteHeaderBucket": "Delete the bucket",
"deleteBucketText1": "Are you sure you want to delete this bucket?",
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.",
"deleteBucketSuccess": "The bucket has been deleted successfully.",
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.",
"bucketLimitSavedSuccess": "The bucket limit been saved successfully.",
"collapse": "Collapse this bucket"
},
"pseudo": {
"favorites": {
"title": "Favoris"
"title": "Favorites"
}
}
},
"namespace": {
"title": "Espaces de noms et listes",
"title": "Namespaces & Projects",
"namespace": "Espace de noms",
"showArchived": "Montrer les archivés",
"noneAvailable": "Tu nas pas despace de noms pour le moment.",
"unarchive": "Désarchiver",
"archived": "Archivé",
"noLists": "Cet espace de noms ne contient pas de listes.",
"createList": "Créer une nouvelle liste dans cet espace de noms.",
"noProjects": "This namespace does not contain any projects.",
"createProject": "Create a new project in this namespace.",
"namespaces": "Espaces de noms",
"search": "Écris pour rechercher un espace de noms…",
"create": {
"title": "Nouvel espace de noms",
"titleRequired": "Indique un nom.",
"explanation": "A namespace is a collection of lists you can share and use to organize your lists with. In fact, every list belongs to a namespace.",
"explanation": "A namespace is a collection of projects you can share and use to organize your projects with. In fact, every project belongs to a namespace.",
"tooltip": "Quest-ce quun espace de noms ?",
"success": "Espace de noms créé."
},
"archive": {
"titleArchive": "Archiver « {namespace} »",
"titleUnarchive": "Désarchiver « {namespace} »",
"archiveText": "Tu ne pourras pas modifier cet espace de noms ou créer de nouvelles listes tant que tu ne lauras pas désarchivé. Ceci archivera également toutes les listes de cet espace de noms.",
"unarchiveText": "Tu pourras créer de nouvelles listes ou les modifier.",
"archiveText": "You won't be able to edit this namespace or create new projects until you un-archive it. This will also archive all projects in this namespace.",
"unarchiveText": "You will be able to create new projects or edit it.",
"success": "Espace de noms archivé.",
"unarchiveSuccess": "Espace de noms archivé.",
"description": "Larchivage dun espace de noms signifie quon ne peut pas créer de nouvelles listes dans cet espace, ni le modifier."
"description": "If a namespace is archived, you cannot create new projects or edit it."
},
"delete": {
"title": "Supprimer « {namespace} »",
"text1": "Supprimer cet espace de noms et tout son contenu ?",
"text2": "Ceci inclut toutes les listes et les tâches et ne peut être annulé !",
"text2": "This includes all projects and tasks and CANNOT BE UNDONE!",
"success": "Espace de noms supprimé."
},
"edit": {
@ -371,8 +371,8 @@
"isArchived": "Cet espace de noms est archivé"
},
"pseudo": {
"sharedLists": {
"title": "Listes partagées"
"sharedProjects": {
"title": "Shared Projects"
},
"favorites": {
"title": "Favoris"
@ -403,8 +403,9 @@
},
"create": {
"title": "Nouveau filtre enregistré",
"description": "Un filtre enregistré est une liste virtuelle qui est calculée à partir dun ensemble de filtres à chaque fois quon y accède. Une fois créé, il apparaît dans un espace de noms spécial.",
"action": "Créer un nouveau filtre enregistré"
"description": "A saved filter is a virtual project which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
"action": "Créer un nouveau filtre enregistré",
"titleRequired": "Please provide a title for the filter."
},
"delete": {
"header": "Supprimer ce filtre enregistré",
@ -434,7 +435,7 @@
"label": {
"title": "Étiquettes",
"manage": "Gérer les étiquettes",
"description": "Clique sur une étiquette pour la modifier. Tu peux modifier toutes les étiquettes que tu as créées, tu peux utiliser toutes les étiquettes qui sont associées à une tâche dont tu as accès à la liste.",
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose project you have access.",
"newCTA": "Tu nas actuellement aucune étiquette.",
"search": "Écris pour rechercher une étiquette…",
"create": {
@ -459,7 +460,7 @@
},
"sharing": {
"authenticating": "Authentification…",
"passwordRequired": "Cette liste partagée nécessite un mot de passe. Entre-le ci-dessous :",
"passwordRequired": "This shared project requires a password. Please enter it below:",
"error": "Une erreur sest produite.",
"invalidPassword": "Le mot de passe est invalide."
},
@ -528,7 +529,7 @@
"code": "Code",
"quote": "Citation",
"unorderedList": "Liste non ordonnée",
"orderedList": "Liste ordonnée",
"orderedList": "Ordered List",
"cleanBlock": "Bloc propre",
"link": "Lien",
"image": "Image",
@ -601,6 +602,7 @@
"addReminder": "Ajouter un nouveau rappel…",
"doneSuccess": "Tâche marquée comme terminée.",
"undoneSuccess": "Tâche marquée comme non terminée.",
"undo": "Undo",
"openDetail": "Ouvrir la vue détaillée de la tâche",
"checklistTotal": "{checked} sur {total} tâches",
"checklistAllDone": "{total} tâches",
@ -617,7 +619,7 @@
"chooseDueDate": "Clique ici pour définir une date déchéance",
"chooseStartDate": "Clique ici pour fixer une date de début",
"chooseEndDate": "Clique ici pour fixer une date de fin",
"move": "Déplacer une tâche vers une autre liste",
"move": "Move task to a different project",
"done": "Marquer la tâche comme terminée !",
"undone": "Marquer comme inachevé",
"created": "Créé {0} par {1}",
@ -625,7 +627,7 @@
"doneAt": "Terminé {0}",
"updateSuccess": "Tâche enregistrée.",
"deleteSuccess": "Tâche supprimée.",
"belongsToList": "Cette tâche appartient à la liste « {list} »",
"belongsToProject": "This task belongs to project '{project}'",
"due": "Échéance {at}",
"closePopup": "Fermer la fenêtre",
"delete": {
@ -645,7 +647,7 @@
"percentDone": "Définir la progression",
"attachments": "Ajouter des pièces jointes",
"relatedTasks": "Ajouter une relation",
"moveList": "Déplacer",
"moveProject": "Move",
"color": "Définir la couleur",
"delete": "Supprimer",
"favorite": "Ajouter aux favoris",
@ -672,21 +674,21 @@
"updated": "Mis à jour"
},
"subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
"subscribedProjectThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this project through its namespace.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
"subscribedTaskThroughParentProject": "You can't unsubscribe here because you are subscribed to this task through its project.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedProject": "You are currently subscribed to this project and will receive notifications for changes.",
"notSubscribedProject": "You are not subscribed to this project and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Sabonner",
"unsubscribe": "Se désabonner",
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessProject": "You are now subscribed to this project",
"unsubscribeSuccessProject": "You are now unsubscribed to this project",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
},
@ -760,7 +762,7 @@
"new": "Nouvelle relation de tâche",
"searchPlaceholder": "Écris la recherche dune nouvelle tâche à ajouter comme connexe…",
"createPlaceholder": "Ajouter cette tâche comme nouvelle tâche connexe",
"differentList": "Cette tâche appartient à une autre liste.",
"differentProject": "This task belongs to a different project.",
"differentNamespace": "Cette tâche fait partie d'un espace de noms différent.",
"noneYet": "Pas encore de relations de tâches.",
"delete": "Supprimer la relation de tâche",
@ -810,10 +812,10 @@
"priority1": "Pour définir la priorité dune tâche, ajoutez un chiffre de 1 à 5, précédé dun {prefix}.",
"priority2": "Plus le numéro est élevé, plus la priorité est élevée.",
"assignees": "Pour assigner directement la tâche à un utilisateur, ajoutez son nom d'utilisateur préfixé avec {prefix} à la tâche.",
"list1": "Pour définir une liste dans laquelle la tâche doit apparaître, entrez son nom précédé de {prefix}.",
"list2": "Ceci renverra une erreur si la liste nexiste pas.",
"list3": "To use spaces, simply add a \" or ' around the list name.",
"list4": "For example: {prefix}\"List with spaces\".",
"project1": "To set a project for the task to appear in, enter its name prefixed with {prefix}.",
"project2": "This will return an error if the project does not exist.",
"project3": "To use spaces, simply add a \" or ' around the project name.",
"project4": "For example: {prefix}\"Project with spaces\".",
"dateAndTime": "Date et heure",
"date": "Toute date sera utilisée comme date déchéance de la nouvelle tâche. Vous pouvez utiliser des dates dans un des formats suivants :",
"dateWeekday": "nimporte quel jour de la semaine, utilisera la date suivante avec cette date",
@ -846,19 +848,19 @@
"delete": {
"header": "Supprimer léquipe",
"text1": "Supprimer cette équipe et tous ses membres ?",
"text2": "Tous les membres de l'équipe perdront l'accès aux listes et aux espaces de noms partagés avec cette équipe. Ceci NE PEUT PAS ÊTRE ANNULÉ !",
"text2": "All team members will lose access to projects and namespaces shared with this team. This CANNOT BE UNDONE!",
"success": "Équipe supprimée."
},
"deleteUser": {
"header": "Retirer un·e utilisateur·rice de léquipe",
"text1": "Retirer cette personne de léquipe ?",
"text2": "Ils perdront l'accès à toutes les listes et espaces de noms auxquels cette équipe a accès. Ceci NE PEUT PAS ÊTRE ANNULÉ !",
"text2": "They will lose access to all projects and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "Utilisateur·rice retiré·e de léquipe."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will lose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"text2": "You will lose access to all projects and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
}
},
@ -890,22 +892,22 @@
"attachment": "Ajouter une pièce jointe à cette tâche",
"related": "Modifier les tâches connexes de cette tâche",
"color": "Changer la couleur de cette tâche",
"move": "Déplacer cette tâche dans une autre liste",
"move": "Move this task to another project",
"reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
},
"list": {
"title": "Vues en liste",
"switchToListView": "Passer en vue liste",
"switchToGanttView": "Passer en vue Gantt",
"switchToKanbanView": "Passer en vue kanban",
"switchToTableView": "Passer en vue tableau"
"project": {
"title": "Project Views",
"switchToListView": "Switch to list view",
"switchToGanttView": "Switch to gantt view",
"switchToKanbanView": "Switch to kanban view",
"switchToTableView": "Switch to table view"
},
"navigation": {
"title": "Navigation",
"overview": "Navigate to overview",
"upcoming": "Navigate to upcoming tasks",
"namespaces": "Navigate to namespaces & lists",
"namespaces": "Navigate to namespaces & projects",
"labels": "Accéder aux étiquettes",
"teams": "Accéder aux équipes"
}
@ -922,7 +924,7 @@
"unarchive": "Désarchiver",
"setBackground": "Définir larrière-plan",
"share": "Partager",
"newList": "Nouvelle liste"
"newProject": "New project"
},
"apiConfig": {
"url": "URL Vikunja",
@ -941,24 +943,24 @@
"notification": {
"title": "Notifications",
"none": "Tu nas pas de notifications. Passe une bonne journée !",
"explainer": "Les notifications apparaissent ici lorsque des actions (pour les espaces de noms, les listes ou les tâches) auxquelles tu es abonné·e se produisent."
"explainer": "Notifications will appear here when actions on namespaces, projects or tasks you subscribed to happen."
},
"quickActions": {
"commands": "Commandes",
"placeholder": "Écris une commande ou une recherche…",
"hint": "Vous pouvez utiliser {list} pour limiter la recherche à une liste. Combiner {list} ou {label} (étiquettes) avec une requête de recherche pour rechercher une tâche avec ces étiquettes ou sur cette liste. Utilisez {assignee} pour rechercher uniquement des équipes.",
"hint": "You can use {project} to limit the search to a project. Combine {project} or {label} (labels) with a search query to search for a task with these labels or on that project. Use {assignee} to only search for teams.",
"tasks": "Tâches",
"lists": "Listes",
"projects": "Projects",
"teams": "Équipes",
"newList": "Entre le nom de la liste…",
"newProject": "Enter the title of the new project…",
"newTask": "Entre le nom de la tâche…",
"newNamespace": "Entre le nom de lespace de noms…",
"newTeam": "Entre le nom de la nouvelle équipe…",
"createTask": "Créer une tâche dans la liste actuelle ({title})",
"createList": "Créer une liste dans lespace de noms actuel ({title})",
"createTask": "Create a task in the current project ({title})",
"createProject": "Create a project in the current namespace ({title})",
"cmds": {
"newTask": "Nouvelle tâche",
"newList": "Nouvelle liste",
"newProject": "New project",
"newNamespace": "Nouvel espace de noms",
"newTeam": "Nouvelle équipe"
}
@ -990,15 +992,15 @@
"1018": "Le paramètre du type davatar de lutilisateur·rice est invalide.",
"2001": "Lidentifiant ne peut pas être vide ou égal à 0.",
"2002": "Certaines des données de la requête étaient invalides.",
"3001": "La liste nexiste pas.",
"3004": "Tu dois avoir des droits de lecture sur cette liste pour effectuer cette action.",
"3005": "Tu dois entrer un nom de liste.",
"3006": "Le partage de liste nexiste pas.",
"3007": "Une liste avec cet identifiant existe déjà.",
"3008": "La liste est archivée et ne peut donc être consultée quen lecture seule. Ceci est également vrai pour toutes les tâches associées à cette liste.",
"4001": "Le texte de la tâche de liste ne peut pas être vide.",
"4002": "La tâche de liste nexiste pas.",
"4003": "Toutes les tâches de modification en bloc doivent appartenir à la même liste.",
"3001": "The project does not exist.",
"3004": "You need to have read permissions on that project to perform that action.",
"3005": "The project title cannot be empty.",
"3006": "The project share does not exist.",
"3007": "A project with this identifier already exists.",
"3008": "The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project.",
"4001": "The project task text cannot be empty.",
"4002": "The project task does not exist.",
"4003": "All bulk editing tasks must belong to the same project.",
"4004": "Besoin dau moins une tâche lors de la modification en bloc de tâches.",
"4005": "Tu nas pas le droit de voir la tâche.",
"4006": "Tu ne peux pas définir une tâche parente comme tâche elle-même.",
@ -1024,21 +1026,21 @@
"5012": "Lespace de noms est archivé et ne peut donc être consulté quen lecture seule.",
"6001": "Le nom de l'équipe ne peut pas être vide.",
"6002": "Léquipe nexiste pas.",
"6004": "Léquipe a déjà accès à cet espace de noms ou à cette liste.",
"6004": "The team already has access to that namespace or project.",
"6005": "Lutilisateur·rice est déjà membre de cette équipe.",
"6006": "Impossible de supprimer le dernier membre de léquipe.",
"6007": "Léquipe na pas accès à la liste pour effectuer cette action.",
"7002": "Lutilisateur·rice a déjà accès à cette liste.",
"7003": "Tu nas pas accès à cette liste.",
"6007": "The team does not have access to the project to perform that action.",
"7002": "The user already has access to that project.",
"7003": "You do not have access to that project.",
"8001": "Cette étiquette existe déjà sur cette tâche.",
"8002": "Létiquette nexiste pas.",
"8003": "Tu nas pas accès à cette étiquette.",
"9001": "Le droit est invalide.",
"10001": "Le seau nexiste pas.",
"10002": "Le seau ne fait pas partie de cette liste.",
"10003": "Tu ne peux pas supprimer le dernier seau dune liste.",
"10002": "The bucket does not belong to that project.",
"10003": "You cannot remove the last bucket on a project.",
"10004": "Tu ne peux pas ajouter la tâche à ce seau car il a déjà dépassé la limite de tâches quil peut contenir.",
"10005": "Il peut y avoir seulement un seau des terminés par liste.",
"10005": "There can be only one done bucket per project.",
"11001": "Le filtre enregistré nexiste pas.",
"11002": "Les filtres enregistrés ne sont pas disponibles pour les partages de liens.",
"12001": "Le type dentité dabonnement est invalide.",

Some files were not shown because too many files have changed in this diff Show More